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
61 import PrelPack ( unpackNBytesST, packString, unpackCStringST )
63 import Time ( ClockTime(..) )
68 %*********************************************************
70 \subsection{Permissions}
72 %*********************************************************
74 The @Permissions@ type is used to record whether certain
75 operations are permissible on a file/directory:
76 [to whom? - owner/group/world - the Report don't say much]
82 executable, searchable :: Bool
83 } deriving (Eq, Ord, Read, Show)
86 %*********************************************************
88 \subsection{Implementation}
90 %*********************************************************
92 @createDirectory dir@ creates a new directory {\em dir} which is
93 initially empty, or as near to empty as the operating system
96 The operation may fail with:
99 \item @isPermissionError@ / @PermissionDenied@
100 The process has insufficient privileges to perform the operation.
102 \item @isAlreadyExistsError@ / @AlreadyExists@
103 The operand refers to a directory that already exists.
105 \item @HardwareFault@
106 A physical I/O error has occurred.
108 \item @InvalidArgument@
109 The operand is not a valid directory name.
110 @[ENAMETOOLONG, ELOOP]@
112 There is no path to the directory.
114 \item @ResourceExhausted@
115 Insufficient resources (virtual memory, process file descriptors,
116 physical disk space, etc.) are available to perform the operation.
117 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
118 \item @InappropriateType@
119 The path refers to an existing non-directory object.
124 createDirectory :: FilePath -> IO ()
125 createDirectory path = do
126 rc <- primCreateDirectory (primPackString path)
127 if rc == 0 then return () else
128 constructErrorAndFailWithInfo "createDirectory" path
131 @removeDirectory dir@ removes an existing directory {\em dir}. The
132 implementation may specify additional constraints which must be
133 satisfied before a directory can be removed (e.g. the directory has to
134 be empty, or may not be in use by other processes). It is not legal
135 for an implementation to partially remove a directory unless the
136 entire directory is removed. A conformant implementation need not
137 support directory removal in all situations (e.g. removal of the root
140 The operation may fail with:
142 \item @HardwareFault@
143 A physical I/O error has occurred.
145 \item @InvalidArgument@
146 The operand is not a valid directory name.
147 @[ENAMETOOLONG, ELOOP]@
148 \item @isDoesNotExist@ / @NoSuchThing@
149 The directory does not exist.
151 \item @isPermissionError@ / @PermissionDenied@
152 The process has insufficient privileges to perform the operation.
153 @[EROFS, EACCES, EPERM]@
154 \item @UnsatisfiedConstraints@
155 Implementation-dependent constraints are not satisfied.
156 @[EBUSY, ENOTEMPTY, EEXIST]@
157 \item @UnsupportedOperation@
158 The implementation does not support removal in this situation.
160 \item @InappropriateType@
161 The operand refers to an existing non-directory object.
166 removeDirectory :: FilePath -> IO ()
167 removeDirectory path = do
168 rc <- primRemoveDirectory (primPackString path)
172 constructErrorAndFailWithInfo "removeDirectory" path
175 @removeFile file@ removes the directory entry for an existing file
176 {\em file}, where {\em file} is not itself a directory. The
177 implementation may specify additional constraints which must be
178 satisfied before a file can be removed (e.g. the file may not be in
179 use by other processes).
181 The operation may fail with:
183 \item @HardwareFault@
184 A physical I/O error has occurred.
186 \item @InvalidArgument@
187 The operand is not a valid file name.
188 @[ENAMETOOLONG, ELOOP]@
189 \item @isDoesNotExist@ / @NoSuchThing@
190 The file does not exist.
192 \item @isPermissionError@ / @PermissionDenied@
193 The process has insufficient privileges to perform the operation.
194 @[EROFS, EACCES, EPERM]@
195 \item @UnsatisfiedConstraints@
196 Implementation-dependent constraints are not satisfied.
198 \item @InappropriateType@
199 The operand refers to an existing directory.
204 removeFile :: FilePath -> IO ()
206 rc <- primRemoveFile (primPackString path)
210 constructErrorAndFailWithInfo "removeFile" path
213 @renameDirectory old@ {\em new} changes the name of an existing
214 directory from {\em old} to {\em new}. If the {\em new} directory
215 already exists, it is atomically replaced by the {\em old} directory.
216 If the {\em new} directory is neither the {\em old} directory nor an
217 alias of the {\em old} directory, it is removed as if by
218 $removeDirectory$. A conformant implementation need not support
219 renaming directories in all situations (e.g. renaming to an existing
220 directory, or across different physical devices), but the constraints
223 The operation may fail with:
225 \item @HardwareFault@
226 A physical I/O error has occurred.
228 \item @InvalidArgument@
229 Either operand is not a valid directory name.
230 @[ENAMETOOLONG, ELOOP]@
231 \item @isDoesNotExistError@ / @NoSuchThing@
232 The original directory does not exist, or there is no path to the target.
234 \item @isPermissionError@ / @PermissionDenied@
235 The process has insufficient privileges to perform the operation.
236 @[EROFS, EACCES, EPERM]@
237 \item @ResourceExhausted@
238 Insufficient resources are available to perform the operation.
239 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
240 \item @UnsatisfiedConstraints@
241 Implementation-dependent constraints are not satisfied.
242 @[EBUSY, ENOTEMPTY, EEXIST]@
243 \item @UnsupportedOperation@
244 The implementation does not support renaming in this situation.
246 \item @InappropriateType@
247 Either path refers to an existing non-directory object.
252 renameDirectory :: FilePath -> FilePath -> IO ()
253 renameDirectory opath npath = do
254 rc <- primRenameDirectory (primPackString opath) (primPackString npath)
258 constructErrorAndFailWithInfo "renameDirectory" ("old: " ++ opath ++ ",new: " ++ npath)
261 @renameFile old@ {\em new} changes the name of an existing file system
262 object from {\em old} to {\em new}. If the {\em new} object already
263 exists, it is atomically replaced by the {\em old} object. Neither
264 path may refer to an existing directory. A conformant implementation
265 need not support renaming files in all situations (e.g. renaming
266 across different physical devices), but the constraints must be
269 The operation may fail with:
271 \item @HardwareFault@
272 A physical I/O error has occurred.
274 \item @InvalidArgument@
275 Either operand is not a valid file name.
276 @[ENAMETOOLONG, ELOOP]@
277 \item @isDoesNotExistError@ / @NoSuchThing@
278 The original file does not exist, or there is no path to the target.
280 \item @isPermissionError@ / @PermissionDenied@
281 The process has insufficient privileges to perform the operation.
282 @[EROFS, EACCES, EPERM]@
283 \item @ResourceExhausted@
284 Insufficient resources are available to perform the operation.
285 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
286 \item @UnsatisfiedConstraints@
287 Implementation-dependent constraints are not satisfied.
289 \item @UnsupportedOperation@
290 The implementation does not support renaming in this situation.
292 \item @InappropriateType@
293 Either path refers to an existing directory.
294 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
298 renameFile :: FilePath -> FilePath -> IO ()
299 renameFile opath npath = do
300 rc <- primRenameFile (primPackString opath) (primPackString npath)
304 constructErrorAndFailWithInfo "renameFile" opath
307 @getDirectoryContents dir@ returns a list of {\em all} entries
310 The operation may fail with:
312 \item @HardwareFault@
313 A physical I/O error has occurred.
315 \item @InvalidArgument@
316 The operand is not a valid directory name.
317 @[ENAMETOOLONG, ELOOP]@
318 \item @isDoesNotExistError@ / @NoSuchThing@
319 The directory does not exist.
321 \item @isPermissionError@ / @PermissionDenied@
322 The process has insufficient privileges to perform the operation.
324 \item @ResourceExhausted@
325 Insufficient resources are available to perform the operation.
327 \item @InappropriateType@
328 The path refers to an existing non-directory object.
333 getDirectoryContents :: FilePath -> IO [FilePath]
334 getDirectoryContents path = do
335 dir <- primOpenDir (primPackString path)
337 then constructErrorAndFailWithInfo "getDirectoryContents" path
340 loop :: Addr -> IO [String]
342 dirent_ptr <- primReadDir dir
343 if dirent_ptr == nullAddr
345 -- readDir__ implicitly performs closedir() when the
349 str <- primGetDirentDName dirent_ptr
350 entry <- primUnpackCString str
352 return (entry:entries)
355 If the operating system has a notion of current directories,
356 @getCurrentDirectory@ returns an absolute path to the
357 current directory of the calling process.
359 The operation may fail with:
361 \item @HardwareFault@
362 A physical I/O error has occurred.
364 \item @isDoesNotExistError@ / @NoSuchThing@
365 There is no path referring to the current directory.
366 @[EPERM, ENOENT, ESTALE...]@
367 \item @isPermissionError@ / @PermissionDenied@
368 The process has insufficient privileges to perform the operation.
370 \item @ResourceExhausted@
371 Insufficient resources are available to perform the operation.
372 \item @UnsupportedOperation@
373 The operating system has no notion of current directory.
377 getCurrentDirectory :: IO FilePath
378 getCurrentDirectory = do
379 str <- primGetCurrentDirectory
382 pwd <- primUnpackCString str
386 constructErrorAndFail "getCurrentDirectory"
389 If the operating system has a notion of current directories,
390 @setCurrentDirectory dir@ changes the current
391 directory of the calling process to {\em dir}.
393 The operation may fail with:
395 \item @HardwareFault@
396 A physical I/O error has occurred.
398 \item @InvalidArgument@
399 The operand is not a valid directory name.
400 @[ENAMETOOLONG, ELOOP]@
401 \item @isDoesNotExistError@ / @NoSuchThing@
402 The directory does not exist.
404 \item @isPermissionError@ / @PermissionDenied@
405 The process has insufficient privileges to perform the operation.
407 \item @UnsupportedOperation@
408 The operating system has no notion of current directory, or the
409 current directory cannot be dynamically changed.
410 \item @InappropriateType@
411 The path refers to an existing non-directory object.
416 setCurrentDirectory :: FilePath -> IO ()
417 setCurrentDirectory path = do
418 rc <- primSetCurrentDirectory (primPackString path)
421 else constructErrorAndFailWithInfo "setCurrentDirectory" path
424 To clarify, @doesDirectoryExist@ returns True if a file system object
425 exist, and it's a directory. @doesFileExist@ returns True if the file
426 system object exist, but it's not a directory (i.e., for every other
427 file system object that is not a directory.)
430 doesDirectoryExist :: FilePath -> IO Bool
431 doesDirectoryExist name =
433 (getFileStatus name >>= \ st -> return (isDirectory st))
434 (\ _ -> return False)
436 doesFileExist :: FilePath -> IO Bool
437 doesFileExist name = do
439 (getFileStatus name >>= \ st -> return (not (isDirectory st)))
440 (\ _ -> return False)
442 foreign import ccall "libHS_cbits.so" "const_F_OK" unsafe const_F_OK :: Int
445 getModificationTime :: FilePath -> IO ClockTime
446 getModificationTime name =
447 getFileStatus name >>= \ st ->
451 getPermissions :: FilePath -> IO Permissions
452 getPermissions name = do
453 st <- getFileStatus name
456 isect v = intersectFileMode v fm == v
460 readable = isect ownerReadMode,
461 writable = isect ownerWriteMode,
462 executable = not (isDirectory st) && isect ownerExecuteMode,
463 searchable = not (isRegularFile st) && isect ownerExecuteMode
467 setPermissions :: FilePath -> Permissions -> IO ()
468 setPermissions name (Permissions r w e s) = do
470 read = if r then ownerReadMode else emptyFileMode
471 write = if w then ownerWriteMode else emptyFileMode
472 exec = if e || s then ownerExecuteMode else emptyFileMode
474 mode = read `unionFileMode` (write `unionFileMode` exec)
476 rc <- primChmod (primPackString name) mode
479 else ioError (IOError Nothing SystemError "setPermissions" "insufficient permissions")
482 (Sigh)..copied from Posix.Files to avoid dep. on posix library
485 type FileStatus = PrimByteArray
487 getFileStatus :: FilePath -> IO FileStatus
488 getFileStatus name = do
489 bytes <- primNewByteArray sizeof_stat
490 rc <- primStat (primPackString name) bytes
493 then primUnsafeFreezeByteArray bytes
495 then stToIO (unsafeFreezeByteArray bytes)
497 else ioError (IOError Nothing SystemError "getFileStatus" "")
500 modificationTime :: FileStatus -> IO ClockTime
501 modificationTime stat = do
502 i1 <- stToIO (newWordArray (0,1))
504 secs <- stToIO (readWordArray i1 0)
505 return (TOD (toInteger (wordToInt secs)) 0)
507 foreign import ccall "libHS_cbits.so" "set_stat_st_mtime" unsafe
508 setFileMode :: PrimMutableByteArray RealWorld -> FileStatus -> IO ()
511 isDirectory :: FileStatus -> Bool
512 isDirectory stat = prim_S_ISDIR (fileMode stat) /= 0
514 isRegularFile :: FileStatus -> Bool
515 isRegularFile stat = prim_S_ISREG (fileMode stat) /= 0
517 foreign import ccall "libHS_cbits.so" "sizeof_stat" unsafe sizeof_stat :: Int
518 foreign import ccall "libHS_cbits.so" "prim_stat" unsafe
519 primStat :: PrimByteArray -> PrimMutableByteArray RealWorld -> IO Int
521 foreign import ccall "libHS_cbits.so" "get_stat_st_mode" unsafe fileMode :: FileStatus -> FileMode
522 foreign import ccall "libHS_cbits.so" "prim_S_ISDIR" unsafe prim_S_ISDIR :: FileMode -> Int
523 foreign import ccall "libHS_cbits.so" "prim_S_ISREG" unsafe prim_S_ISREG :: FileMode -> Int
529 emptyFileMode :: FileMode
530 unionFileMode :: FileMode -> FileMode -> FileMode
531 intersectFileMode :: FileMode -> FileMode -> FileMode
533 foreign import ccall "libHS_cbits.so" "const_S_IRUSR" unsafe ownerReadMode :: FileMode
534 foreign import ccall "libHS_cbits.so" "const_S_IWUSR" unsafe ownerWriteMode :: FileMode
535 foreign import ccall "libHS_cbits.so" "const_S_IXUSR" unsafe ownerExecuteMode :: FileMode
538 emptyFileMode = primIntToWord 0
539 unionFileMode = primOrWord
540 intersectFileMode = primAndWord
543 emptyFileMode = W# (int2Word# 0#)
544 unionFileMode = orWord
545 intersectFileMode = andWord
550 Some defns. to allow us to share code.
555 primPackString :: [Char] -> ByteArray Int
556 primPackString = packString
558 primUnpackCString :: Addr -> IO String
559 primUnpackCString a = stToIO (unpackCStringST a)
561 type PrimByteArray = ByteArray Int
562 type PrimMutableByteArray s = MutableByteArray RealWorld Int
563 type CString = PrimByteArray
565 orWord, andWord :: Word -> Word -> Word
566 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
567 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
569 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
570 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
573 foreign import ccall "libHS_cbits.so" "createDirectory" unsafe primCreateDirectory :: CString -> IO Int
574 foreign import ccall "libHS_cbits.so" "removeDirectory" unsafe primRemoveDirectory :: CString -> IO Int
575 foreign import ccall "libHS_cbits.so" "removeFile" unsafe primRemoveFile :: CString -> IO Int
576 foreign import ccall "libHS_cbits.so" "renameDirectory" unsafe primRenameDirectory :: CString -> CString -> IO Int
577 foreign import ccall "libHS_cbits.so" "renameFile" unsafe primRenameFile :: CString -> CString -> IO Int
578 foreign import ccall "libHS_cbits.so" "openDir__" unsafe primOpenDir :: CString -> IO Addr
579 foreign import ccall "libHS_cbits.so" "readDir__" unsafe primReadDir :: Addr -> IO Addr
580 foreign import ccall "libHS_cbits.so" "get_dirent_d_name" unsafe primGetDirentDName :: Addr -> IO Addr
581 foreign import ccall "libHS_cbits.so" "setCurrentDirectory" unsafe primSetCurrentDirectory :: CString -> IO Int
582 foreign import ccall "libHS_cbits.so" "getCurrentDirectory" unsafe primGetCurrentDirectory :: IO Addr
583 foreign import ccall "libc.so.6" "free" unsafe primFree :: Addr -> IO ()
584 foreign import ccall "libc.so.6" "malloc" unsafe primMalloc :: Word -> IO Addr
585 foreign import ccall "libc.so.6" "chmod" unsafe primChmod :: CString -> Word -> IO Int