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(Permissions,readable,writable,executable,searchable)
25 , createDirectory -- :: FilePath -> IO ()
26 , removeDirectory -- :: FilePath -> IO ()
27 , renameDirectory -- :: FilePath -> FilePath -> IO ()
29 , getDirectoryContents -- :: FilePath -> IO [FilePath]
30 , getCurrentDirectory -- :: IO FilePath
31 , setCurrentDirectory -- :: FilePath -> IO ()
33 , removeFile -- :: FilePath -> IO ()
34 , renameFile -- :: FilePath -> FilePath -> IO ()
36 , doesFileExist -- :: FilePath -> IO Bool
37 , doesDirectoryExist -- :: FilePath -> IO Bool
39 , getPermissions -- :: FilePath -> IO Permissions
40 , setPermissions -- :: FilePath -> Permissions -> IO ()
44 , getModificationTime -- :: FilePath -> IO ClockTime
56 import PrelPack ( unpackNBytesST, packString, unpackCStringST )
58 import Time ( ClockTime(..) )
63 %*********************************************************
65 \subsection{Permissions}
67 %*********************************************************
69 The @Permissions@ type is used to record whether certain
70 operations are permissible on a file/directory:
71 [to whom? - owner/group/world - the Report don't say much]
77 executable, searchable :: Bool
78 } deriving (Eq, Ord, Read, Show)
81 %*********************************************************
83 \subsection{Implementation}
85 %*********************************************************
87 @createDirectory dir@ creates a new directory {\em dir} which is
88 initially empty, or as near to empty as the operating system
91 The operation may fail with:
94 \item @isPermissionError@ / @PermissionDenied@
95 The process has insufficient privileges to perform the operation.
97 \item @isAlreadyExistsError@ / @AlreadyExists@
98 The operand refers to a directory that already exists.
100 \item @HardwareFault@
101 A physical I/O error has occurred.
103 \item @InvalidArgument@
104 The operand is not a valid directory name.
105 @[ENAMETOOLONG, ELOOP]@
107 There is no path to the directory.
109 \item @ResourceExhausted@
110 Insufficient resources (virtual memory, process file descriptors,
111 physical disk space, etc.) are available to perform the operation.
112 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
113 \item @InappropriateType@
114 The path refers to an existing non-directory object.
119 createDirectory :: FilePath -> IO ()
120 createDirectory path = do
121 rc <- primCreateDirectory (primPackString path)
122 if rc == 0 then return () else
123 constructErrorAndFailWithInfo "createDirectory" path
126 @removeDirectory dir@ removes an existing directory {\em dir}. The
127 implementation may specify additional constraints which must be
128 satisfied before a directory can be removed (e.g. the directory has to
129 be empty, or may not be in use by other processes). It is not legal
130 for an implementation to partially remove a directory unless the
131 entire directory is removed. A conformant implementation need not
132 support directory removal in all situations (e.g. removal of the root
135 The operation may fail with:
137 \item @HardwareFault@
138 A physical I/O error has occurred.
140 \item @InvalidArgument@
141 The operand is not a valid directory name.
142 @[ENAMETOOLONG, ELOOP]@
143 \item @isDoesNotExist@ / @NoSuchThing@
144 The directory does not exist.
146 \item @isPermissionError@ / @PermissionDenied@
147 The process has insufficient privileges to perform the operation.
148 @[EROFS, EACCES, EPERM]@
149 \item @UnsatisfiedConstraints@
150 Implementation-dependent constraints are not satisfied.
151 @[EBUSY, ENOTEMPTY, EEXIST]@
152 \item @UnsupportedOperation@
153 The implementation does not support removal in this situation.
155 \item @InappropriateType@
156 The operand refers to an existing non-directory object.
161 removeDirectory :: FilePath -> IO ()
162 removeDirectory path = do
163 rc <- primRemoveDirectory (primPackString path)
167 constructErrorAndFailWithInfo "removeDirectory" path
170 @removeFile file@ removes the directory entry for an existing file
171 {\em file}, where {\em file} is not itself a directory. The
172 implementation may specify additional constraints which must be
173 satisfied before a file can be removed (e.g. the file may not be in
174 use by other processes).
176 The operation may fail with:
178 \item @HardwareFault@
179 A physical I/O error has occurred.
181 \item @InvalidArgument@
182 The operand is not a valid file name.
183 @[ENAMETOOLONG, ELOOP]@
184 \item @isDoesNotExist@ / @NoSuchThing@
185 The file does not exist.
187 \item @isPermissionError@ / @PermissionDenied@
188 The process has insufficient privileges to perform the operation.
189 @[EROFS, EACCES, EPERM]@
190 \item @UnsatisfiedConstraints@
191 Implementation-dependent constraints are not satisfied.
193 \item @InappropriateType@
194 The operand refers to an existing directory.
199 removeFile :: FilePath -> IO ()
201 rc <- primRemoveFile (primPackString path)
205 constructErrorAndFailWithInfo "removeFile" path
208 @renameDirectory old@ {\em new} changes the name of an existing
209 directory from {\em old} to {\em new}. If the {\em new} directory
210 already exists, it is atomically replaced by the {\em old} directory.
211 If the {\em new} directory is neither the {\em old} directory nor an
212 alias of the {\em old} directory, it is removed as if by
213 $removeDirectory$. A conformant implementation need not support
214 renaming directories in all situations (e.g. renaming to an existing
215 directory, or across different physical devices), but the constraints
218 The operation may fail with:
220 \item @HardwareFault@
221 A physical I/O error has occurred.
223 \item @InvalidArgument@
224 Either operand is not a valid directory name.
225 @[ENAMETOOLONG, ELOOP]@
226 \item @isDoesNotExistError@ / @NoSuchThing@
227 The original directory does not exist, or there is no path to the target.
229 \item @isPermissionError@ / @PermissionDenied@
230 The process has insufficient privileges to perform the operation.
231 @[EROFS, EACCES, EPERM]@
232 \item @ResourceExhausted@
233 Insufficient resources are available to perform the operation.
234 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
235 \item @UnsatisfiedConstraints@
236 Implementation-dependent constraints are not satisfied.
237 @[EBUSY, ENOTEMPTY, EEXIST]@
238 \item @UnsupportedOperation@
239 The implementation does not support renaming in this situation.
241 \item @InappropriateType@
242 Either path refers to an existing non-directory object.
247 renameDirectory :: FilePath -> FilePath -> IO ()
248 renameDirectory opath npath = do
249 rc <- primRenameDirectory (primPackString opath) (primPackString npath)
253 constructErrorAndFailWithInfo "renameDirectory" ("old: " ++ opath ++ ",new: " ++ npath)
256 @renameFile old@ {\em new} changes the name of an existing file system
257 object from {\em old} to {\em new}. If the {\em new} object already
258 exists, it is atomically replaced by the {\em old} object. Neither
259 path may refer to an existing directory. A conformant implementation
260 need not support renaming files in all situations (e.g. renaming
261 across different physical devices), but the constraints must be
264 The operation may fail with:
266 \item @HardwareFault@
267 A physical I/O error has occurred.
269 \item @InvalidArgument@
270 Either operand is not a valid file name.
271 @[ENAMETOOLONG, ELOOP]@
272 \item @isDoesNotExistError@ / @NoSuchThing@
273 The original file does not exist, or there is no path to the target.
275 \item @isPermissionError@ / @PermissionDenied@
276 The process has insufficient privileges to perform the operation.
277 @[EROFS, EACCES, EPERM]@
278 \item @ResourceExhausted@
279 Insufficient resources are available to perform the operation.
280 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
281 \item @UnsatisfiedConstraints@
282 Implementation-dependent constraints are not satisfied.
284 \item @UnsupportedOperation@
285 The implementation does not support renaming in this situation.
287 \item @InappropriateType@
288 Either path refers to an existing directory.
289 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
293 renameFile :: FilePath -> FilePath -> IO ()
294 renameFile opath npath = do
295 rc <- primRenameFile (primPackString opath) (primPackString npath)
299 constructErrorAndFailWithInfo "renameFile" opath
302 @getDirectoryContents dir@ returns a list of {\em all} entries
305 The operation may fail with:
307 \item @HardwareFault@
308 A physical I/O error has occurred.
310 \item @InvalidArgument@
311 The operand is not a valid directory name.
312 @[ENAMETOOLONG, ELOOP]@
313 \item @isDoesNotExistError@ / @NoSuchThing@
314 The directory does not exist.
316 \item @isPermissionError@ / @PermissionDenied@
317 The process has insufficient privileges to perform the operation.
319 \item @ResourceExhausted@
320 Insufficient resources are available to perform the operation.
322 \item @InappropriateType@
323 The path refers to an existing non-directory object.
328 getDirectoryContents :: FilePath -> IO [FilePath]
329 getDirectoryContents path = do
330 dir <- primOpenDir (primPackString path)
332 then constructErrorAndFailWithInfo "getDirectoryContents" path
335 loop :: Addr -> IO [String]
337 dirent_ptr <- primReadDir dir
338 if dirent_ptr == nullAddr
340 -- readDir__ implicitly performs closedir() when the
344 str <- primGetDirentDName dirent_ptr
345 entry <- primUnpackCString str
347 return (entry:entries)
350 If the operating system has a notion of current directories,
351 @getCurrentDirectory@ returns an absolute path to the
352 current directory of the calling process.
354 The operation may fail with:
356 \item @HardwareFault@
357 A physical I/O error has occurred.
359 \item @isDoesNotExistError@ / @NoSuchThing@
360 There is no path referring to the current directory.
361 @[EPERM, ENOENT, ESTALE...]@
362 \item @isPermissionError@ / @PermissionDenied@
363 The process has insufficient privileges to perform the operation.
365 \item @ResourceExhausted@
366 Insufficient resources are available to perform the operation.
367 \item @UnsupportedOperation@
368 The operating system has no notion of current directory.
372 getCurrentDirectory :: IO FilePath
373 getCurrentDirectory = do
374 str <- primGetCurrentDirectory
377 pwd <- primUnpackCString str
381 constructErrorAndFail "getCurrentDirectory"
384 If the operating system has a notion of current directories,
385 @setCurrentDirectory dir@ changes the current
386 directory of the calling process to {\em dir}.
388 The operation may fail with:
390 \item @HardwareFault@
391 A physical I/O error has occurred.
393 \item @InvalidArgument@
394 The operand is not a valid directory name.
395 @[ENAMETOOLONG, ELOOP]@
396 \item @isDoesNotExistError@ / @NoSuchThing@
397 The directory does not exist.
399 \item @isPermissionError@ / @PermissionDenied@
400 The process has insufficient privileges to perform the operation.
402 \item @UnsupportedOperation@
403 The operating system has no notion of current directory, or the
404 current directory cannot be dynamically changed.
405 \item @InappropriateType@
406 The path refers to an existing non-directory object.
411 setCurrentDirectory :: FilePath -> IO ()
412 setCurrentDirectory path = do
413 rc <- primSetCurrentDirectory (primPackString path)
416 else constructErrorAndFailWithInfo "setCurrentDirectory" path
419 To clarify, @doesDirectoryExist@ returns True if a file system object
420 exist, and it's a directory. @doesFileExist@ returns True if the file
421 system object exist, but it's not a directory (i.e., for every other
422 file system object that is not a directory.)
425 doesDirectoryExist :: FilePath -> IO Bool
426 doesDirectoryExist name =
428 (getFileStatus name >>= \ st -> return (isDirectory st))
429 (\ _ -> return False)
431 doesFileExist :: FilePath -> IO Bool
432 doesFileExist name = do
434 (getFileStatus name >>= \ st -> return (not (isDirectory st)))
435 (\ _ -> return False)
437 foreign import ccall "libHS_cbits.so" "const_F_OK" const_F_OK :: Int
440 getModificationTime :: FilePath -> IO ClockTime
441 getModificationTime name =
442 getFileStatus name >>= \ st ->
446 getPermissions :: FilePath -> IO Permissions
447 getPermissions name = do
448 st <- getFileStatus name
451 isect v = intersectFileMode v fm == v
455 readable = isect ownerReadMode,
456 writable = isect ownerWriteMode,
457 executable = not (isDirectory st) && isect ownerExecuteMode,
458 searchable = not (isRegularFile st) && isect ownerExecuteMode
462 setPermissions :: FilePath -> Permissions -> IO ()
463 setPermissions name (Permissions r w e s) = do
465 read = if r then ownerReadMode else emptyFileMode
466 write = if w then ownerWriteMode else emptyFileMode
467 exec = if e || s then ownerExecuteMode else emptyFileMode
469 mode = read `unionFileMode` (write `unionFileMode` exec)
471 rc <- primChmod (primPackString name) mode
474 else ioError (IOError Nothing SystemError "setPermissions" "insufficient permissions")
477 (Sigh)..copied from Posix.Files to avoid dep. on posix library
480 type FileStatus = PrimByteArray
482 getFileStatus :: FilePath -> IO FileStatus
483 getFileStatus name = do
484 bytes <- primNewByteArray sizeof_stat
485 rc <- primStat (primPackString name) bytes
488 then primUnsafeFreezeByteArray bytes
490 then stToIO (unsafeFreezeByteArray bytes)
492 else ioError (IOError Nothing SystemError "getFileStatus" "")
495 modificationTime :: FileStatus -> IO ClockTime
496 modificationTime stat = do
497 -- ToDo: better, this is ugly stuff.
500 secs <- cvtUnsigned i1
503 malloc1 = IO $ \ s# ->
504 case newIntArray# 1# s# of
505 (# s2#, barr# #) -> (# s2#, MutableByteArray bnds barr# #)
508 -- The C routine fills in an unsigned word. We don't have `unsigned2Integer#,'
509 -- so we freeze the data bits and use them for an MP_INT structure. Note that
510 -- zero is still handled specially, although (J# 1# 1# (ptr to 0#)) is probably
511 -- acceptable to gmp.
513 cvtUnsigned (MutableByteArray _ arr#) = IO $ \ s# ->
514 case readIntArray# arr# 0# s# of
519 case unsafeFreezeByteArray# arr# s2# of
520 (# s3#, frozen# #) ->
521 (# s3#, J# 1# 1# frozen# #)
523 foreign import ccall "libHS_cbits.so" "set_stat_st_mtime"
524 setFileMode :: PrimMutableByteArray RealWorld -> FileStatus -> IO ()
528 isDirectory :: FileStatus -> Bool
529 isDirectory stat = prim_S_ISDIR (fileMode stat) /= 0
531 isRegularFile :: FileStatus -> Bool
532 isRegularFile stat = prim_S_ISREG (fileMode stat) /= 0
534 foreign import ccall "libHS_cbits.so" "sizeof_stat" sizeof_stat :: Int
535 foreign import ccall "libHS_cbits.so" "prim_stat" primStat :: PrimByteArray -> PrimMutableByteArray RealWorld -> IO Int
537 foreign import ccall "libHS_cbits.so" "get_stat_st_mode" fileMode :: FileStatus -> FileMode
538 foreign import ccall "libHS_cbits.so" "prim_S_ISDIR" prim_S_ISDIR :: FileMode -> Int
539 foreign import ccall "libHS_cbits.so" "prim_S_ISREG" prim_S_ISREG :: FileMode -> Int
545 emptyFileMode :: FileMode
546 unionFileMode :: FileMode -> FileMode -> FileMode
547 intersectFileMode :: FileMode -> FileMode -> FileMode
549 foreign import ccall "libHS_cbits.so" "const_S_IRUSR" unsafe ownerReadMode :: FileMode
550 foreign import ccall "libHS_cbits.so" "const_S_IWUSR" unsafe ownerWriteMode :: FileMode
551 foreign import ccall "libHS_cbits.so" "const_S_IXUSR" unsafe ownerExecuteMode :: FileMode
554 emptyFileMode = primIntToWord 0
555 unionFileMode = primOrWord
556 intersectFileMode = primAndWord
559 emptyFileMode = W# (int2Word# 0#)
560 unionFileMode = orWord
561 intersectFileMode = andWord
566 Some defns. to allow us to share code.
571 primPackString :: [Char] -> ByteArray Int
572 primPackString = packString
574 primUnpackCString :: Addr -> IO String
575 primUnpackCString a = stToIO (unpackCStringST a)
577 type PrimByteArray = ByteArray Int
578 type PrimMutableByteArray s = MutableByteArray RealWorld Int
579 type CString = PrimByteArray
581 orWord, andWord :: Word -> Word -> Word
582 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
583 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
585 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
586 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
589 foreign import ccall "libHS_cbits.so" "createDirectory" primCreateDirectory :: CString -> IO Int
590 foreign import ccall "libHS_cbits.so" "removeDirectory" primRemoveDirectory :: CString -> IO Int
591 foreign import ccall "libHS_cbits.so" "removeFile" primRemoveFile :: CString -> IO Int
592 foreign import ccall "libHS_cbits.so" "renameDirectory" primRenameDirectory :: CString -> CString -> IO Int
593 foreign import ccall "libHS_cbits.so" "renameFile" primRenameFile :: CString -> CString -> IO Int
594 foreign import ccall "libHS_cbits.so" "openDir__" primOpenDir :: CString -> IO Addr
595 foreign import ccall "libHS_cbits.so" "readDir__" primReadDir :: Addr -> IO Addr
596 foreign import ccall "libHS_cbits.so" "get_dirent_d_name" primGetDirentDName :: Addr -> IO Addr
597 foreign import ccall "libHS_cbits.so" "setCurrentDirectory" primSetCurrentDirectory :: CString -> IO Int
598 foreign import ccall "libHS_cbits.so" "getCurrentDirectory" primGetCurrentDirectory :: IO Addr
599 foreign import ccall "libc.so.6" "free" primFree :: Addr -> IO ()
600 foreign import ccall "libc.so.6" "malloc" primMalloc :: Word -> IO Addr
601 foreign import ccall "libc.so.6" "chmod" primChmod :: CString -> Word -> IO Int