1 -----------------------------------------------------------------------------
3 -- Module : System.Directory
4 -- Copyright : (c) The University of Glasgow 2001
5 -- License : BSD-style (see the file libraries/core/LICENSE)
7 -- Maintainer : libraries@haskell.org
8 -- Stability : provisional
9 -- Portability : portable
11 -- $Id: Directory.hs,v 1.2 2002/04/24 15:47:10 sof Exp $
13 -- System-independent interface to directory manipulation.
15 -----------------------------------------------------------------------------
18 A directory contains a series of entries, each of which is a named
19 reference to a file system object (file, directory etc.). Some
20 entries may be hidden, inaccessible, or have some administrative
21 function (e.g. "." or ".." under POSIX), but in this standard all such
22 entries are considered to form part of the directory contents.
23 Entries in sub-directories are not, however, considered to form part
24 of the directory contents.
26 Each file system object is referenced by a {\em path}. There is
27 normally at least one absolute path to each file system object. In
28 some operating systems, it may also be possible to have paths which
29 are relative to the current directory.
32 module System.Directory
36 readable, -- :: Permissions -> Bool
37 writable, -- :: Permissions -> Bool
38 executable, -- :: Permissions -> Bool
39 searchable -- :: Permissions -> Bool
42 , createDirectory -- :: FilePath -> IO ()
43 , removeDirectory -- :: FilePath -> IO ()
44 , renameDirectory -- :: FilePath -> FilePath -> IO ()
46 , getDirectoryContents -- :: FilePath -> IO [FilePath]
47 , getCurrentDirectory -- :: IO FilePath
48 , setCurrentDirectory -- :: FilePath -> IO ()
50 , removeFile -- :: FilePath -> IO ()
51 , renameFile -- :: FilePath -> FilePath -> IO ()
53 , doesFileExist -- :: FilePath -> IO Bool
54 , doesDirectoryExist -- :: FilePath -> IO Bool
56 , getPermissions -- :: FilePath -> IO Permissions
57 , setPermissions -- :: FilePath -> Permissions -> IO ()
59 , getModificationTime -- :: FilePath -> IO ClockTime
64 import System.Time ( ClockTime(..) )
69 #ifdef __GLASGOW_HASKELL__
71 import GHC.IOBase ( IOException(..), IOErrorType(..), ioException )
74 -----------------------------------------------------------------------------
77 -- The Permissions type is used to record whether certain
78 -- operations are permissible on a file/directory:
79 -- [to whom? - presumably the "current user"]
84 executable, searchable :: Bool
85 } deriving (Eq, Ord, Read, Show)
87 -----------------------------------------------------------------------------
90 -- `createDirectory dir' creates a new directory dir which is
91 -- initially empty, or as near to empty as the operating system
94 -- The operation may fail with:
98 \item @isPermissionError@ / @PermissionDenied@
99 The process has insufficient privileges to perform the operation.
101 \item @isAlreadyExistsError@ / @AlreadyExists@
102 The operand refers to a directory that already exists.
104 \item @HardwareFault@
105 A physical I/O error has occurred.
107 \item @InvalidArgument@
108 The operand is not a valid directory name.
109 @[ENAMETOOLONG, ELOOP]@
111 There is no path to the directory.
113 \item @ResourceExhausted@
114 Insufficient resources (virtual memory, process file descriptors,
115 physical disk space, etc.) are available to perform the operation.
116 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
117 \item @InappropriateType@
118 The path refers to an existing non-directory object.
123 createDirectory :: FilePath -> IO ()
124 createDirectory path = do
125 withCString path $ \s -> do
126 throwErrnoIfMinus1Retry_ "createDirectory" $
130 @removeDirectory dir@ removes an existing directory {\em dir}. The
131 implementation may specify additional constraints which must be
132 satisfied before a directory can be removed (e.g. the directory has to
133 be empty, or may not be in use by other processes). It is not legal
134 for an implementation to partially remove a directory unless the
135 entire directory is removed. A conformant implementation need not
136 support directory removal in all situations (e.g. removal of the root
139 The operation may fail with:
141 \item @HardwareFault@
142 A physical I/O error has occurred.
144 \item @InvalidArgument@
145 The operand is not a valid directory name.
146 @[ENAMETOOLONG, ELOOP]@
147 \item @isDoesNotExist@ / @NoSuchThing@
148 The directory does not exist.
150 \item @isPermissionError@ / @PermissionDenied@
151 The process has insufficient privileges to perform the operation.
152 @[EROFS, EACCES, EPERM]@
153 \item @UnsatisfiedConstraints@
154 Implementation-dependent constraints are not satisfied.
155 @[EBUSY, ENOTEMPTY, EEXIST]@
156 \item @UnsupportedOperation@
157 The implementation does not support removal in this situation.
159 \item @InappropriateType@
160 The operand refers to an existing non-directory object.
165 removeDirectory :: FilePath -> IO ()
166 removeDirectory path = do
167 withCString path $ \s ->
168 throwErrnoIfMinus1Retry_ "removeDirectory" (c_rmdir s)
171 @Removefile file@ removes the directory entry for an existing file
172 {\em file}, where {\em file} is not itself a directory. The
173 implementation may specify additional constraints which must be
174 satisfied before a file can be removed (e.g. the file may not be in
175 use by other processes).
177 The operation may fail with:
179 \item @HardwareFault@
180 A physical I/O error has occurred.
182 \item @InvalidArgument@
183 The operand is not a valid file name.
184 @[ENAMETOOLONG, ELOOP]@
185 \item @isDoesNotExist@ / @NoSuchThing@
186 The file does not exist.
188 \item @isPermissionError@ / @PermissionDenied@
189 The process has insufficient privileges to perform the operation.
190 @[EROFS, EACCES, EPERM]@
191 \item @UnsatisfiedConstraints@
192 Implementation-dependent constraints are not satisfied.
194 \item @InappropriateType@
195 The operand refers to an existing directory.
200 removeFile :: FilePath -> IO ()
202 withCString path $ \s ->
203 throwErrnoIfMinus1Retry_ "removeFile" (c_unlink s)
206 @renameDirectory@ {\em old} {\em new} changes the name of an existing
207 directory from {\em old} to {\em new}. If the {\em new} directory
208 already exists, it is atomically replaced by the {\em old} directory.
209 If the {\em new} directory is neither the {\em old} directory nor an
210 alias of the {\em old} directory, it is removed as if by
211 $removeDirectory$. A conformant implementation need not support
212 renaming directories in all situations (e.g. renaming to an existing
213 directory, or across different physical devices), but the constraints
216 The operation may fail with:
218 \item @HardwareFault@
219 A physical I/O error has occurred.
221 \item @InvalidArgument@
222 Either operand is not a valid directory name.
223 @[ENAMETOOLONG, ELOOP]@
224 \item @isDoesNotExistError@ / @NoSuchThing@
225 The original directory does not exist, or there is no path to the target.
227 \item @isPermissionError@ / @PermissionDenied@
228 The process has insufficient privileges to perform the operation.
229 @[EROFS, EACCES, EPERM]@
230 \item @ResourceExhausted@
231 Insufficient resources are available to perform the operation.
232 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
233 \item @UnsatisfiedConstraints@
234 Implementation-dependent constraints are not satisfied.
235 @[EBUSY, ENOTEMPTY, EEXIST]@
236 \item @UnsupportedOperation@
237 The implementation does not support renaming in this situation.
239 \item @InappropriateType@
240 Either path refers to an existing non-directory object.
245 renameDirectory :: FilePath -> FilePath -> IO ()
246 renameDirectory opath npath =
247 withFileStatus opath $ \st -> do
248 is_dir <- isDirectory st
250 then ioException (IOError Nothing InappropriateType "renameDirectory"
251 ("not a directory") (Just opath))
254 withCString opath $ \s1 ->
255 withCString npath $ \s2 ->
256 throwErrnoIfMinus1Retry_ "renameDirectory" (c_rename s1 s2)
259 @renameFile@ {\em old} {\em new} changes the name of an existing file system
260 object from {\em old} to {\em new}. If the {\em new} object already
261 exists, it is atomically replaced by the {\em old} object. Neither
262 path may refer to an existing directory. A conformant implementation
263 need not support renaming files in all situations (e.g. renaming
264 across different physical devices), but the constraints must be
267 The operation may fail with:
269 \item @HardwareFault@
270 A physical I/O error has occurred.
272 \item @InvalidArgument@
273 Either operand is not a valid file name.
274 @[ENAMETOOLONG, ELOOP]@
275 \item @isDoesNotExistError@ / @NoSuchThing@
276 The original file does not exist, or there is no path to the target.
278 \item @isPermissionError@ / @PermissionDenied@
279 The process has insufficient privileges to perform the operation.
280 @[EROFS, EACCES, EPERM]@
281 \item @ResourceExhausted@
282 Insufficient resources are available to perform the operation.
283 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
284 \item @UnsatisfiedConstraints@
285 Implementation-dependent constraints are not satisfied.
287 \item @UnsupportedOperation@
288 The implementation does not support renaming in this situation.
290 \item @InappropriateType@
291 Either path refers to an existing directory.
292 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
296 renameFile :: FilePath -> FilePath -> IO ()
297 renameFile opath npath =
298 withFileOrSymlinkStatus opath $ \st -> do
299 is_dir <- isDirectory st
301 then ioException (IOError Nothing InappropriateType "renameFile"
302 "is a directory" (Just opath))
305 withCString opath $ \s1 ->
306 withCString npath $ \s2 ->
307 throwErrnoIfMinus1Retry_ "renameFile" (c_rename s1 s2)
310 @getDirectoryContents dir@ returns a list of {\em all} entries
313 The operation may fail with:
315 \item @HardwareFault@
316 A physical I/O error has occurred.
318 \item @InvalidArgument@
319 The operand is not a valid directory name.
320 @[ENAMETOOLONG, ELOOP]@
321 \item @isDoesNotExistError@ / @NoSuchThing@
322 The directory does not exist.
324 \item @isPermissionError@ / @PermissionDenied@
325 The process has insufficient privileges to perform the operation.
327 \item @ResourceExhausted@
328 Insufficient resources are available to perform the operation.
330 \item @InappropriateType@
331 The path refers to an existing non-directory object.
336 getDirectoryContents :: FilePath -> IO [FilePath]
337 getDirectoryContents path = do
338 alloca $ \ ptr_dEnt -> do
339 p <- withCString path $ \s ->
340 throwErrnoIfNullRetry "getDirectoryContents" (c_opendir s)
343 loop :: Ptr (Ptr CDirent) -> Ptr CDir -> IO [String]
344 loop ptr_dEnt dir = do
346 r <- readdir dir ptr_dEnt
349 dEnt <- peek ptr_dEnt
353 entry <- (d_name dEnt >>= peekCString)
355 entries <- loop ptr_dEnt dir
356 return (entry:entries)
357 else do errno <- getErrno
358 if (errno == eINTR) then loop ptr_dEnt dir else do
359 throwErrnoIfMinus1_ "getDirectoryContents" $ c_closedir dir
360 let (Errno eo) = errno
361 if (eo == end_of_dir)
363 else throwErrno "getDirectoryContents"
368 If the operating system has a notion of current directories,
369 @getCurrentDirectory@ returns an absolute path to the
370 current directory of the calling process.
372 The operation may fail with:
374 \item @HardwareFault@
375 A physical I/O error has occurred.
377 \item @isDoesNotExistError@ / @NoSuchThing@
378 There is no path referring to the current directory.
379 @[EPERM, ENOENT, ESTALE...]@
380 \item @isPermissionError@ / @PermissionDenied@
381 The process has insufficient privileges to perform the operation.
383 \item @ResourceExhausted@
384 Insufficient resources are available to perform the operation.
385 \item @UnsupportedOperation@
386 The operating system has no notion of current directory.
390 getCurrentDirectory :: IO FilePath
391 getCurrentDirectory = do
392 p <- mallocBytes path_max
394 where go p bytes = do
395 p' <- c_getcwd p (fromIntegral bytes)
397 then do s <- peekCString p'
400 else do errno <- getErrno
402 then do let bytes' = bytes * 2
403 p' <- reallocBytes p bytes'
405 else throwErrno "getCurrentDirectory"
408 If the operating system has a notion of current directories,
409 @setCurrentDirectory dir@ changes the current
410 directory of the calling process to {\em dir}.
412 The operation may fail with:
414 \item @HardwareFault@
415 A physical I/O error has occurred.
417 \item @InvalidArgument@
418 The operand is not a valid directory name.
419 @[ENAMETOOLONG, ELOOP]@
420 \item @isDoesNotExistError@ / @NoSuchThing@
421 The directory does not exist.
423 \item @isPermissionError@ / @PermissionDenied@
424 The process has insufficient privileges to perform the operation.
426 \item @UnsupportedOperation@
427 The operating system has no notion of current directory, or the
428 current directory cannot be dynamically changed.
429 \item @InappropriateType@
430 The path refers to an existing non-directory object.
435 setCurrentDirectory :: FilePath -> IO ()
436 setCurrentDirectory path = do
437 withCString path $ \s ->
438 throwErrnoIfMinus1Retry_ "setCurrentDirectory" (c_chdir s)
439 -- ToDo: add path to error
442 To clarify, @doesDirectoryExist@ returns True if a file system object
443 exist, and it's a directory. @doesFileExist@ returns True if the file
444 system object exist, but it's not a directory (i.e., for every other
445 file system object that is not a directory.)
448 doesDirectoryExist :: FilePath -> IO Bool
449 doesDirectoryExist name =
451 (withFileStatus name $ \st -> isDirectory st)
452 (\ _ -> return False)
454 doesFileExist :: FilePath -> IO Bool
455 doesFileExist name = do
457 (withFileStatus name $ \st -> do b <- isDirectory st; return (not b))
458 (\ _ -> return False)
460 getModificationTime :: FilePath -> IO ClockTime
461 getModificationTime name =
462 withFileStatus name $ \ st ->
465 getPermissions :: FilePath -> IO Permissions
466 getPermissions name = do
467 withCString name $ \s -> do
468 read <- c_access s r_OK
469 write <- c_access s w_OK
470 exec <- c_access s x_OK
471 withFileStatus name $ \st -> do
472 is_dir <- isDirectory st
475 readable = read == 0,
476 writable = write == 0,
477 executable = not is_dir && exec == 0,
478 searchable = is_dir && exec == 0
482 setPermissions :: FilePath -> Permissions -> IO ()
483 setPermissions name (Permissions r w e s) = do
485 read = if r then s_IRUSR else emptyCMode
486 write = if w then s_IWUSR else emptyCMode
487 exec = if e || s then s_IXUSR else emptyCMode
489 mode = read `unionCMode` (write `unionCMode` exec)
491 withCString name $ \s ->
492 throwErrnoIfMinus1_ "setPermissions" $ c_chmod s mode
494 withFileStatus :: FilePath -> (Ptr CStat -> IO a) -> IO a
495 withFileStatus name f = do
496 allocaBytes sizeof_stat $ \p ->
497 withCString name $ \s -> do
498 throwErrnoIfMinus1Retry_ "withFileStatus" (c_stat s p)
501 withFileOrSymlinkStatus :: FilePath -> (Ptr CStat -> IO a) -> IO a
502 withFileOrSymlinkStatus name f = do
503 allocaBytes sizeof_stat $ \p ->
504 withCString name $ \s -> do
505 throwErrnoIfMinus1Retry_ "withFileOrSymlinkStatus" (lstat s p)
508 modificationTime :: Ptr CStat -> IO ClockTime
509 modificationTime stat = do
510 mtime <- st_mtime stat
511 return (TOD (toInteger (mtime :: CTime)) 0)
513 isDirectory :: Ptr CStat -> IO Bool
514 isDirectory stat = do
516 return (s_isdir mode)
521 unionCMode :: CMode -> CMode -> CMode
525 foreign import ccall unsafe "__hscore_path_max"
528 foreign import ccall unsafe "__hscore_readdir"
529 readdir :: Ptr CDir -> Ptr (Ptr CDirent) -> IO CInt
531 foreign import ccall unsafe "__hscore_free_dirent"
532 freeDirEnt :: Ptr CDirent -> IO ()
534 foreign import ccall unsafe "__hscore_end_of_dir"
537 foreign import ccall unsafe "__hscore_d_name"
538 d_name :: Ptr CDirent -> IO CString
540 foreign import ccall unsafe "__hscore_R_OK" r_OK :: CMode
541 foreign import ccall unsafe "__hscore_W_OK" w_OK :: CMode
542 foreign import ccall unsafe "__hscore_X_OK" x_OK :: CMode
544 foreign import ccall unsafe "__hscore_S_IRUSR" s_IRUSR :: CMode
545 foreign import ccall unsafe "__hscore_S_IWUSR" s_IWUSR :: CMode
546 foreign import ccall unsafe "__hscore_S_IXUSR" s_IXUSR :: CMode