You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

1076 lines
32 KiB

  1. -- |
  2. -- Module : HPath.IO
  3. -- Copyright : © 2016 Julian Ospald
  4. -- License : BSD3
  5. --
  6. -- Maintainer : Julian Ospald <hasufell@posteo.de>
  7. -- Stability : experimental
  8. -- Portability : portable
  9. --
  10. -- This module provides high-level IO related file operations like
  11. -- copy, delete, move and so on. It only operates on /Path x/ which
  12. -- guarantees us well-typed paths. Passing in /Path Abs/ to any
  13. -- of these functions generally increases safety. Passing /Path Rel/
  14. -- may trigger looking up the current directory via `getcwd` in some
  15. -- cases where it cannot be avoided.
  16. --
  17. -- Some functions are just path-safe wrappers around
  18. -- unix functions, others have stricter exception handling
  19. -- and some implement functionality that doesn't have a unix
  20. -- counterpart (like `copyDirRecursive`).
  21. --
  22. -- Some of these operations are due to their nature __not atomic__, which
  23. -- means they may do multiple syscalls which form one context. Some
  24. -- of them also have to examine the filetypes explicitly before the
  25. -- syscalls, so a reasonable decision can be made. That means
  26. -- the result is undefined if another process changes that context
  27. -- while the non-atomic operation is still happening. However, where
  28. -- possible, as few syscalls as possible are used and the underlying
  29. -- exception handling is kept.
  30. --
  31. -- Note: `BlockDevice`, `CharacterDevice`, `NamedPipe` and `Socket`
  32. -- are ignored by some of the more high-level functions (like `easyCopy`).
  33. -- For other functions (like `copyFile`), the behavior on these file types is
  34. -- unreliable/unsafe. Check the documentation of those functions for details.
  35. {-# LANGUAGE CPP #-}
  36. {-# LANGUAGE PackageImports #-}
  37. {-# LANGUAGE OverloadedStrings #-}
  38. module HPath.IO
  39. (
  40. -- * Types
  41. FileType(..)
  42. , RecursiveErrorMode(..)
  43. , CopyMode(..)
  44. -- * File copying
  45. , copyDirRecursive
  46. , recreateSymlink
  47. , copyFile
  48. , easyCopy
  49. -- * File deletion
  50. , deleteFile
  51. , deleteDir
  52. , deleteDirRecursive
  53. , easyDelete
  54. -- * File opening
  55. , openFile
  56. , executeFile
  57. -- * File creation
  58. , createRegularFile
  59. , createDir
  60. , createDirRecursive
  61. , createSymlink
  62. -- * File renaming/moving
  63. , renameFile
  64. , moveFile
  65. -- * File reading
  66. , readFile
  67. , readFileEOF
  68. -- * File writing
  69. , writeFile
  70. , appendFile
  71. -- * File permissions
  72. , newFilePerms
  73. , newDirPerms
  74. -- * Directory reading
  75. , getDirsFiles
  76. -- * Filetype operations
  77. , getFileType
  78. -- * Others
  79. , canonicalizePath
  80. , toAbs
  81. )
  82. where
  83. import Control.Applicative
  84. (
  85. (<$>)
  86. )
  87. import Control.Exception
  88. (
  89. IOException
  90. , bracket
  91. , throwIO
  92. )
  93. import Control.Monad
  94. (
  95. unless
  96. , void
  97. , when
  98. )
  99. import Control.Monad.IfElse
  100. (
  101. unlessM
  102. )
  103. import Data.ByteString
  104. (
  105. ByteString
  106. )
  107. #if MIN_VERSION_bytestring(0,10,2)
  108. import Data.ByteString.Builder
  109. #else
  110. import Data.ByteString.Lazy.Builder
  111. #endif
  112. (
  113. Builder
  114. , byteString
  115. , toLazyByteString
  116. )
  117. import qualified Data.ByteString.Lazy as L
  118. import Data.ByteString.Unsafe
  119. (
  120. unsafePackCStringFinalizer
  121. )
  122. import Data.Foldable
  123. (
  124. for_
  125. )
  126. import Data.IORef
  127. (
  128. IORef
  129. , modifyIORef
  130. , newIORef
  131. , readIORef
  132. )
  133. import Data.Maybe
  134. (
  135. catMaybes
  136. )
  137. import Data.Monoid
  138. (
  139. (<>)
  140. , mempty
  141. )
  142. import Data.Word
  143. (
  144. Word8
  145. )
  146. import Foreign.C.Error
  147. (
  148. eEXIST
  149. , eNOENT
  150. , eNOTEMPTY
  151. , eXDEV
  152. , getErrno
  153. )
  154. import Foreign.C.Types
  155. (
  156. CSize
  157. )
  158. import Foreign.Marshal.Alloc
  159. (
  160. allocaBytes
  161. )
  162. import Foreign.Ptr
  163. (
  164. Ptr
  165. )
  166. import GHC.IO.Exception
  167. (
  168. IOErrorType(..)
  169. )
  170. import HPath
  171. import HPath.Internal
  172. import HPath.IO.Errors
  173. import Prelude hiding (appendFile, readFile, writeFile)
  174. import qualified Streamly.FileSystem.Handle as FH
  175. import qualified Streamly.Internal.FileSystem.Handle as IFH
  176. import qualified Streamly.Prelude as S
  177. import qualified System.IO as SIO
  178. import System.IO.Error
  179. (
  180. catchIOError
  181. , ioeGetErrorType
  182. )
  183. import System.Posix.ByteString
  184. (
  185. exclusive
  186. )
  187. import System.Posix.Directory.ByteString
  188. (
  189. createDirectory
  190. , getWorkingDirectory
  191. , removeDirectory
  192. )
  193. import System.Posix.Directory.Traversals
  194. (
  195. getDirectoryContents'
  196. )
  197. import System.Posix.Files.ByteString
  198. (
  199. createSymbolicLink
  200. , fileMode
  201. , getFdStatus
  202. , groupExecuteMode
  203. , groupReadMode
  204. , groupWriteMode
  205. , otherExecuteMode
  206. , otherReadMode
  207. , otherWriteMode
  208. , ownerModes
  209. , ownerReadMode
  210. , ownerWriteMode
  211. , readSymbolicLink
  212. , removeLink
  213. , rename
  214. , setFileMode
  215. , unionFileModes
  216. )
  217. import qualified System.Posix.Files.ByteString as PF
  218. import qualified "unix" System.Posix.IO.ByteString as SPI
  219. import qualified "unix-bytestring" System.Posix.IO.ByteString as SPB
  220. import System.Posix.FD
  221. (
  222. openFd
  223. )
  224. import qualified System.Posix.Directory.Traversals as SPDT
  225. import qualified System.Posix.Directory.Foreign as SPDF
  226. import qualified System.Posix.Process.ByteString as SPP
  227. import System.Posix.Types
  228. (
  229. FileMode
  230. , ProcessID
  231. , Fd
  232. )
  233. -------------
  234. --[ Types ]--
  235. -------------
  236. data FileType = Directory
  237. | RegularFile
  238. | SymbolicLink
  239. | BlockDevice
  240. | CharacterDevice
  241. | NamedPipe
  242. | Socket
  243. deriving (Eq, Show)
  244. -- |The error mode for recursive operations.
  245. --
  246. -- On `FailEarly` the whole operation fails immediately if any of the
  247. -- recursive sub-operations fail, which is sort of the default
  248. -- for IO operations.
  249. --
  250. -- On `CollectFailures` skips errors in the recursion and keeps on recursing.
  251. -- However all errors are collected in the `RecursiveFailure` error type,
  252. -- which is raised finally if there was any error. Also note that
  253. -- `RecursiveFailure` does not give any guarantees on the ordering
  254. -- of the collected exceptions.
  255. data RecursiveErrorMode = FailEarly
  256. | CollectFailures
  257. -- |The mode for copy and file moves.
  258. -- Overwrite mode is usually not very well defined, but is a convenience
  259. -- shortcut.
  260. data CopyMode = Strict -- ^ fail if any target exists
  261. | Overwrite -- ^ overwrite targets
  262. --------------------
  263. --[ File Copying ]--
  264. --------------------
  265. -- |Copies the contents of a directory recursively to the given destination, while preserving permissions.
  266. -- Does not follow symbolic links. This behaves more or less like
  267. -- the following, without descending into the destination if it
  268. -- already exists:
  269. --
  270. -- @
  271. -- cp -a \/source\/dir \/destination\/somedir
  272. -- @
  273. --
  274. -- For directory contents, this will ignore any file type that is not
  275. -- `RegularFile`, `SymbolicLink` or `Directory`.
  276. --
  277. -- For `Overwrite` copy mode this does not prune destination directory
  278. -- contents, so the destination might contain more files than the source after
  279. -- the operation has completed. Permissions of existing directories are
  280. -- fixed.
  281. --
  282. -- Safety/reliability concerns:
  283. --
  284. -- * not atomic
  285. -- * examines filetypes explicitly
  286. -- * an explicit check `throwDestinationInSource` is carried out for the
  287. -- top directory for basic sanity, because otherwise we might end up
  288. -- with an infinite copy loop... however, this operation is not
  289. -- carried out recursively (because it's slow)
  290. --
  291. -- Throws:
  292. --
  293. -- - `NoSuchThing` if source directory does not exist
  294. -- - `PermissionDenied` if source directory can't be opened
  295. -- - `SameFile` if source and destination are the same file
  296. -- (`HPathIOException`)
  297. -- - `DestinationInSource` if destination is contained in source
  298. -- (`HPathIOException`)
  299. --
  300. -- Throws in `FailEarly` RecursiveErrorMode only:
  301. --
  302. -- - `PermissionDenied` if output directory is not writable
  303. -- - `InvalidArgument` if source directory is wrong type (symlink)
  304. -- - `InappropriateType` if source directory is wrong type (regular file)
  305. --
  306. -- Throws in `CollectFailures` RecursiveErrorMode only:
  307. --
  308. -- - `RecursiveFailure` if any of the recursive operations that are not
  309. -- part of the top-directory sanity-checks fail (`HPathIOException`)
  310. --
  311. -- Throws in `Strict` CopyMode only:
  312. --
  313. -- - `AlreadyExists` if destination already exists
  314. --
  315. -- Note: may call `getcwd` (only if destination is a relative path)
  316. copyDirRecursive :: Path b1 -- ^ source dir
  317. -> Path b2 -- ^ destination (parent dirs
  318. -- are not automatically created)
  319. -> CopyMode
  320. -> RecursiveErrorMode
  321. -> IO ()
  322. copyDirRecursive fromp destdirp cm rm
  323. = do
  324. ce <- newIORef []
  325. -- for performance, sanity checks are only done for the top dir
  326. throwSameFile fromp destdirp
  327. throwDestinationInSource fromp destdirp
  328. go ce fromp destdirp
  329. collectedExceptions <- readIORef ce
  330. unless (null collectedExceptions)
  331. (throwIO . RecursiveFailure $ collectedExceptions)
  332. where
  333. go :: IORef [(RecursiveFailureHint, IOException)]
  334. -> Path b1 -> Path b2 -> IO ()
  335. go ce fromp'@(MkPath fromBS) destdirp'@(MkPath destdirpBS) = do
  336. -- NOTE: order is important here, so we don't get empty directories
  337. -- on failure
  338. -- get the contents of the source dir
  339. contents <- handleIOE (ReadContentsFailed fromBS destdirpBS) ce [] $ do
  340. contents <- getDirsFiles fromp'
  341. -- create the destination dir and
  342. -- only return contents if we succeed
  343. handleIOE (CreateDirFailed fromBS destdirpBS) ce [] $ do
  344. fmode' <- PF.fileMode <$> PF.getSymbolicLinkStatus fromBS
  345. case cm of
  346. Strict -> createDirectory destdirpBS fmode'
  347. Overwrite -> catchIOError (createDirectory destdirpBS
  348. fmode')
  349. $ \e ->
  350. case ioeGetErrorType e of
  351. AlreadyExists -> setFileMode destdirpBS
  352. fmode'
  353. _ -> ioError e
  354. return contents
  355. -- NOTE: we can't use `easyCopy` here, because we want to call `go`
  356. -- recursively to skip the top-level sanity checks
  357. -- if reading the contents and creating the destination dir worked,
  358. -- then copy the contents to the destination too
  359. for_ contents $ \f -> do
  360. ftype <- getFileType f
  361. newdest <- (destdirp' </>) <$> basename f
  362. case ftype of
  363. SymbolicLink -> handleIOE (RecreateSymlinkFailed (toFilePath f) (toFilePath newdest)) ce ()
  364. $ recreateSymlink f newdest cm
  365. Directory -> go ce f newdest
  366. RegularFile -> handleIOE (CopyFileFailed (toFilePath f) (toFilePath newdest)) ce ()
  367. $ copyFile f newdest cm
  368. _ -> return ()
  369. -- helper to handle errors for both RecursiveErrorModes and return a
  370. -- default value
  371. handleIOE :: RecursiveFailureHint
  372. -> IORef [(RecursiveFailureHint, IOException)]
  373. -> a -> IO a -> IO a
  374. handleIOE hint ce def = case rm of
  375. FailEarly -> handleIOError throwIO
  376. CollectFailures -> handleIOError (\e -> modifyIORef ce ((hint, e):)
  377. >> return def)
  378. -- |Recreate a symlink.
  379. --
  380. -- In `Overwrite` copy mode only files and empty directories are deleted.
  381. --
  382. -- Safety/reliability concerns:
  383. --
  384. -- * `Overwrite` mode is inherently non-atomic
  385. --
  386. -- Throws:
  387. --
  388. -- - `InvalidArgument` if source file is wrong type (not a symlink)
  389. -- - `PermissionDenied` if output directory cannot be written to
  390. -- - `PermissionDenied` if source directory cannot be opened
  391. -- - `SameFile` if source and destination are the same file
  392. -- (`HPathIOException`)
  393. --
  394. --
  395. -- Throws in `Strict` mode only:
  396. --
  397. -- - `AlreadyExists` if destination already exists
  398. --
  399. -- Throws in `Overwrite` mode only:
  400. --
  401. -- - `UnsatisfiedConstraints` if destination file is non-empty directory
  402. --
  403. -- Notes:
  404. --
  405. -- - calls `symlink`
  406. -- - calls `getcwd` in Overwrite mode (if destination is a relative path)
  407. recreateSymlink :: Path b1 -- ^ the old symlink file
  408. -> Path b2 -- ^ destination file
  409. -> CopyMode
  410. -> IO ()
  411. recreateSymlink symsource@(MkPath symsourceBS) newsym@(MkPath newsymBS) cm
  412. = do
  413. throwSameFile symsource newsym
  414. sympoint <- readSymbolicLink symsourceBS
  415. case cm of
  416. Strict -> return ()
  417. Overwrite -> do
  418. writable <- toAbs newsym >>= isWritable
  419. isfile <- doesFileExist newsym
  420. isdir <- doesDirectoryExist newsym
  421. when (writable && isfile) (deleteFile newsym)
  422. when (writable && isdir) (deleteDir newsym)
  423. createSymbolicLink sympoint newsymBS
  424. -- |Copies the given regular file to the given destination.
  425. -- Neither follows symbolic links, nor accepts them.
  426. -- For "copying" symbolic links, use `recreateSymlink` instead.
  427. --
  428. -- Note that this is still sort of a low-level function and doesn't
  429. -- examine file types. For a more high-level version, use `easyCopy`
  430. -- instead.
  431. --
  432. -- In `Overwrite` copy mode only overwrites actual files, not directories.
  433. -- In `Strict` mode the destination file must not exist.
  434. --
  435. -- Safety/reliability concerns:
  436. --
  437. -- * `Overwrite` mode is not atomic
  438. -- * when used on `CharacterDevice`, reads the "contents" and copies
  439. -- them to a regular file, which might take indefinitely
  440. -- * when used on `BlockDevice`, may either read the "contents"
  441. -- and copy them to a regular file (potentially hanging indefinitely)
  442. -- or may create a regular empty destination file
  443. -- * when used on `NamedPipe`, will hang indefinitely
  444. --
  445. -- Throws:
  446. --
  447. -- - `NoSuchThing` if source file does not exist
  448. -- - `NoSuchThing` if source file is a a `Socket`
  449. -- - `PermissionDenied` if output directory is not writable
  450. -- - `PermissionDenied` if source directory can't be opened
  451. -- - `InvalidArgument` if source file is wrong type (symlink or directory)
  452. -- - `SameFile` if source and destination are the same file
  453. -- (`HPathIOException`)
  454. --
  455. -- Throws in `Strict` mode only:
  456. --
  457. -- - `AlreadyExists` if destination already exists
  458. --
  459. -- Notes:
  460. --
  461. -- - may call `getcwd` in Overwrite mode (if destination is a relative path)
  462. copyFile :: Path b1 -- ^ source file
  463. -> Path b2 -- ^ destination file
  464. -> CopyMode
  465. -> IO ()
  466. copyFile fp@(MkPath from) tp@(MkPath to) cm = do
  467. throwSameFile fp tp
  468. bracket (do
  469. fd <- openFd from SPI.ReadOnly [SPDF.oNofollow] Nothing
  470. handle <- SPI.fdToHandle fd
  471. pure (fd, handle))
  472. (\(_, handle) -> SIO.hClose handle)
  473. $ \(fromFd, fH) -> do
  474. sourceFileMode <- System.Posix.Files.ByteString.fileMode <$> getFdStatus fromFd
  475. let dflags = [SPDF.oNofollow, case cm of
  476. Strict -> SPDF.oExcl
  477. Overwrite -> SPDF.oTrunc]
  478. bracketeer (do
  479. fd <- openFd to SPI.WriteOnly dflags $ Just sourceFileMode
  480. handle <- SPI.fdToHandle fd
  481. pure (fd, handle))
  482. (\(_, handle) -> SIO.hClose handle)
  483. (\(_, handle) -> do
  484. SIO.hClose handle
  485. case cm of
  486. -- if we created the file and copying failed, it's
  487. -- safe to clean up
  488. Strict -> deleteFile tp
  489. Overwrite -> pure ())
  490. $ \(_, tH) -> do
  491. SIO.hSetBinaryMode fH True
  492. SIO.hSetBinaryMode tH True
  493. streamlyCopy (fH, tH)
  494. where
  495. streamlyCopy (fH, tH) = S.fold (FH.writeChunks tH) $ IFH.toChunksWithBufferOf (256*1024) fH
  496. -- |Copies a regular file, directory or symbolic link. In case of a
  497. -- symbolic link it is just recreated, even if it points to a directory.
  498. -- Any other file type is ignored.
  499. --
  500. -- Safety/reliability concerns:
  501. --
  502. -- * examines filetypes explicitly
  503. -- * calls `copyDirRecursive` for directories
  504. --
  505. -- Note: may call `getcwd` in Overwrite mode (if destination is a relative path)
  506. easyCopy :: Path b1
  507. -> Path b2
  508. -> CopyMode
  509. -> RecursiveErrorMode
  510. -> IO ()
  511. easyCopy from to cm rm = do
  512. ftype <- getFileType from
  513. case ftype of
  514. SymbolicLink -> recreateSymlink from to cm
  515. RegularFile -> copyFile from to cm
  516. Directory -> copyDirRecursive from to cm rm
  517. _ -> return ()
  518. ---------------------
  519. --[ File Deletion ]--
  520. ---------------------
  521. -- |Deletes the given file. Raises `eISDIR`
  522. -- if run on a directory. Does not follow symbolic links.
  523. --
  524. -- Throws:
  525. --
  526. -- - `InappropriateType` for wrong file type (directory)
  527. -- - `NoSuchThing` if the file does not exist
  528. -- - `PermissionDenied` if the directory cannot be read
  529. deleteFile :: Path b -> IO ()
  530. deleteFile (MkPath p) = removeLink p
  531. -- |Deletes the given directory, which must be empty, never symlinks.
  532. --
  533. -- Throws:
  534. --
  535. -- - `InappropriateType` for wrong file type (symlink to directory)
  536. -- - `InappropriateType` for wrong file type (regular file)
  537. -- - `NoSuchThing` if directory does not exist
  538. -- - `UnsatisfiedConstraints` if directory is not empty
  539. -- - `PermissionDenied` if we can't open or write to parent directory
  540. --
  541. -- Notes: calls `rmdir`
  542. deleteDir :: Path b -> IO ()
  543. deleteDir (MkPath p) = removeDirectory p
  544. -- |Deletes the given directory recursively. Does not follow symbolic
  545. -- links. Tries `deleteDir` first before attemtping a recursive
  546. -- deletion.
  547. --
  548. -- On directory contents this behaves like `easyDelete`
  549. -- and thus will ignore any file type that is not `RegularFile`,
  550. -- `SymbolicLink` or `Directory`.
  551. --
  552. -- Safety/reliability concerns:
  553. --
  554. -- * not atomic
  555. -- * examines filetypes explicitly
  556. --
  557. -- Throws:
  558. --
  559. -- - `InappropriateType` for wrong file type (symlink to directory)
  560. -- - `InappropriateType` for wrong file type (regular file)
  561. -- - `NoSuchThing` if directory does not exist
  562. -- - `PermissionDenied` if we can't open or write to parent directory
  563. deleteDirRecursive :: Path b -> IO ()
  564. deleteDirRecursive p =
  565. catchErrno [eNOTEMPTY, eEXIST]
  566. (deleteDir p)
  567. $ do
  568. files <- getDirsFiles p
  569. for_ files $ \file -> do
  570. ftype <- getFileType file
  571. case ftype of
  572. SymbolicLink -> deleteFile file
  573. Directory -> deleteDirRecursive file
  574. RegularFile -> deleteFile file
  575. _ -> return ()
  576. removeDirectory . toFilePath $ p
  577. -- |Deletes a file, directory or symlink.
  578. -- In case of directory, performs recursive deletion. In case of
  579. -- a symlink, the symlink file is deleted.
  580. -- Any other file type is ignored.
  581. --
  582. -- Safety/reliability concerns:
  583. --
  584. -- * examines filetypes explicitly
  585. -- * calls `deleteDirRecursive` for directories
  586. easyDelete :: Path b -> IO ()
  587. easyDelete p = do
  588. ftype <- getFileType p
  589. case ftype of
  590. SymbolicLink -> deleteFile p
  591. Directory -> deleteDirRecursive p
  592. RegularFile -> deleteFile p
  593. _ -> return ()
  594. --------------------
  595. --[ File Opening ]--
  596. --------------------
  597. -- |Opens a file appropriately by invoking xdg-open. The file type
  598. -- is not checked. This forks a process.
  599. openFile :: Path b
  600. -> IO ProcessID
  601. openFile (MkPath fp) =
  602. SPP.forkProcess $ SPP.executeFile "xdg-open" True [fp] Nothing
  603. -- |Executes a program with the given arguments. This forks a process.
  604. executeFile :: Path b -- ^ program
  605. -> [ByteString] -- ^ arguments
  606. -> IO ProcessID
  607. executeFile (MkPath fp) args =
  608. SPP.forkProcess $ SPP.executeFile fp True args Nothing
  609. ---------------------
  610. --[ File Creation ]--
  611. ---------------------
  612. -- |Create an empty regular file at the given directory with the given
  613. -- filename.
  614. --
  615. -- Throws:
  616. --
  617. -- - `PermissionDenied` if output directory cannot be written to
  618. -- - `AlreadyExists` if destination already exists
  619. -- - `NoSuchThing` if any of the parent components of the path
  620. -- do not exist
  621. createRegularFile :: FileMode -> Path b -> IO ()
  622. createRegularFile fm (MkPath destBS) =
  623. bracket (SPI.openFd destBS SPI.WriteOnly (Just fm)
  624. (SPI.defaultFileFlags { exclusive = True }))
  625. SPI.closeFd
  626. (\_ -> return ())
  627. -- |Create an empty directory at the given directory with the given filename.
  628. --
  629. -- Throws:
  630. --
  631. -- - `PermissionDenied` if output directory cannot be written to
  632. -- - `AlreadyExists` if destination already exists
  633. -- - `NoSuchThing` if any of the parent components of the path
  634. -- do not exist
  635. createDir :: FileMode -> Path b -> IO ()
  636. createDir fm (MkPath destBS) = createDirectory destBS fm
  637. -- |Create an empty directory at the given directory with the given filename.
  638. -- All parent directories are created with the same filemode. This
  639. -- basically behaves like:
  640. --
  641. -- @
  642. -- mkdir -p \/some\/dir
  643. -- @
  644. --
  645. -- Safety/reliability concerns:
  646. --
  647. -- * not atomic
  648. --
  649. -- Throws:
  650. --
  651. -- - `PermissionDenied` if any part of the path components do not
  652. -- exist and cannot be written to
  653. -- - `AlreadyExists` if destination already exists and
  654. -- is not a directory
  655. --
  656. -- Note: calls `getcwd` if the input path is a relative path
  657. createDirRecursive :: FileMode -> Path b -> IO ()
  658. createDirRecursive fm p =
  659. toAbs p >>= go
  660. where
  661. go :: Path Abs -> IO ()
  662. go dest@(MkPath destBS) = do
  663. catchIOError (createDirectory destBS fm) $ \e -> do
  664. errno <- getErrno
  665. case errno of
  666. en | en == eEXIST -> unlessM (doesDirectoryExist dest) (ioError e)
  667. | en == eNOENT -> createDirRecursive fm (dirname dest)
  668. >> createDirectory destBS fm
  669. | otherwise -> ioError e
  670. -- |Create a symlink.
  671. --
  672. -- Throws:
  673. --
  674. -- - `PermissionDenied` if output directory cannot be written to
  675. -- - `AlreadyExists` if destination file already exists
  676. -- - `NoSuchThing` if any of the parent components of the path
  677. -- do not exist
  678. --
  679. -- Note: calls `symlink`
  680. createSymlink :: Path b -- ^ destination file
  681. -> ByteString -- ^ path the symlink points to
  682. -> IO ()
  683. createSymlink (MkPath destBS) sympoint
  684. = createSymbolicLink sympoint destBS
  685. ----------------------------
  686. --[ File Renaming/Moving ]--
  687. ----------------------------
  688. -- |Rename a given file with the provided filename. Destination and source
  689. -- must be on the same device, otherwise `eXDEV` will be raised.
  690. --
  691. -- Does not follow symbolic links, but renames the symbolic link file.
  692. --
  693. -- Safety/reliability concerns:
  694. --
  695. -- * has a separate set of exception handling, apart from the syscall
  696. --
  697. -- Throws:
  698. --
  699. -- - `NoSuchThing` if source file does not exist
  700. -- - `PermissionDenied` if output directory cannot be written to
  701. -- - `PermissionDenied` if source directory cannot be opened
  702. -- - `UnsupportedOperation` if source and destination are on different
  703. -- devices
  704. -- - `AlreadyExists` if destination already exists
  705. -- - `SameFile` if destination and source are the same file
  706. -- (`HPathIOException`)
  707. --
  708. -- Note: calls `rename` (but does not allow to rename over existing files)
  709. renameFile :: Path b1 -> Path b2 -> IO ()
  710. renameFile fromf@(MkPath fromfBS) tof@(MkPath tofBS) = do
  711. throwSameFile fromf tof
  712. throwFileDoesExist tof
  713. throwDirDoesExist tof
  714. rename fromfBS tofBS
  715. -- |Move a file. This also works across devices by copy-delete fallback.
  716. -- And also works on directories.
  717. --
  718. -- Does not follow symbolic links, but renames the symbolic link file.
  719. --
  720. --
  721. -- Safety/reliability concerns:
  722. --
  723. -- * `Overwrite` mode is not atomic
  724. -- * copy-delete fallback is inherently non-atomic
  725. -- * since this function calls `easyCopy` and `easyDelete` as a fallback
  726. -- to `renameFile`, file types that are not `RegularFile`, `SymbolicLink`
  727. -- or `Directory` may be ignored
  728. -- * for `Overwrite` mode, the destination will be deleted (not recursively)
  729. -- before moving
  730. --
  731. -- Throws:
  732. --
  733. -- - `NoSuchThing` if source file does not exist
  734. -- - `PermissionDenied` if output directory cannot be written to
  735. -- - `PermissionDenied` if source directory cannot be opened
  736. -- - `SameFile` if destination and source are the same file
  737. -- (`HPathIOException`)
  738. --
  739. -- Throws in `Strict` mode only:
  740. --
  741. -- - `AlreadyExists` if destination already exists
  742. --
  743. -- Notes:
  744. --
  745. -- - calls `rename` (but does not allow to rename over existing files)
  746. -- - calls `getcwd` in Overwrite mode if destination is a relative path
  747. moveFile :: Path b1 -- ^ file to move
  748. -> Path b2 -- ^ destination
  749. -> CopyMode
  750. -> IO ()
  751. moveFile from to cm = do
  752. throwSameFile from to
  753. case cm of
  754. Strict -> catchErrno [eXDEV] (renameFile from to) $ do
  755. easyCopy from to Strict FailEarly
  756. easyDelete from
  757. Overwrite -> do
  758. ft <- getFileType from
  759. writable <- toAbs to >>= isWritable
  760. case ft of
  761. RegularFile -> do
  762. exists <- doesFileExist to
  763. when (exists && writable) (deleteFile to)
  764. SymbolicLink -> do
  765. exists <- doesFileExist to
  766. when (exists && writable) (deleteFile to)
  767. Directory -> do
  768. exists <- doesDirectoryExist to
  769. when (exists && writable) (deleteDir to)
  770. _ -> return ()
  771. moveFile from to Strict
  772. --------------------
  773. --[ File Reading ]--
  774. --------------------
  775. -- |Read the given file at once into memory as a strict ByteString.
  776. -- Symbolic links are followed, no sanity checks on file size
  777. -- or file type. File must exist.
  778. --
  779. -- Note: the size of the file is determined in advance, as to only
  780. -- have one allocation.
  781. --
  782. -- Safety/reliability concerns:
  783. --
  784. -- * since amount of bytes to read is determined in advance,
  785. -- the file might be read partially only if something else is
  786. -- appending to it while reading
  787. -- * the whole file is read into memory!
  788. --
  789. -- Throws:
  790. --
  791. -- - `InappropriateType` if file is not a regular file or a symlink
  792. -- - `PermissionDenied` if we cannot read the file or the directory
  793. -- containting it
  794. -- - `NoSuchThing` if the file does not exist
  795. readFile :: Path b -> IO ByteString
  796. readFile (MkPath fp) =
  797. bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd -> do
  798. stat <- PF.getFdStatus fd
  799. let fsize = PF.fileSize stat
  800. SPB.fdRead fd (fromIntegral fsize)
  801. -- |Read the given file in chunks of size `8192` into memory until
  802. -- `fread` returns 0. Returns a lazy ByteString, because it uses
  803. -- Builders under the hood.
  804. --
  805. -- Safety/reliability concerns:
  806. --
  807. -- * the whole file is read into memory!
  808. --
  809. -- Throws:
  810. --
  811. -- - `InappropriateType` if file is not a regular file or a symlink
  812. -- - `PermissionDenied` if we cannot read the file or the directory
  813. -- containting it
  814. -- - `NoSuchThing` if the file does not exist
  815. readFileEOF :: Path b -> IO L.ByteString
  816. readFileEOF (MkPath fp) =
  817. bracket (openFd fp SPI.ReadOnly [] Nothing) (SPI.closeFd) $ \fd ->
  818. allocaBytes (fromIntegral bufSize) $ \buf -> read' fd buf mempty
  819. where
  820. bufSize :: CSize
  821. bufSize = 8192
  822. read' :: Fd -> Ptr Word8 -> Builder -> IO L.ByteString
  823. read' fd buf builder = do
  824. size <- SPB.fdReadBuf fd buf bufSize
  825. if size == 0
  826. then return $ toLazyByteString builder
  827. else do
  828. readBS <- unsafePackCStringFinalizer buf
  829. (fromIntegral size)
  830. (return ())
  831. read' fd buf (builder <> byteString readBS)
  832. --------------------
  833. --[ File Writing ]--
  834. --------------------
  835. -- |Write a given ByteString to a file, truncating the file beforehand.
  836. -- The file must exist. Follows symlinks.
  837. --
  838. -- Throws:
  839. --
  840. -- - `InappropriateType` if file is not a regular file or a symlink
  841. -- - `PermissionDenied` if we cannot read the file or the directory
  842. -- containting it
  843. -- - `NoSuchThing` if the file does not exist
  844. writeFile :: Path b -> ByteString -> IO ()
  845. writeFile (MkPath fp) bs =
  846. bracket (openFd fp SPI.WriteOnly [SPDF.oTrunc] Nothing) (SPI.closeFd) $ \fd ->
  847. void $ SPB.fdWrite fd bs
  848. -- |Append a given ByteString to a file.
  849. -- The file must exist. Follows symlinks.
  850. --
  851. -- Throws:
  852. --
  853. -- - `InappropriateType` if file is not a regular file or a symlink
  854. -- - `PermissionDenied` if we cannot read the file or the directory
  855. -- containting it
  856. -- - `NoSuchThing` if the file does not exist
  857. appendFile :: Path b -> ByteString -> IO ()
  858. appendFile (MkPath fp) bs =
  859. bracket (openFd fp SPI.WriteOnly [SPDF.oAppend] Nothing)
  860. (SPI.closeFd) $ \fd -> void $ SPB.fdWrite fd bs
  861. -----------------------
  862. --[ File Permissions]--
  863. -----------------------
  864. -- |Default permissions for a new file.
  865. newFilePerms :: FileMode
  866. newFilePerms
  867. = ownerWriteMode
  868. `unionFileModes` ownerReadMode
  869. `unionFileModes` groupWriteMode
  870. `unionFileModes` groupReadMode
  871. `unionFileModes` otherWriteMode
  872. `unionFileModes` otherReadMode
  873. -- |Default permissions for a new directory.
  874. newDirPerms :: FileMode
  875. newDirPerms
  876. = ownerModes
  877. `unionFileModes` groupExecuteMode
  878. `unionFileModes` groupReadMode
  879. `unionFileModes` otherExecuteMode
  880. `unionFileModes` otherReadMode
  881. -------------------------
  882. --[ Directory reading ]--
  883. -------------------------
  884. -- |Gets all filenames of the given directory. This excludes "." and "..".
  885. -- This version does not follow symbolic links.
  886. --
  887. -- The contents are not sorted and there is no guarantee on the ordering.
  888. --
  889. -- Throws:
  890. --
  891. -- - `NoSuchThing` if directory does not exist
  892. -- - `InappropriateType` if file type is wrong (file)
  893. -- - `InappropriateType` if file type is wrong (symlink to file)
  894. -- - `InappropriateType` if file type is wrong (symlink to dir)
  895. -- - `PermissionDenied` if directory cannot be opened
  896. getDirsFiles :: Path b -- ^ dir to read
  897. -> IO [Path b]
  898. getDirsFiles p@(MkPath fp) = do
  899. fd <- openFd fp SPI.ReadOnly [SPDF.oNofollow] Nothing
  900. return
  901. . catMaybes
  902. . fmap (\x -> (</>) p <$> (parseMaybe . snd $ x))
  903. =<< getDirectoryContents' fd
  904. where
  905. parseMaybe :: ByteString -> Maybe (Path Fn)
  906. parseMaybe = parseFn
  907. ---------------------------
  908. --[ FileType operations ]--
  909. ---------------------------
  910. -- |Get the file type of the file located at the given path. Does
  911. -- not follow symbolic links.
  912. --
  913. -- Throws:
  914. --
  915. -- - `NoSuchThing` if the file does not exist
  916. -- - `PermissionDenied` if any part of the path is not accessible
  917. getFileType :: Path b -> IO FileType
  918. getFileType (MkPath fp) = do
  919. fs <- PF.getSymbolicLinkStatus fp
  920. decide fs
  921. where
  922. decide fs
  923. | PF.isDirectory fs = return Directory
  924. | PF.isRegularFile fs = return RegularFile
  925. | PF.isSymbolicLink fs = return SymbolicLink
  926. | PF.isBlockDevice fs = return BlockDevice
  927. | PF.isCharacterDevice fs = return CharacterDevice
  928. | PF.isNamedPipe fs = return NamedPipe
  929. | PF.isSocket fs = return Socket
  930. | otherwise = ioError $ userError "No filetype?!"
  931. --------------
  932. --[ Others ]--
  933. --------------
  934. -- |Applies `realpath` on the given path.
  935. --
  936. -- Throws:
  937. --
  938. -- - `NoSuchThing` if the file at the given path does not exist
  939. -- - `NoSuchThing` if the symlink is broken
  940. canonicalizePath :: Path b -> IO (Path Abs)
  941. canonicalizePath (MkPath l) = do
  942. nl <- SPDT.realpath l
  943. return $ MkPath nl
  944. -- |Converts any path to an absolute path.
  945. -- This is done in the following way:
  946. --
  947. -- - if the path is already an absolute one, just return it
  948. -- - if it's a relative path, prepend the current directory to it
  949. toAbs :: Path b -> IO (Path Abs)
  950. toAbs (MkPath bs) = do
  951. let mabs = parseAbs bs :: Maybe (Path Abs)
  952. case mabs of
  953. Just a -> return a
  954. Nothing -> do
  955. cwd <- getWorkingDirectory >>= parseAbs
  956. rel <- parseRel bs -- we know it must be relative now
  957. return $ cwd </> rel