Skip to content

Commit b63ea71

Browse files
committed
Add long path support wrt #39
1 parent e2b5ebc commit b63ea71

File tree

3 files changed

+78
-4
lines changed

3 files changed

+78
-4
lines changed

file-io.cabal

+11-1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,11 @@ flag os-string
2929
default: False
3030
manual: False
3131

32+
flag long-paths
33+
description: Enable a hack for ad-hoc long path support on Windows
34+
default: True
35+
manual: True
36+
3237
library
3338
default-language: Haskell2010
3439

@@ -51,6 +56,9 @@ library
5156
else
5257
build-depends: filepath >= 1.4.100.0 && < 1.5.0.0
5358

59+
if flag(long-paths)
60+
cpp-options: -DLONG_PATHS
61+
5462
exposed-modules:
5563
System.File.OsPath
5664
System.File.OsPath.Internal
@@ -111,6 +119,8 @@ test-suite Properties
111119
main-is: Properties.hs
112120
type: exitcode-stdio-1.0
113121
default-language: Haskell2010
114-
build-depends: base >=4.13.0.0 && <5, bytestring, tasty, tasty-hunit, file-io, filepath, temporary
122+
build-depends: base >=4.13.0.0 && <5, bytestring, directory, tasty, tasty-hunit, file-io, filepath, temporary
115123
ghc-options: -Wall -threaded -rtsopts "-with-rtsopts=-N10"
124+
if flag(long-paths)
125+
cpp-options: -DLONG_PATHS
116126

tests/Properties.hs

+33-1
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,21 @@ import GHC.IO.Exception (IOErrorType(..), IOException(..))
1818
import System.IO
1919
import System.IO.Temp
2020
import qualified Data.ByteString as BS
21+
#if defined(LONG_PATHS)
22+
import Control.Monad (when)
23+
import System.Directory.OsPath (createDirectory, makeAbsolute)
24+
import System.IO.Error (catchIOError)
25+
#endif
2126

2227

2328
main :: IO ()
2429
main = defaultMain $ testGroup "All"
2530
[ testGroup "System.File.OsPath"
26-
[ testCase "readFile . writeFile" writeFileReadFile
31+
[
32+
#if defined(LONG_PATHS)
33+
testCase "writeFile (very long path)" writeFileLongPath,
34+
#endif
35+
testCase "readFile . writeFile" writeFileReadFile
2736
, testCase "readFile . writeFile . writeFile" writeWriteFileReadFile
2837
, testCase "readFile . appendFile . writeFile" appendFileReadFile
2938
, testCase "iomode: ReadFile does not allow write" iomodeReadFile
@@ -56,6 +65,29 @@ main = defaultMain $ testGroup "All"
5665
]
5766
]
5867

68+
#if defined(LONG_PATHS)
69+
writeFileLongPath :: Assertion
70+
writeFileLongPath = do
71+
withSystemTempDirectory "test" $ \baseDir' -> do
72+
baseDir <- OSP.encodeFS baseDir'
73+
let longName = mconcat (replicate 10 [osp|foo|])
74+
let longDir = baseDir </> longName </> longName
75+
76+
supportsLongPaths <- do
77+
-- create 2 dirs because 1 path segment by itself can't exceed MAX_PATH
78+
-- tests: [createDirectory]
79+
createDirectory =<< makeAbsolute longName
80+
createDirectory longDir
81+
return True
82+
`catchIOError` \ _ ->
83+
return False
84+
85+
when supportsLongPaths $ do
86+
OSP.writeFile (longDir </> [osp|foo|]) "test"
87+
contents <- OSP.readFile (longDir </> [osp|foo|])
88+
"test" @=? contents
89+
#endif
90+
5991
writeFileReadFile :: Assertion
6092
writeFileReadFile = do
6193
withSystemTempDirectory "test" $ \baseDir' -> do

windows/System/File/Platform.hsc

+34-2
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,22 @@ import "filepath" System.OsString.Internal.Types (WindowsString(..), WindowsChar
5252
import qualified "filepath" System.OsPath.Data.ByteString.Short.Word16 as BC
5353
#endif
5454

55+
import System.IO.Error (modifyIOError, ioeSetFileName)
56+
import GHC.IO.Encoding.UTF16 (mkUTF16le)
57+
import GHC.IO.Encoding.Failure (CodingFailureMode(TransliterateCodingFailure))
58+
import Control.Exception (displayException, Exception)
59+
60+
#if defined(LONG_PATHS)
61+
import System.IO.Error (ioeSetLocation, ioeGetLocation, catchIOError)
62+
import Data.Char (isAlpha, isAscii, toUpper)
63+
import qualified System.Win32.WindowsString.Info as WS
64+
#endif
65+
5566
-- | Open a file and return the 'Handle'.
5667
openFile :: WindowsPath -> IOMode -> IO Handle
57-
openFile fp iomode = bracketOnError
68+
openFile fp' iomode = (`ioeSetWsPath` fp') `modifyIOError` do
69+
fp <- furnishPath fp'
70+
bracketOnError
5871
(WS.createFile
5972
fp
6073
accessMode
@@ -104,7 +117,9 @@ writeShareMode =
104117

105118
-- | Open an existing file and return the 'Handle'.
106119
openExistingFile :: WindowsPath -> IOMode -> IO Handle
107-
openExistingFile fp iomode = bracketOnError
120+
openExistingFile fp' iomode = (`ioeSetWsPath` fp') `modifyIOError` do
121+
fp <- furnishPath fp'
122+
bracketOnError
108123
(WS.createFile
109124
fp
110125
accessMode
@@ -248,3 +263,20 @@ any_ = coerce BC.any
248263

249264
#endif
250265

266+
ioeSetWsPath :: IOError -> WindowsPath -> IOError
267+
ioeSetWsPath err =
268+
ioeSetFileName err .
269+
rightOrError .
270+
WS.decodeWith (mkUTF16le TransliterateCodingFailure)
271+
272+
rightOrError :: Exception e => Either e a -> a
273+
rightOrError (Left e) = error (displayException e)
274+
rightOrError (Right a) = a
275+
276+
-- inlined stuff from directory package
277+
furnishPath :: WindowsPath -> IO WindowsPath
278+
#if !defined(LONG_PATHS)
279+
furnishPath path = pure path
280+
#else
281+
furnishPath path = pure path
282+
#endif

0 commit comments

Comments
 (0)