1 -----------------------------------------------------------------------------
3 -- Module : System.Directory
4 -- Copyright : (c) The University of Glasgow 2001
5 -- License : BSD-style (see the file libraries/base/LICENSE)
7 -- Maintainer : libraries@haskell.org
9 -- Portability : portable
11 -- System-independent interface to directory manipulation.
13 -----------------------------------------------------------------------------
15 module System.Directory
19 -- * Actions on directories
20 createDirectory -- :: FilePath -> IO ()
21 , removeDirectory -- :: FilePath -> IO ()
22 , renameDirectory -- :: FilePath -> FilePath -> IO ()
24 , getDirectoryContents -- :: FilePath -> IO [FilePath]
25 , getCurrentDirectory -- :: IO FilePath
26 , setCurrentDirectory -- :: FilePath -> IO ()
29 , removeFile -- :: FilePath -> IO ()
30 , renameFile -- :: FilePath -> FilePath -> IO ()
33 , doesFileExist -- :: FilePath -> IO Bool
34 , doesDirectoryExist -- :: FilePath -> IO Bool
42 readable, -- :: Permissions -> Bool
43 writable, -- :: Permissions -> Bool
44 executable, -- :: Permissions -> Bool
45 searchable -- :: Permissions -> Bool
48 , getPermissions -- :: FilePath -> IO Permissions
49 , setPermissions -- :: FilePath -> Permissions -> IO ()
53 , getModificationTime -- :: FilePath -> IO ClockTime
58 #elif defined(__HUGS__)
64 import Control.Exception ( bracket )
65 import Control.Monad ( when )
66 import System.Posix.Types
67 import System.Posix.Internals
68 import System.Time ( ClockTime(..) )
70 import System.IO.Error
74 #ifdef __GLASGOW_HASKELL__
75 import GHC.IOBase ( IOException(..), IOErrorType(..), ioException )
79 A directory contains a series of entries, each of which is a named
80 reference to a file system object (file, directory etc.). Some
81 entries may be hidden, inaccessible, or have some administrative
82 function (e.g. `.' or `..' under POSIX
83 <http://www.opengroup.org/onlinepubs/007904975/toc.htm>), but in
84 this standard all such entries are considered to form part of the
85 directory contents. Entries in sub-directories are not, however,
86 considered to form part of the directory contents.
88 Each file system object is referenced by a /path/. There is
89 normally at least one absolute path to each file system object. In
90 some operating systems, it may also be possible to have paths which
91 are relative to the current directory.
94 -----------------------------------------------------------------------------
99 The 'Permissions' type is used to record whether certain operations are
100 permissible on a file\/directory. 'getPermissions' and 'setPermissions'
101 get and set these permissions, respectively. Permissions apply both to
102 files and directories. For directories, the executable field will be
103 'False', and for files the searchable field will be 'False'. Note that
104 directories may be searchable without being readable, if permission has
105 been given to use them as part of a path, but not to examine the
108 Note that to change some, but not all permissions, a construct on the following lines must be used.
110 > makeReadable f = do
111 > p <- getPermissions f
112 > setPermissions f (p {readable = True})
119 executable, searchable :: Bool
120 } deriving (Eq, Ord, Read, Show)
122 {- |The 'getPermissions' operation returns the
123 permissions for the file or directory.
125 The operation may fail with:
127 * 'isPermissionError' if the user is not permitted to access
130 * 'isDoesNotExistError' if the file or directory does not exist.
134 getPermissions :: FilePath -> IO Permissions
135 getPermissions name = do
136 withCString name $ \s -> do
137 read <- c_access s r_OK
138 write <- c_access s w_OK
139 exec <- c_access s x_OK
140 withFileStatus "getPermissions" name $ \st -> do
141 is_dir <- isDirectory st
144 readable = read == 0,
145 writable = write == 0,
146 executable = not is_dir && exec == 0,
147 searchable = is_dir && exec == 0
151 {- |The 'setPermissions' operation sets the
152 permissions for the file or directory.
154 The operation may fail with:
156 * 'isPermissionError' if the user is not permitted to set
159 * 'isDoesNotExistError' if the file or directory does not exist.
163 setPermissions :: FilePath -> Permissions -> IO ()
164 setPermissions name (Permissions r w e s) = do
165 allocaBytes sizeof_stat $ \ p_stat -> do
166 withCString name $ \p_name -> do
167 throwErrnoIfMinus1_ "setPermissions" $ do
169 mode <- st_mode p_stat
170 let mode1 = modifyBit r mode s_IRUSR
171 let mode2 = modifyBit w mode1 s_IWUSR
172 let mode3 = modifyBit (e || s) mode2 s_IXUSR
176 modifyBit :: Bool -> CMode -> CMode -> CMode
177 modifyBit False m b = m .&. (complement b)
178 modifyBit True m b = m .|. b
180 -----------------------------------------------------------------------------
183 {- |@'createDirectory' dir@ creates a new directory @dir@ which is
184 initially empty, or as near to empty as the operating system
187 The operation may fail with:
189 * 'isPermissionError' \/ 'PermissionDenied'
190 The process has insufficient privileges to perform the operation.
193 * 'isAlreadyExistsError' \/ 'AlreadyExists'
194 The operand refers to a directory that already exists.
198 A physical I\/O error has occurred.
202 The operand is not a valid directory name.
203 @[ENAMETOOLONG, ELOOP]@
206 There is no path to the directory.
209 * 'ResourceExhausted'
210 Insufficient resources (virtual memory, process file descriptors,
211 physical disk space, etc.) are available to perform the operation.
212 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
214 * 'InappropriateType'
215 The path refers to an existing non-directory object.
220 createDirectory :: FilePath -> IO ()
221 createDirectory path = do
222 withCString path $ \s -> do
223 throwErrnoIfMinus1Retry_ "createDirectory" $
226 {- | @'removeDirectory' dir@ removes an existing directory /dir/. The
227 implementation may specify additional constraints which must be
228 satisfied before a directory can be removed (e.g. the directory has to
229 be empty, or may not be in use by other processes). It is not legal
230 for an implementation to partially remove a directory unless the
231 entire directory is removed. A conformant implementation need not
232 support directory removal in all situations (e.g. removal of the root
235 The operation may fail with:
238 A physical I\/O error has occurred.
242 The operand is not a valid directory name.
243 [ENAMETOOLONG, ELOOP]
245 * 'isDoesNotExistError' \/ 'NoSuchThing'
246 The directory does not exist.
249 * 'isPermissionError' \/ 'PermissionDenied'
250 The process has insufficient privileges to perform the operation.
251 @[EROFS, EACCES, EPERM]@
253 * 'UnsatisfiedConstraints'
254 Implementation-dependent constraints are not satisfied.
255 @[EBUSY, ENOTEMPTY, EEXIST]@
257 * 'UnsupportedOperation'
258 The implementation does not support removal in this situation.
261 * 'InappropriateType'
262 The operand refers to an existing non-directory object.
267 removeDirectory :: FilePath -> IO ()
268 removeDirectory path = do
269 modifyIOError (`ioeSetFileName` path) $
270 withCString path $ \s ->
271 throwErrnoIfMinus1Retry_ "removeDirectory" (c_rmdir s)
273 {- |'removeFile' /file/ removes the directory entry for an existing file
274 /file/, where /file/ is not itself a directory. The
275 implementation may specify additional constraints which must be
276 satisfied before a file can be removed (e.g. the file may not be in
277 use by other processes).
279 The operation may fail with:
282 A physical I\/O error has occurred.
286 The operand is not a valid file name.
287 @[ENAMETOOLONG, ELOOP]@
289 * 'isDoesNotExistError' \/ 'NoSuchThing'
290 The file does not exist.
293 * 'isPermissionError' \/ 'PermissionDenied'
294 The process has insufficient privileges to perform the operation.
295 @[EROFS, EACCES, EPERM]@
297 * 'UnsatisfiedConstraints'
298 Implementation-dependent constraints are not satisfied.
301 * 'InappropriateType'
302 The operand refers to an existing directory.
307 removeFile :: FilePath -> IO ()
309 modifyIOError (`ioeSetFileName` path) $
310 withCString path $ \s ->
311 throwErrnoIfMinus1Retry_ "removeFile" (c_unlink s)
313 {- |@'renameDirectory' old new@ changes the name of an existing
314 directory from /old/ to /new/. If the /new/ directory
315 already exists, it is atomically replaced by the /old/ directory.
316 If the /new/ directory is neither the /old/ directory nor an
317 alias of the /old/ directory, it is removed as if by
318 'removeDirectory'. A conformant implementation need not support
319 renaming directories in all situations (e.g. renaming to an existing
320 directory, or across different physical devices), but the constraints
323 On Win32 platforms, @renameDirectory@ fails if the /new/ directory already
326 The operation may fail with:
329 A physical I\/O error has occurred.
333 Either operand is not a valid directory name.
334 @[ENAMETOOLONG, ELOOP]@
336 * 'isDoesNotExistError' \/ 'NoSuchThing'
337 The original directory does not exist, or there is no path to the target.
340 * 'isPermissionError' \/ 'PermissionDenied'
341 The process has insufficient privileges to perform the operation.
342 @[EROFS, EACCES, EPERM]@
344 * 'ResourceExhausted'
345 Insufficient resources are available to perform the operation.
346 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
348 * 'UnsatisfiedConstraints'
349 Implementation-dependent constraints are not satisfied.
350 @[EBUSY, ENOTEMPTY, EEXIST]@
352 * 'UnsupportedOperation'
353 The implementation does not support renaming in this situation.
356 * 'InappropriateType'
357 Either path refers to an existing non-directory object.
362 renameDirectory :: FilePath -> FilePath -> IO ()
363 renameDirectory opath npath =
364 withFileStatus "renameDirectory" opath $ \st -> do
365 is_dir <- isDirectory st
367 then ioException (IOError Nothing InappropriateType "renameDirectory"
368 ("not a directory") (Just opath))
371 withCString opath $ \s1 ->
372 withCString npath $ \s2 ->
373 throwErrnoIfMinus1Retry_ "renameDirectory" (c_rename s1 s2)
375 {- |@'renameFile' old new@ changes the name of an existing file system
376 object from /old/ to /new/. If the /new/ object already
377 exists, it is atomically replaced by the /old/ object. Neither
378 path may refer to an existing directory. A conformant implementation
379 need not support renaming files in all situations (e.g. renaming
380 across different physical devices), but the constraints must be
383 The operation may fail with:
386 A physical I\/O error has occurred.
390 Either operand is not a valid file name.
391 @[ENAMETOOLONG, ELOOP]@
393 * 'isDoesNotExistError' \/ 'NoSuchThing'
394 The original file does not exist, or there is no path to the target.
397 * 'isPermissionError' \/ 'PermissionDenied'
398 The process has insufficient privileges to perform the operation.
399 @[EROFS, EACCES, EPERM]@
401 * 'ResourceExhausted'
402 Insufficient resources are available to perform the operation.
403 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
405 * 'UnsatisfiedConstraints'
406 Implementation-dependent constraints are not satisfied.
409 * 'UnsupportedOperation'
410 The implementation does not support renaming in this situation.
413 * 'InappropriateType'
414 Either path refers to an existing directory.
415 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
419 renameFile :: FilePath -> FilePath -> IO ()
420 renameFile opath npath =
421 withFileOrSymlinkStatus "renameFile" opath $ \st -> do
422 is_dir <- isDirectory st
424 then ioException (IOError Nothing InappropriateType "renameFile"
425 "is a directory" (Just opath))
428 withCString opath $ \s1 ->
429 withCString npath $ \s2 ->
430 throwErrnoIfMinus1Retry_ "renameFile" (c_rename s1 s2)
432 {- |@'copyFile' old new@ copies the existing file from /old/ to /new/.
433 If the /new/ file already exists, it is atomically replaced by the /old/ file.
434 Neither path may refer to an existing directory.
436 copyFile :: FilePath -> FilePath -> IO ()
437 copyFile fromFPath toFPath = handle (changeFunName) $
438 (bracket (openBinaryFile fromFPath ReadMode) hClose $ \hFrom ->
439 bracket (openBinaryFile toFPath WriteMode) hClose $ \hTo ->
440 allocaBytes bufferSize $ \buffer ->
441 copyContents hFrom hTo buffer) `catch` (ioError . changeFunName)
445 changeFunName (IOError h iot fun str mb_fp) = IOError h iot "copyFile" str mb_fp
448 copyContents hFrom hTo buffer = do
449 count <- hGetBuf hFrom buffer bufferSize
450 when (count > 0) $ do
451 hPutBuf hTo buffer count
452 copyContents hFrom hTo buffer
455 {- |@'getDirectoryContents' dir@ returns a list of /all/ entries
458 The operation may fail with:
461 A physical I\/O error has occurred.
465 The operand is not a valid directory name.
466 @[ENAMETOOLONG, ELOOP]@
468 * 'isDoesNotExistError' \/ 'NoSuchThing'
469 The directory does not exist.
472 * 'isPermissionError' \/ 'PermissionDenied'
473 The process has insufficient privileges to perform the operation.
476 * 'ResourceExhausted'
477 Insufficient resources are available to perform the operation.
480 * 'InappropriateType'
481 The path refers to an existing non-directory object.
486 getDirectoryContents :: FilePath -> IO [FilePath]
487 getDirectoryContents path = do
488 modifyIOError (`ioeSetFileName` path) $
489 alloca $ \ ptr_dEnt ->
491 (withCString path $ \s ->
492 throwErrnoIfNullRetry desc (c_opendir s))
493 (\p -> throwErrnoIfMinus1_ desc (c_closedir p))
494 (\p -> loop ptr_dEnt p)
496 desc = "getDirectoryContents"
498 loop :: Ptr (Ptr CDirent) -> Ptr CDir -> IO [String]
499 loop ptr_dEnt dir = do
501 r <- readdir dir ptr_dEnt
504 dEnt <- peek ptr_dEnt
508 entry <- (d_name dEnt >>= peekCString)
510 entries <- loop ptr_dEnt dir
511 return (entry:entries)
512 else do errno <- getErrno
513 if (errno == eINTR) then loop ptr_dEnt dir else do
514 let (Errno eo) = errno
515 if (eo == end_of_dir)
521 {- |If the operating system has a notion of current directories,
522 'getCurrentDirectory' returns an absolute path to the
523 current directory of the calling process.
525 The operation may fail with:
528 A physical I\/O error has occurred.
531 * 'isDoesNotExistError' \/ 'NoSuchThing'
532 There is no path referring to the current directory.
533 @[EPERM, ENOENT, ESTALE...]@
535 * 'isPermissionError' \/ 'PermissionDenied'
536 The process has insufficient privileges to perform the operation.
539 * 'ResourceExhausted'
540 Insufficient resources are available to perform the operation.
542 * 'UnsupportedOperation'
543 The operating system has no notion of current directory.
547 getCurrentDirectory :: IO FilePath
548 getCurrentDirectory = do
549 p <- mallocBytes long_path_size
551 where go p bytes = do
552 p' <- c_getcwd p (fromIntegral bytes)
554 then do s <- peekCString p'
557 else do errno <- getErrno
559 then do let bytes' = bytes * 2
560 p' <- reallocBytes p bytes'
562 else throwErrno "getCurrentDirectory"
564 {- |If the operating system has a notion of current directories,
565 @'setCurrentDirectory' dir@ changes the current
566 directory of the calling process to /dir/.
568 The operation may fail with:
571 A physical I\/O error has occurred.
575 The operand is not a valid directory name.
576 @[ENAMETOOLONG, ELOOP]@
578 * 'isDoesNotExistError' \/ 'NoSuchThing'
579 The directory does not exist.
582 * 'isPermissionError' \/ 'PermissionDenied'
583 The process has insufficient privileges to perform the operation.
586 * 'UnsupportedOperation'
587 The operating system has no notion of current directory, or the
588 current directory cannot be dynamically changed.
590 * 'InappropriateType'
591 The path refers to an existing non-directory object.
596 setCurrentDirectory :: FilePath -> IO ()
597 setCurrentDirectory path = do
598 modifyIOError (`ioeSetFileName` path) $
599 withCString path $ \s ->
600 throwErrnoIfMinus1Retry_ "setCurrentDirectory" (c_chdir s)
601 -- ToDo: add path to error
603 {- |The operation 'doesDirectoryExist' returns 'True' if the argument file
604 exists and is a directory, and 'False' otherwise.
607 doesDirectoryExist :: FilePath -> IO Bool
608 doesDirectoryExist name =
610 (withFileStatus "doesDirectoryExist" name $ \st -> isDirectory st)
611 (\ _ -> return False)
613 {- |The operation 'doesFileExist' returns 'True'
614 if the argument file exists and is not a directory, and 'False' otherwise.
617 doesFileExist :: FilePath -> IO Bool
618 doesFileExist name = do
620 (withFileStatus "doesFileExist" name $ \st -> do b <- isDirectory st; return (not b))
621 (\ _ -> return False)
623 {- |The 'getModificationTime' operation returns the
624 clock time at which the file or directory was last modified.
626 The operation may fail with:
628 * 'isPermissionError' if the user is not permitted to access
629 the modification time; or
631 * 'isDoesNotExistError' if the file or directory does not exist.
635 getModificationTime :: FilePath -> IO ClockTime
636 getModificationTime name =
637 withFileStatus "getModificationTime" name $ \ st ->
640 withFileStatus :: String -> FilePath -> (Ptr CStat -> IO a) -> IO a
641 withFileStatus loc name f = do
642 modifyIOError (`ioeSetFileName` name) $
643 allocaBytes sizeof_stat $ \p ->
644 withCString (fileNameEndClean name) $ \s -> do
645 throwErrnoIfMinus1Retry_ loc (c_stat s p)
648 withFileOrSymlinkStatus :: String -> FilePath -> (Ptr CStat -> IO a) -> IO a
649 withFileOrSymlinkStatus loc name f = do
650 modifyIOError (`ioeSetFileName` name) $
651 allocaBytes sizeof_stat $ \p ->
652 withCString name $ \s -> do
653 throwErrnoIfMinus1Retry_ loc (lstat s p)
656 modificationTime :: Ptr CStat -> IO ClockTime
657 modificationTime stat = do
658 mtime <- st_mtime stat
659 let realToInteger = round . realToFrac :: Real a => a -> Integer
660 return (TOD (realToInteger (mtime :: CTime)) 0)
662 isDirectory :: Ptr CStat -> IO Bool
663 isDirectory stat = do
665 return (s_isdir mode)
667 fileNameEndClean :: String -> String
668 fileNameEndClean name =
669 if i > 0 && (ec == '\\' || ec == '/') then
670 fileNameEndClean (take i name)
674 i = (length name) - 1
677 foreign import ccall unsafe "__hscore_long_path_size"
678 long_path_size :: Int
680 foreign import ccall unsafe "__hscore_R_OK" r_OK :: CMode
681 foreign import ccall unsafe "__hscore_W_OK" w_OK :: CMode
682 foreign import ccall unsafe "__hscore_X_OK" x_OK :: CMode
684 foreign import ccall unsafe "__hscore_S_IRUSR" s_IRUSR :: CMode
685 foreign import ccall unsafe "__hscore_S_IWUSR" s_IWUSR :: CMode
686 foreign import ccall unsafe "__hscore_S_IXUSR" s_IXUSR :: CMode