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 ()
31 , copyFile -- :: FilePath -> FilePath -> IO ()
34 , doesFileExist -- :: FilePath -> IO Bool
35 , doesDirectoryExist -- :: FilePath -> IO Bool
43 readable, -- :: Permissions -> Bool
44 writable, -- :: Permissions -> Bool
45 executable, -- :: Permissions -> Bool
46 searchable -- :: Permissions -> Bool
49 , getPermissions -- :: FilePath -> IO Permissions
50 , setPermissions -- :: FilePath -> Permissions -> IO ()
54 , getModificationTime -- :: FilePath -> IO ClockTime
59 #elif defined(__HUGS__)
65 import Control.Exception ( bracket )
66 import Control.Monad ( when )
67 import System.Posix.Types
68 import System.Posix.Internals
69 import System.Time ( ClockTime(..) )
71 import System.IO.Error
75 #ifdef __GLASGOW_HASKELL__
76 import GHC.IOBase ( IOException(..), IOErrorType(..), ioException )
80 A directory contains a series of entries, each of which is a named
81 reference to a file system object (file, directory etc.). Some
82 entries may be hidden, inaccessible, or have some administrative
83 function (e.g. `.' or `..' under POSIX
84 <http://www.opengroup.org/onlinepubs/007904975/toc.htm>), but in
85 this standard all such entries are considered to form part of the
86 directory contents. Entries in sub-directories are not, however,
87 considered to form part of the directory contents.
89 Each file system object is referenced by a /path/. There is
90 normally at least one absolute path to each file system object. In
91 some operating systems, it may also be possible to have paths which
92 are relative to the current directory.
95 -----------------------------------------------------------------------------
100 The 'Permissions' type is used to record whether certain operations are
101 permissible on a file\/directory. 'getPermissions' and 'setPermissions'
102 get and set these permissions, respectively. Permissions apply both to
103 files and directories. For directories, the executable field will be
104 'False', and for files the searchable field will be 'False'. Note that
105 directories may be searchable without being readable, if permission has
106 been given to use them as part of a path, but not to examine the
109 Note that to change some, but not all permissions, a construct on the following lines must be used.
111 > makeReadable f = do
112 > p <- getPermissions f
113 > setPermissions f (p {readable = True})
120 executable, searchable :: Bool
121 } deriving (Eq, Ord, Read, Show)
123 {- |The 'getPermissions' operation returns the
124 permissions for the file or directory.
126 The operation may fail with:
128 * 'isPermissionError' if the user is not permitted to access
131 * 'isDoesNotExistError' if the file or directory does not exist.
135 getPermissions :: FilePath -> IO Permissions
136 getPermissions name = do
137 withCString name $ \s -> do
138 read <- c_access s r_OK
139 write <- c_access s w_OK
140 exec <- c_access s x_OK
141 withFileStatus "getPermissions" name $ \st -> do
142 is_dir <- isDirectory st
145 readable = read == 0,
146 writable = write == 0,
147 executable = not is_dir && exec == 0,
148 searchable = is_dir && exec == 0
152 {- |The 'setPermissions' operation sets the
153 permissions for the file or directory.
155 The operation may fail with:
157 * 'isPermissionError' if the user is not permitted to set
160 * 'isDoesNotExistError' if the file or directory does not exist.
164 setPermissions :: FilePath -> Permissions -> IO ()
165 setPermissions name (Permissions r w e s) = do
166 allocaBytes sizeof_stat $ \ p_stat -> do
167 withCString name $ \p_name -> do
168 throwErrnoIfMinus1_ "setPermissions" $ do
170 mode <- st_mode p_stat
171 let mode1 = modifyBit r mode s_IRUSR
172 let mode2 = modifyBit w mode1 s_IWUSR
173 let mode3 = modifyBit (e || s) mode2 s_IXUSR
177 modifyBit :: Bool -> CMode -> CMode -> CMode
178 modifyBit False m b = m .&. (complement b)
179 modifyBit True m b = m .|. b
181 -----------------------------------------------------------------------------
184 {- |@'createDirectory' dir@ creates a new directory @dir@ which is
185 initially empty, or as near to empty as the operating system
188 The operation may fail with:
190 * 'isPermissionError' \/ 'PermissionDenied'
191 The process has insufficient privileges to perform the operation.
194 * 'isAlreadyExistsError' \/ 'AlreadyExists'
195 The operand refers to a directory that already exists.
199 A physical I\/O error has occurred.
203 The operand is not a valid directory name.
204 @[ENAMETOOLONG, ELOOP]@
207 There is no path to the directory.
210 * 'ResourceExhausted'
211 Insufficient resources (virtual memory, process file descriptors,
212 physical disk space, etc.) are available to perform the operation.
213 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
215 * 'InappropriateType'
216 The path refers to an existing non-directory object.
221 createDirectory :: FilePath -> IO ()
222 createDirectory path = do
223 withCString path $ \s -> do
224 throwErrnoIfMinus1Retry_ "createDirectory" $
227 {- | @'removeDirectory' dir@ removes an existing directory /dir/. The
228 implementation may specify additional constraints which must be
229 satisfied before a directory can be removed (e.g. the directory has to
230 be empty, or may not be in use by other processes). It is not legal
231 for an implementation to partially remove a directory unless the
232 entire directory is removed. A conformant implementation need not
233 support directory removal in all situations (e.g. removal of the root
236 The operation may fail with:
239 A physical I\/O error has occurred.
243 The operand is not a valid directory name.
244 [ENAMETOOLONG, ELOOP]
246 * 'isDoesNotExistError' \/ 'NoSuchThing'
247 The directory does not exist.
250 * 'isPermissionError' \/ 'PermissionDenied'
251 The process has insufficient privileges to perform the operation.
252 @[EROFS, EACCES, EPERM]@
254 * 'UnsatisfiedConstraints'
255 Implementation-dependent constraints are not satisfied.
256 @[EBUSY, ENOTEMPTY, EEXIST]@
258 * 'UnsupportedOperation'
259 The implementation does not support removal in this situation.
262 * 'InappropriateType'
263 The operand refers to an existing non-directory object.
268 removeDirectory :: FilePath -> IO ()
269 removeDirectory path = do
270 modifyIOError (`ioeSetFileName` path) $
271 withCString path $ \s ->
272 throwErrnoIfMinus1Retry_ "removeDirectory" (c_rmdir s)
274 {- |'removeFile' /file/ removes the directory entry for an existing file
275 /file/, where /file/ is not itself a directory. The
276 implementation may specify additional constraints which must be
277 satisfied before a file can be removed (e.g. the file may not be in
278 use by other processes).
280 The operation may fail with:
283 A physical I\/O error has occurred.
287 The operand is not a valid file name.
288 @[ENAMETOOLONG, ELOOP]@
290 * 'isDoesNotExistError' \/ 'NoSuchThing'
291 The file does not exist.
294 * 'isPermissionError' \/ 'PermissionDenied'
295 The process has insufficient privileges to perform the operation.
296 @[EROFS, EACCES, EPERM]@
298 * 'UnsatisfiedConstraints'
299 Implementation-dependent constraints are not satisfied.
302 * 'InappropriateType'
303 The operand refers to an existing directory.
308 removeFile :: FilePath -> IO ()
310 modifyIOError (`ioeSetFileName` path) $
311 withCString path $ \s ->
312 throwErrnoIfMinus1Retry_ "removeFile" (c_unlink s)
314 {- |@'renameDirectory' old new@ changes the name of an existing
315 directory from /old/ to /new/. If the /new/ directory
316 already exists, it is atomically replaced by the /old/ directory.
317 If the /new/ directory is neither the /old/ directory nor an
318 alias of the /old/ directory, it is removed as if by
319 'removeDirectory'. A conformant implementation need not support
320 renaming directories in all situations (e.g. renaming to an existing
321 directory, or across different physical devices), but the constraints
324 On Win32 platforms, @renameDirectory@ fails if the /new/ directory already
327 The operation may fail with:
330 A physical I\/O error has occurred.
334 Either operand is not a valid directory name.
335 @[ENAMETOOLONG, ELOOP]@
337 * 'isDoesNotExistError' \/ 'NoSuchThing'
338 The original directory does not exist, or there is no path to the target.
341 * 'isPermissionError' \/ 'PermissionDenied'
342 The process has insufficient privileges to perform the operation.
343 @[EROFS, EACCES, EPERM]@
345 * 'ResourceExhausted'
346 Insufficient resources are available to perform the operation.
347 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
349 * 'UnsatisfiedConstraints'
350 Implementation-dependent constraints are not satisfied.
351 @[EBUSY, ENOTEMPTY, EEXIST]@
353 * 'UnsupportedOperation'
354 The implementation does not support renaming in this situation.
357 * 'InappropriateType'
358 Either path refers to an existing non-directory object.
363 renameDirectory :: FilePath -> FilePath -> IO ()
364 renameDirectory opath npath =
365 withFileStatus "renameDirectory" opath $ \st -> do
366 is_dir <- isDirectory st
368 then ioException (IOError Nothing InappropriateType "renameDirectory"
369 ("not a directory") (Just opath))
372 withCString opath $ \s1 ->
373 withCString npath $ \s2 ->
374 throwErrnoIfMinus1Retry_ "renameDirectory" (c_rename s1 s2)
376 {- |@'renameFile' old new@ changes the name of an existing file system
377 object from /old/ to /new/. If the /new/ object already
378 exists, it is atomically replaced by the /old/ object. Neither
379 path may refer to an existing directory. A conformant implementation
380 need not support renaming files in all situations (e.g. renaming
381 across different physical devices), but the constraints must be
384 The operation may fail with:
387 A physical I\/O error has occurred.
391 Either operand is not a valid file name.
392 @[ENAMETOOLONG, ELOOP]@
394 * 'isDoesNotExistError' \/ 'NoSuchThing'
395 The original file does not exist, or there is no path to the target.
398 * 'isPermissionError' \/ 'PermissionDenied'
399 The process has insufficient privileges to perform the operation.
400 @[EROFS, EACCES, EPERM]@
402 * 'ResourceExhausted'
403 Insufficient resources are available to perform the operation.
404 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
406 * 'UnsatisfiedConstraints'
407 Implementation-dependent constraints are not satisfied.
410 * 'UnsupportedOperation'
411 The implementation does not support renaming in this situation.
414 * 'InappropriateType'
415 Either path refers to an existing directory.
416 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
420 renameFile :: FilePath -> FilePath -> IO ()
421 renameFile opath npath =
422 withFileOrSymlinkStatus "renameFile" opath $ \st -> do
423 is_dir <- isDirectory st
425 then ioException (IOError Nothing InappropriateType "renameFile"
426 "is a directory" (Just opath))
429 withCString opath $ \s1 ->
430 withCString npath $ \s2 ->
431 throwErrnoIfMinus1Retry_ "renameFile" (c_rename s1 s2)
433 {- |@'copyFile' old new@ copies the existing file from /old/ to /new/.
434 If the /new/ file already exists, it is atomically replaced by the /old/ file.
435 Neither path may refer to an existing directory.
437 copyFile :: FilePath -> FilePath -> IO ()
438 copyFile fromFPath toFPath =
439 (bracket (openBinaryFile fromFPath ReadMode) hClose $ \hFrom ->
440 bracket (openBinaryFile toFPath WriteMode) hClose $ \hTo ->
441 allocaBytes bufferSize $ \buffer ->
442 copyContents hFrom hTo buffer) `catch` (ioError . changeFunName)
446 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