2 {-# OPTIONS_HADDOCK hide #-}
4 module Distribution
.Compat
.CopyFile
15 import Distribution
.Compat
.Prelude
18 #ifndef mingw32_HOST_OS
19 import Distribution
.Compat
.Internal
.TempFile
21 import Control
.Exception
23 import qualified Data
.ByteString
.Lazy
as BSL
26 import System
.IO.Error
28 import System
.Directory
29 ( doesFileExist, renameFile, removeFile )
30 import System
.FilePath
33 ( IOMode(ReadMode
), hClose, hGetBuf
, hPutBuf
, hFileSize
38 import System
.Posix
.Types
40 import System
.Posix
.Files
41 ( getFileStatus
, fileMode
, setFileMode
)
43 #else /* else mingw32_HOST_OS
*/
45 import qualified Data
.ByteString
.Lazy
as BSL
46 import System
.IO.Error
48 import System
.Directory
50 import System
.FilePath
51 ( addTrailingPathSeparator
52 , hasTrailingPathSeparator
63 ( IOMode(ReadMode
), hFileSize
66 import qualified System
.Win32
.File
as Win32
( copyFile
)
67 #endif
/* mingw32_HOST_OS
*/
69 copyOrdinaryFile
, copyExecutableFile
:: FilePath -> FilePath -> IO ()
70 copyOrdinaryFile src dest
= copyFile src dest
>> setFileOrdinary dest
71 copyExecutableFile src dest
= copyFile src dest
>> setFileExecutable dest
73 setFileOrdinary
, setFileExecutable
, setDirOrdinary
:: FilePath -> IO ()
74 #ifndef mingw32_HOST_OS
75 -- When running with a restrictive UMASK such as 0077 we still want to
76 -- install files and directories that are accessible to other users.
77 setFileOrdinary path
= addFileMode path
0o644
-- file perms -rw-r--r--
78 setFileExecutable path
= addFileMode path
0o755
-- file perms -rwxr-xr-x
80 addFileMode
:: FilePath -> FileMode
-> IO ()
81 addFileMode name m
= do
82 o
<- fileMode
<$> getFileStatus name
83 setFileMode name
(m
.|
. o
)
85 setFileOrdinary _
= return ()
86 setFileExecutable _
= return ()
88 -- This happens to be true on Unix and currently on Windows too:
89 setDirOrdinary
= setFileExecutable
91 -- | Copies a file to a new destination.
92 -- Often you should use `copyFileChanged` instead.
93 copyFile
:: FilePath -> FilePath -> IO ()
94 copyFile fromFPath toFPath
=
96 `catchIO`
(\ioe
-> throwIO
(ioeSetLocation ioe
"copyFile"))
98 #ifndef mingw32_HOST_OS
99 copy
= withBinaryFile fromFPath ReadMode
$ \hFrom
->
100 bracketOnError openTmp cleanTmp
$ \(tmpFPath
, hTmp
) ->
101 do allocaBytes bufferSize
$ copyContents hFrom hTmp
103 renameFile tmpFPath toFPath
104 openTmp
= openBinaryTempFile
(takeDirectory toFPath
) ".copyFile.tmp"
105 cleanTmp
(tmpFPath
, hTmp
) = do
106 hClose hTmp `catchIO`
\_
-> return ()
107 removeFile tmpFPath `catchIO`
\_
-> return ()
110 copyContents hFrom hTo buffer
= do
111 count
<- hGetBuf hFrom buffer bufferSize
112 when (count
> 0) $ do
113 hPutBuf hTo buffer count
114 copyContents hFrom hTo buffer
116 copy
= Win32
.copyFile
(toExtendedLengthPath fromFPath
)
117 (toExtendedLengthPath toFPath
)
120 -- NOTE: Shamelessly lifted from System.Directory.Internal.Windows
122 -- | Add the @"\\\\?\\"@ prefix if necessary or possible. The path remains
123 -- unchanged if the prefix is not added. This function can sometimes be used
124 -- to bypass the @MAX_PATH@ length restriction in Windows API calls.
126 -- See Note [Path normalization].
127 toExtendedLengthPath
:: FilePath -> FilePath
128 toExtendedLengthPath path
129 | isRelative path
= path
131 case normalisedPath
of
132 '\\' : '?
' : '?
' : '\\' : _
-> normalisedPath
133 '\\' : '\\' : '?
' : '\\' : _
-> normalisedPath
134 '\\' : '\\' : '.' : '\\' : _
-> normalisedPath
135 '\\' : subpath
@('\\' : _
) -> "\\\\?\\UNC" <> subpath
136 _
-> "\\\\?\\" <> normalisedPath
137 where normalisedPath
= simplifyWindows path
139 -- | Similar to 'normalise' but:
141 -- * empty paths stay empty,
142 -- * parent dirs (@..@) are expanded, and
143 -- * paths starting with @\\\\?\\@ are preserved.
145 -- The goal is to preserve the meaning of paths better than 'normalise'.
147 -- Note [Path normalization]
148 -- 'normalise' doesn't simplify path names but will convert / into \\
149 -- this would normally not be a problem as once the path hits the RTS we would
150 -- have simplified the path then. However since we're calling the WIn32 API
151 -- directly we have to do the simplification before the call. Without this the
152 -- path Z:// would become Z:\\\\ and when converted to a device path the path
153 -- becomes \\?\Z:\\\\ which is an invalid path.
155 -- This is not a bug in normalise as it explicitly states that it won't simplify
157 simplifyWindows
:: FilePath -> FilePath
158 simplifyWindows
"" = ""
159 simplifyWindows path
=
161 "\\\\?\\" -> drive
' <> subpath
164 simplifiedPath
= joinDrive drive
' subpath
'
165 (drive
, subpath
) = splitDrive path
166 drive
' = upperDrive
(normaliseTrailingSep
(normalisePathSeps drive
))
167 subpath
' = appendSep
. avoidEmpty
. prependSep
. joinPath
.
168 stripPardirs
. expandDots
. skipSeps
.
169 splitDirectories
$ subpath
171 upperDrive d
= case d
of
172 c
: ':' : s |
isAlpha c
&& all isPathSeparator s
-> toUpper c
: ':' : s
174 skipSeps
= filter (not . (`
elem`
(pure
<$> pathSeparators
)))
175 stripPardirs | pathIsAbsolute || subpathIsAbsolute
= dropWhile (== "..")
177 prependSep | subpathIsAbsolute
= (pathSeparator
:)
179 avoidEmpty |
not pathIsAbsolute
180 && (null drive || hasTrailingPathSep
) -- prefer "C:" over "C:."
183 appendSep p | hasTrailingPathSep
184 && not (pathIsAbsolute
&& null p
)
185 = addTrailingPathSeparator p
187 pathIsAbsolute
= not (isRelative path
)
188 subpathIsAbsolute
= any isPathSeparator
(take 1 subpath
)
189 hasTrailingPathSep
= hasTrailingPathSeparator subpath
191 -- | Given a list of path segments, expand @.@ and @..@. The path segments
192 -- must not contain path separators.
193 expandDots
:: [FilePath] -> [FilePath]
194 expandDots
= reverse . go
[]
204 [] -> go
(x
: ys
') xs
205 ".." : _
-> go
(x
: ys
') xs
209 -- | Convert to the right kind of slashes.
210 normalisePathSeps
:: FilePath -> FilePath
211 normalisePathSeps p
= (\ c
-> if isPathSeparator c
then pathSeparator
else c
) <$> p
213 -- | Remove redundant trailing slashes and pick the right kind of slash.
214 normaliseTrailingSep
:: FilePath -> FilePath
215 normaliseTrailingSep path
= do
216 let path
' = reverse path
217 let (sep
, path
'') = span isPathSeparator path
'
218 let addSep
= if null sep
then id else (pathSeparator
:)
219 reverse (addSep path
'')
221 -- | Convert empty paths to the current directory, otherwise leave it
223 emptyToCurDir
:: FilePath -> FilePath
224 emptyToCurDir
"" = "."
225 emptyToCurDir path
= path
226 #endif
/* mingw32_HOST_OS
*/
228 -- | Like `copyFile`, but does not touch the target if source and destination
229 -- are already byte-identical. This is recommended as it is useful for
230 -- time-stamp based recompilation avoidance.
231 copyFileChanged
:: FilePath -> FilePath -> IO ()
232 copyFileChanged src dest
= do
233 equal
<- filesEqual src dest
234 unless equal
$ copyFile src dest
236 -- | Checks if two files are byte-identical.
237 -- Returns False if either of the files do not exist or if files
238 -- are of different size.
239 filesEqual
:: FilePath -> FilePath -> IO Bool
240 filesEqual f1 f2
= do
241 ex1
<- doesFileExist f1
242 ex2
<- doesFileExist f2
245 else withBinaryFile f1 ReadMode
$ \h1
->
246 withBinaryFile f2 ReadMode
$ \h2
-> do
252 c1
<- BSL
.hGetContents h1
253 c2
<- BSL
.hGetContents h2