2 % (c) The AQUA Project, Glasgow University, 1994-1999
4 \section[Directory]{Directory interface}
6 A directory contains a series of entries, each of which is a named
7 reference to a file system object (file, directory etc.). Some
8 entries may be hidden, inaccessible, or have some administrative
9 function (e.g. "." or ".." under POSIX), but in this standard all such
10 entries are considered to form part of the directory contents.
11 Entries in sub-directories are not, however, considered to form part
12 of the directory contents.
14 Each file system object is referenced by a {\em path}. There is
15 normally at least one absolute path to each file system object. In
16 some operating systems, it may also be possible to have paths which
17 are relative to the current directory.
20 {-# OPTIONS -#include <sys/stat.h> -#include <dirent.h> -#include "cbits/stgio.h" #-}
23 Permissions -- abstract
25 , readable -- :: Permissions -> Bool
26 , writable -- :: Permissions -> Bool
27 , executable -- :: Permissions -> Bool
28 , searchable -- :: Permissions -> Bool
30 , createDirectory -- :: FilePath -> IO ()
31 , removeDirectory -- :: FilePath -> IO ()
32 , renameDirectory -- :: FilePath -> FilePath -> IO ()
34 , getDirectoryContents -- :: FilePath -> IO [FilePath]
35 , getCurrentDirectory -- :: IO FilePath
36 , setCurrentDirectory -- :: FilePath -> IO ()
38 , removeFile -- :: FilePath -> IO ()
39 , renameFile -- :: FilePath -> FilePath -> IO ()
41 , doesFileExist -- :: FilePath -> IO Bool
42 , doesDirectoryExist -- :: FilePath -> IO Bool
44 , getPermissions -- :: FilePath -> IO Permissions
45 , setPermissions -- :: FilePath -> Permissions -> IO ()
49 , getModificationTime -- :: FilePath -> IO ClockTime
54 --import PreludeBuiltin
57 import Prelude -- Just to get it in the dependencies
59 import PrelGHC ( RealWorld, int2Word#, or#, and# )
60 import PrelByteArr ( ByteArray, MutableByteArray,
61 newWordArray, readWordArray, newCharArray,
64 import PrelPack ( unpackNBytesST, packString, unpackCStringST )
65 import PrelIOBase ( stToIO,
66 constructErrorAndFail, constructErrorAndFailWithInfo,
67 IOError(IOError), IOErrorType(SystemError) )
68 import Time ( ClockTime(..) )
69 import PrelAddr ( Addr, nullAddr, Word(..), wordToInt )
74 %*********************************************************
76 \subsection{Permissions}
78 %*********************************************************
80 The @Permissions@ type is used to record whether certain
81 operations are permissible on a file/directory:
82 [to whom? - owner/group/world - the Report don't say much]
88 executable, searchable :: Bool
89 } deriving (Eq, Ord, Read, Show)
92 %*********************************************************
94 \subsection{Implementation}
96 %*********************************************************
98 @createDirectory dir@ creates a new directory {\em dir} which is
99 initially empty, or as near to empty as the operating system
102 The operation may fail with:
105 \item @isPermissionError@ / @PermissionDenied@
106 The process has insufficient privileges to perform the operation.
108 \item @isAlreadyExistsError@ / @AlreadyExists@
109 The operand refers to a directory that already exists.
111 \item @HardwareFault@
112 A physical I/O error has occurred.
114 \item @InvalidArgument@
115 The operand is not a valid directory name.
116 @[ENAMETOOLONG, ELOOP]@
118 There is no path to the directory.
120 \item @ResourceExhausted@
121 Insufficient resources (virtual memory, process file descriptors,
122 physical disk space, etc.) are available to perform the operation.
123 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
124 \item @InappropriateType@
125 The path refers to an existing non-directory object.
130 createDirectory :: FilePath -> IO ()
131 createDirectory path = do
132 rc <- primCreateDirectory (primPackString path)
133 if rc == 0 then return () else
134 constructErrorAndFailWithInfo "createDirectory" path
137 @removeDirectory dir@ removes an existing directory {\em dir}. The
138 implementation may specify additional constraints which must be
139 satisfied before a directory can be removed (e.g. the directory has to
140 be empty, or may not be in use by other processes). It is not legal
141 for an implementation to partially remove a directory unless the
142 entire directory is removed. A conformant implementation need not
143 support directory removal in all situations (e.g. removal of the root
146 The operation may fail with:
148 \item @HardwareFault@
149 A physical I/O error has occurred.
151 \item @InvalidArgument@
152 The operand is not a valid directory name.
153 @[ENAMETOOLONG, ELOOP]@
154 \item @isDoesNotExist@ / @NoSuchThing@
155 The directory does not exist.
157 \item @isPermissionError@ / @PermissionDenied@
158 The process has insufficient privileges to perform the operation.
159 @[EROFS, EACCES, EPERM]@
160 \item @UnsatisfiedConstraints@
161 Implementation-dependent constraints are not satisfied.
162 @[EBUSY, ENOTEMPTY, EEXIST]@
163 \item @UnsupportedOperation@
164 The implementation does not support removal in this situation.
166 \item @InappropriateType@
167 The operand refers to an existing non-directory object.
172 removeDirectory :: FilePath -> IO ()
173 removeDirectory path = do
174 rc <- primRemoveDirectory (primPackString path)
178 constructErrorAndFailWithInfo "removeDirectory" path
181 @removeFile file@ removes the directory entry for an existing file
182 {\em file}, where {\em file} is not itself a directory. The
183 implementation may specify additional constraints which must be
184 satisfied before a file can be removed (e.g. the file may not be in
185 use by other processes).
187 The operation may fail with:
189 \item @HardwareFault@
190 A physical I/O error has occurred.
192 \item @InvalidArgument@
193 The operand is not a valid file name.
194 @[ENAMETOOLONG, ELOOP]@
195 \item @isDoesNotExist@ / @NoSuchThing@
196 The file does not exist.
198 \item @isPermissionError@ / @PermissionDenied@
199 The process has insufficient privileges to perform the operation.
200 @[EROFS, EACCES, EPERM]@
201 \item @UnsatisfiedConstraints@
202 Implementation-dependent constraints are not satisfied.
204 \item @InappropriateType@
205 The operand refers to an existing directory.
210 removeFile :: FilePath -> IO ()
212 rc <- primRemoveFile (primPackString path)
216 constructErrorAndFailWithInfo "removeFile" path
219 @renameDirectory old@ {\em new} changes the name of an existing
220 directory from {\em old} to {\em new}. If the {\em new} directory
221 already exists, it is atomically replaced by the {\em old} directory.
222 If the {\em new} directory is neither the {\em old} directory nor an
223 alias of the {\em old} directory, it is removed as if by
224 $removeDirectory$. A conformant implementation need not support
225 renaming directories in all situations (e.g. renaming to an existing
226 directory, or across different physical devices), but the constraints
229 The operation may fail with:
231 \item @HardwareFault@
232 A physical I/O error has occurred.
234 \item @InvalidArgument@
235 Either operand is not a valid directory name.
236 @[ENAMETOOLONG, ELOOP]@
237 \item @isDoesNotExistError@ / @NoSuchThing@
238 The original directory does not exist, or there is no path to the target.
240 \item @isPermissionError@ / @PermissionDenied@
241 The process has insufficient privileges to perform the operation.
242 @[EROFS, EACCES, EPERM]@
243 \item @ResourceExhausted@
244 Insufficient resources are available to perform the operation.
245 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
246 \item @UnsatisfiedConstraints@
247 Implementation-dependent constraints are not satisfied.
248 @[EBUSY, ENOTEMPTY, EEXIST]@
249 \item @UnsupportedOperation@
250 The implementation does not support renaming in this situation.
252 \item @InappropriateType@
253 Either path refers to an existing non-directory object.
258 renameDirectory :: FilePath -> FilePath -> IO ()
259 renameDirectory opath npath = do
260 rc <- primRenameDirectory (primPackString opath) (primPackString npath)
264 constructErrorAndFailWithInfo "renameDirectory" ("old: " ++ opath ++ ",new: " ++ npath)
267 @renameFile old@ {\em new} changes the name of an existing file system
268 object from {\em old} to {\em new}. If the {\em new} object already
269 exists, it is atomically replaced by the {\em old} object. Neither
270 path may refer to an existing directory. A conformant implementation
271 need not support renaming files in all situations (e.g. renaming
272 across different physical devices), but the constraints must be
275 The operation may fail with:
277 \item @HardwareFault@
278 A physical I/O error has occurred.
280 \item @InvalidArgument@
281 Either operand is not a valid file name.
282 @[ENAMETOOLONG, ELOOP]@
283 \item @isDoesNotExistError@ / @NoSuchThing@
284 The original file does not exist, or there is no path to the target.
286 \item @isPermissionError@ / @PermissionDenied@
287 The process has insufficient privileges to perform the operation.
288 @[EROFS, EACCES, EPERM]@
289 \item @ResourceExhausted@
290 Insufficient resources are available to perform the operation.
291 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
292 \item @UnsatisfiedConstraints@
293 Implementation-dependent constraints are not satisfied.
295 \item @UnsupportedOperation@
296 The implementation does not support renaming in this situation.
298 \item @InappropriateType@
299 Either path refers to an existing directory.
300 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
304 renameFile :: FilePath -> FilePath -> IO ()
305 renameFile opath npath = do
306 rc <- primRenameFile (primPackString opath) (primPackString npath)
310 constructErrorAndFailWithInfo "renameFile" opath
313 @getDirectoryContents dir@ returns a list of {\em all} entries
316 The operation may fail with:
318 \item @HardwareFault@
319 A physical I/O error has occurred.
321 \item @InvalidArgument@
322 The operand is not a valid directory name.
323 @[ENAMETOOLONG, ELOOP]@
324 \item @isDoesNotExistError@ / @NoSuchThing@
325 The directory does not exist.
327 \item @isPermissionError@ / @PermissionDenied@
328 The process has insufficient privileges to perform the operation.
330 \item @ResourceExhausted@
331 Insufficient resources are available to perform the operation.
333 \item @InappropriateType@
334 The path refers to an existing non-directory object.
339 getDirectoryContents :: FilePath -> IO [FilePath]
340 getDirectoryContents path = do
341 dir <- primOpenDir (primPackString path)
343 then constructErrorAndFailWithInfo "getDirectoryContents" path
346 loop :: Addr -> IO [String]
348 dirent_ptr <- primReadDir dir
349 if dirent_ptr == nullAddr
351 -- readDir__ implicitly performs closedir() when the
355 str <- primGetDirentDName dirent_ptr
356 entry <- primUnpackCString str
358 return (entry:entries)
361 If the operating system has a notion of current directories,
362 @getCurrentDirectory@ returns an absolute path to the
363 current directory of the calling process.
365 The operation may fail with:
367 \item @HardwareFault@
368 A physical I/O error has occurred.
370 \item @isDoesNotExistError@ / @NoSuchThing@
371 There is no path referring to the current directory.
372 @[EPERM, ENOENT, ESTALE...]@
373 \item @isPermissionError@ / @PermissionDenied@
374 The process has insufficient privileges to perform the operation.
376 \item @ResourceExhausted@
377 Insufficient resources are available to perform the operation.
378 \item @UnsupportedOperation@
379 The operating system has no notion of current directory.
383 getCurrentDirectory :: IO FilePath
384 getCurrentDirectory = do
385 str <- primGetCurrentDirectory
388 pwd <- primUnpackCString str
392 constructErrorAndFail "getCurrentDirectory"
395 If the operating system has a notion of current directories,
396 @setCurrentDirectory dir@ changes the current
397 directory of the calling process to {\em dir}.
399 The operation may fail with:
401 \item @HardwareFault@
402 A physical I/O error has occurred.
404 \item @InvalidArgument@
405 The operand is not a valid directory name.
406 @[ENAMETOOLONG, ELOOP]@
407 \item @isDoesNotExistError@ / @NoSuchThing@
408 The directory does not exist.
410 \item @isPermissionError@ / @PermissionDenied@
411 The process has insufficient privileges to perform the operation.
413 \item @UnsupportedOperation@
414 The operating system has no notion of current directory, or the
415 current directory cannot be dynamically changed.
416 \item @InappropriateType@
417 The path refers to an existing non-directory object.
422 setCurrentDirectory :: FilePath -> IO ()
423 setCurrentDirectory path = do
424 rc <- primSetCurrentDirectory (primPackString path)
427 else constructErrorAndFailWithInfo "setCurrentDirectory" path
430 To clarify, @doesDirectoryExist@ returns True if a file system object
431 exist, and it's a directory. @doesFileExist@ returns True if the file
432 system object exist, but it's not a directory (i.e., for every other
433 file system object that is not a directory.)
436 doesDirectoryExist :: FilePath -> IO Bool
437 doesDirectoryExist name =
439 (getFileStatus name >>= \ st -> return (isDirectory st))
440 (\ _ -> return False)
442 doesFileExist :: FilePath -> IO Bool
443 doesFileExist name = do
445 (getFileStatus name >>= \ st -> return (not (isDirectory st)))
446 (\ _ -> return False)
448 foreign import ccall "libHS_cbits.so" "const_F_OK" unsafe const_F_OK :: Int
451 getModificationTime :: FilePath -> IO ClockTime
452 getModificationTime name =
453 getFileStatus name >>= \ st ->
457 getPermissions :: FilePath -> IO Permissions
458 getPermissions name = do
459 st <- getFileStatus name
462 isect v = intersectFileMode v fm == v
466 readable = isect ownerReadMode,
467 writable = isect ownerWriteMode,
468 executable = not (isDirectory st) && isect ownerExecuteMode,
469 searchable = not (isRegularFile st) && isect ownerExecuteMode
473 setPermissions :: FilePath -> Permissions -> IO ()
474 setPermissions name (Permissions r w e s) = do
476 read = if r then ownerReadMode else emptyFileMode
477 write = if w then ownerWriteMode else emptyFileMode
478 exec = if e || s then ownerExecuteMode else emptyFileMode
480 mode = read `unionFileMode` (write `unionFileMode` exec)
482 rc <- primChmod (primPackString name) mode
485 else ioError (IOError Nothing SystemError "setPermissions" "insufficient permissions")
488 (Sigh)..copied from Posix.Files to avoid dep. on posix library
491 type FileStatus = PrimByteArray
493 getFileStatus :: FilePath -> IO FileStatus
494 getFileStatus name = do
495 bytes <- primNewByteArray sizeof_stat
496 rc <- primStat (primPackString name) bytes
499 then primUnsafeFreezeByteArray bytes
501 then stToIO (unsafeFreezeByteArray bytes)
503 else ioError (IOError Nothing SystemError "getFileStatus" "")
506 modificationTime :: FileStatus -> IO ClockTime
507 modificationTime stat = do
508 i1 <- stToIO (newWordArray (0,1))
510 secs <- stToIO (readWordArray i1 0)
511 return (TOD (toInteger (wordToInt secs)) 0)
513 foreign import ccall "libHS_cbits.so" "set_stat_st_mtime" unsafe
514 setFileMode :: PrimMutableByteArray RealWorld -> FileStatus -> IO ()
517 isDirectory :: FileStatus -> Bool
518 isDirectory stat = prim_S_ISDIR (fileMode stat) /= 0
520 isRegularFile :: FileStatus -> Bool
521 isRegularFile stat = prim_S_ISREG (fileMode stat) /= 0
523 foreign import ccall "libHS_cbits.so" "sizeof_stat" unsafe sizeof_stat :: Int
524 foreign import ccall "libHS_cbits.so" "prim_stat" unsafe
525 primStat :: PrimByteArray -> PrimMutableByteArray RealWorld -> IO Int
527 foreign import ccall "libHS_cbits.so" "get_stat_st_mode" unsafe fileMode :: FileStatus -> FileMode
528 foreign import ccall "libHS_cbits.so" "prim_S_ISDIR" unsafe prim_S_ISDIR :: FileMode -> Int
529 foreign import ccall "libHS_cbits.so" "prim_S_ISREG" unsafe prim_S_ISREG :: FileMode -> Int
535 emptyFileMode :: FileMode
536 unionFileMode :: FileMode -> FileMode -> FileMode
537 intersectFileMode :: FileMode -> FileMode -> FileMode
539 foreign import ccall "libHS_cbits.so" "const_S_IRUSR" unsafe ownerReadMode :: FileMode
540 foreign import ccall "libHS_cbits.so" "const_S_IWUSR" unsafe ownerWriteMode :: FileMode
541 foreign import ccall "libHS_cbits.so" "const_S_IXUSR" unsafe ownerExecuteMode :: FileMode
544 emptyFileMode = primIntToWord 0
545 unionFileMode = primOrWord
546 intersectFileMode = primAndWord
549 emptyFileMode = W# (int2Word# 0#)
550 unionFileMode = orWord
551 intersectFileMode = andWord
556 Some defns. to allow us to share code.
561 primPackString :: [Char] -> ByteArray Int
562 primPackString = packString
564 primUnpackCString :: Addr -> IO String
565 primUnpackCString a = stToIO (unpackCStringST a)
567 type PrimByteArray = ByteArray Int
568 type PrimMutableByteArray s = MutableByteArray RealWorld Int
569 type CString = PrimByteArray
571 orWord, andWord :: Word -> Word -> Word
572 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
573 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
575 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
576 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
579 foreign import ccall "libHS_cbits.so" "createDirectory" unsafe primCreateDirectory :: CString -> IO Int
580 foreign import ccall "libHS_cbits.so" "removeDirectory" unsafe primRemoveDirectory :: CString -> IO Int
581 foreign import ccall "libHS_cbits.so" "removeFile" unsafe primRemoveFile :: CString -> IO Int
582 foreign import ccall "libHS_cbits.so" "renameDirectory" unsafe primRenameDirectory :: CString -> CString -> IO Int
583 foreign import ccall "libHS_cbits.so" "renameFile" unsafe primRenameFile :: CString -> CString -> IO Int
584 foreign import ccall "libHS_cbits.so" "openDir__" unsafe primOpenDir :: CString -> IO Addr
585 foreign import ccall "libHS_cbits.so" "readDir__" unsafe primReadDir :: Addr -> IO Addr
586 foreign import ccall "libHS_cbits.so" "get_dirent_d_name" unsafe primGetDirentDName :: Addr -> IO Addr
587 foreign import ccall "libHS_cbits.so" "setCurrentDirectory" unsafe primSetCurrentDirectory :: CString -> IO Int
588 foreign import ccall "libHS_cbits.so" "getCurrentDirectory" unsafe primGetCurrentDirectory :: IO Addr
589 foreign import ccall "libc.so.6" "free" unsafe primFree :: Addr -> IO ()
590 foreign import ccall "libc.so.6" "malloc" unsafe primMalloc :: Word -> IO Addr
591 foreign import ccall "libc.so.6" "chmod" unsafe primChmod :: CString -> Word -> IO Int