1 % -----------------------------------------------------------------------------
2 % $Id: Directory.lhs,v 1.20 2000/08/24 10:27:01 simonmar Exp $
4 % (c) The University of Glasgow, 1994-2000
7 \section[Directory]{Directory interface}
9 A directory contains a series of entries, each of which is a named
10 reference to a file system object (file, directory etc.). Some
11 entries may be hidden, inaccessible, or have some administrative
12 function (e.g. "." or ".." under POSIX), but in this standard all such
13 entries are considered to form part of the directory contents.
14 Entries in sub-directories are not, however, considered to form part
15 of the directory contents.
17 Each file system object is referenced by a {\em path}. There is
18 normally at least one absolute path to each file system object. In
19 some operating systems, it may also be possible to have paths which
20 are relative to the current directory.
23 {-# OPTIONS -#include <sys/stat.h> -#include <dirent.h> -#include "cbits/stgio.h" #-}
26 Permissions -- abstract
28 , readable -- :: Permissions -> Bool
29 , writable -- :: Permissions -> Bool
30 , executable -- :: Permissions -> Bool
31 , searchable -- :: Permissions -> Bool
33 , createDirectory -- :: FilePath -> IO ()
34 , removeDirectory -- :: FilePath -> IO ()
35 , renameDirectory -- :: FilePath -> FilePath -> IO ()
37 , getDirectoryContents -- :: FilePath -> IO [FilePath]
38 , getCurrentDirectory -- :: IO FilePath
39 , setCurrentDirectory -- :: FilePath -> IO ()
41 , removeFile -- :: FilePath -> IO ()
42 , renameFile -- :: FilePath -> FilePath -> IO ()
44 , doesFileExist -- :: FilePath -> IO Bool
45 , doesDirectoryExist -- :: FilePath -> IO Bool
47 , getPermissions -- :: FilePath -> IO Permissions
48 , setPermissions -- :: FilePath -> Permissions -> IO ()
52 , getModificationTime -- :: FilePath -> IO ClockTime
57 --import PreludeBuiltin
60 import Prelude -- Just to get it in the dependencies
62 import PrelGHC ( RealWorld, or#, and# )
63 import PrelByteArr ( ByteArray, MutableByteArray,
64 newWordArray, readWordArray, newCharArray )
65 import PrelArrExtra ( unsafeFreezeByteArray )
66 import PrelPack ( packString, unpackCStringST )
67 import PrelIOBase ( stToIO,
68 constructErrorAndFail, constructErrorAndFailWithInfo,
69 IOException(..), ioException, IOErrorType(SystemError) )
70 import Time ( ClockTime(..) )
71 import PrelAddr ( Addr, nullAddr, Word(..), wordToInt, intToWord )
76 %*********************************************************
78 \subsection{Permissions}
80 %*********************************************************
82 The @Permissions@ type is used to record whether certain
83 operations are permissible on a file/directory:
84 [to whom? - owner/group/world - the Report don't say much]
90 executable, searchable :: Bool
91 } deriving (Eq, Ord, Read, Show)
94 %*********************************************************
96 \subsection{Implementation}
98 %*********************************************************
100 @createDirectory dir@ creates a new directory {\em dir} which is
101 initially empty, or as near to empty as the operating system
104 The operation may fail with:
107 \item @isPermissionError@ / @PermissionDenied@
108 The process has insufficient privileges to perform the operation.
110 \item @isAlreadyExistsError@ / @AlreadyExists@
111 The operand refers to a directory that already exists.
113 \item @HardwareFault@
114 A physical I/O error has occurred.
116 \item @InvalidArgument@
117 The operand is not a valid directory name.
118 @[ENAMETOOLONG, ELOOP]@
120 There is no path to the directory.
122 \item @ResourceExhausted@
123 Insufficient resources (virtual memory, process file descriptors,
124 physical disk space, etc.) are available to perform the operation.
125 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
126 \item @InappropriateType@
127 The path refers to an existing non-directory object.
132 createDirectory :: FilePath -> IO ()
133 createDirectory path = do
134 rc <- primCreateDirectory (primPackString path)
135 if rc == 0 then return () else
136 constructErrorAndFailWithInfo "createDirectory" path
139 @removeDirectory dir@ removes an existing directory {\em dir}. The
140 implementation may specify additional constraints which must be
141 satisfied before a directory can be removed (e.g. the directory has to
142 be empty, or may not be in use by other processes). It is not legal
143 for an implementation to partially remove a directory unless the
144 entire directory is removed. A conformant implementation need not
145 support directory removal in all situations (e.g. removal of the root
148 The operation may fail with:
150 \item @HardwareFault@
151 A physical I/O error has occurred.
153 \item @InvalidArgument@
154 The operand is not a valid directory name.
155 @[ENAMETOOLONG, ELOOP]@
156 \item @isDoesNotExist@ / @NoSuchThing@
157 The directory does not exist.
159 \item @isPermissionError@ / @PermissionDenied@
160 The process has insufficient privileges to perform the operation.
161 @[EROFS, EACCES, EPERM]@
162 \item @UnsatisfiedConstraints@
163 Implementation-dependent constraints are not satisfied.
164 @[EBUSY, ENOTEMPTY, EEXIST]@
165 \item @UnsupportedOperation@
166 The implementation does not support removal in this situation.
168 \item @InappropriateType@
169 The operand refers to an existing non-directory object.
174 removeDirectory :: FilePath -> IO ()
175 removeDirectory path = do
176 rc <- primRemoveDirectory (primPackString path)
180 constructErrorAndFailWithInfo "removeDirectory" path
183 @removeFile file@ removes the directory entry for an existing file
184 {\em file}, where {\em file} is not itself a directory. The
185 implementation may specify additional constraints which must be
186 satisfied before a file can be removed (e.g. the file may not be in
187 use by other processes).
189 The operation may fail with:
191 \item @HardwareFault@
192 A physical I/O error has occurred.
194 \item @InvalidArgument@
195 The operand is not a valid file name.
196 @[ENAMETOOLONG, ELOOP]@
197 \item @isDoesNotExist@ / @NoSuchThing@
198 The file does not exist.
200 \item @isPermissionError@ / @PermissionDenied@
201 The process has insufficient privileges to perform the operation.
202 @[EROFS, EACCES, EPERM]@
203 \item @UnsatisfiedConstraints@
204 Implementation-dependent constraints are not satisfied.
206 \item @InappropriateType@
207 The operand refers to an existing directory.
212 removeFile :: FilePath -> IO ()
214 rc <- primRemoveFile (primPackString path)
218 constructErrorAndFailWithInfo "removeFile" path
221 @renameDirectory old@ {\em new} changes the name of an existing
222 directory from {\em old} to {\em new}. If the {\em new} directory
223 already exists, it is atomically replaced by the {\em old} directory.
224 If the {\em new} directory is neither the {\em old} directory nor an
225 alias of the {\em old} directory, it is removed as if by
226 $removeDirectory$. A conformant implementation need not support
227 renaming directories in all situations (e.g. renaming to an existing
228 directory, or across different physical devices), but the constraints
231 The operation may fail with:
233 \item @HardwareFault@
234 A physical I/O error has occurred.
236 \item @InvalidArgument@
237 Either operand is not a valid directory name.
238 @[ENAMETOOLONG, ELOOP]@
239 \item @isDoesNotExistError@ / @NoSuchThing@
240 The original directory does not exist, or there is no path to the target.
242 \item @isPermissionError@ / @PermissionDenied@
243 The process has insufficient privileges to perform the operation.
244 @[EROFS, EACCES, EPERM]@
245 \item @ResourceExhausted@
246 Insufficient resources are available to perform the operation.
247 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
248 \item @UnsatisfiedConstraints@
249 Implementation-dependent constraints are not satisfied.
250 @[EBUSY, ENOTEMPTY, EEXIST]@
251 \item @UnsupportedOperation@
252 The implementation does not support renaming in this situation.
254 \item @InappropriateType@
255 Either path refers to an existing non-directory object.
260 renameDirectory :: FilePath -> FilePath -> IO ()
261 renameDirectory opath npath = do
262 rc <- primRenameDirectory (primPackString opath) (primPackString npath)
266 constructErrorAndFailWithInfo "renameDirectory" ("old: " ++ opath ++ ",new: " ++ npath)
269 @renameFile@ {\em old} {\em new} changes the name of an existing file system
270 object from {\em old} to {\em new}. If the {\em new} object already
271 exists, it is atomically replaced by the {\em old} object. Neither
272 path may refer to an existing directory. A conformant implementation
273 need not support renaming files in all situations (e.g. renaming
274 across different physical devices), but the constraints must be
277 The operation may fail with:
279 \item @HardwareFault@
280 A physical I/O error has occurred.
282 \item @InvalidArgument@
283 Either operand is not a valid file name.
284 @[ENAMETOOLONG, ELOOP]@
285 \item @isDoesNotExistError@ / @NoSuchThing@
286 The original file does not exist, or there is no path to the target.
288 \item @isPermissionError@ / @PermissionDenied@
289 The process has insufficient privileges to perform the operation.
290 @[EROFS, EACCES, EPERM]@
291 \item @ResourceExhausted@
292 Insufficient resources are available to perform the operation.
293 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
294 \item @UnsatisfiedConstraints@
295 Implementation-dependent constraints are not satisfied.
297 \item @UnsupportedOperation@
298 The implementation does not support renaming in this situation.
300 \item @InappropriateType@
301 Either path refers to an existing directory.
302 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
306 renameFile :: FilePath -> FilePath -> IO ()
307 renameFile opath npath = do
308 rc <- primRenameFile (primPackString opath) (primPackString npath)
312 constructErrorAndFailWithInfo "renameFile" opath
315 @getDirectoryContents dir@ returns a list of {\em all} entries
318 The operation may fail with:
320 \item @HardwareFault@
321 A physical I/O error has occurred.
323 \item @InvalidArgument@
324 The operand is not a valid directory name.
325 @[ENAMETOOLONG, ELOOP]@
326 \item @isDoesNotExistError@ / @NoSuchThing@
327 The directory does not exist.
329 \item @isPermissionError@ / @PermissionDenied@
330 The process has insufficient privileges to perform the operation.
332 \item @ResourceExhausted@
333 Insufficient resources are available to perform the operation.
335 \item @InappropriateType@
336 The path refers to an existing non-directory object.
341 getDirectoryContents :: FilePath -> IO [FilePath]
342 getDirectoryContents path = do
343 dir <- primOpenDir (primPackString path)
345 then constructErrorAndFailWithInfo "getDirectoryContents" path
348 loop :: Addr -> IO [String]
350 dirent_ptr <- primReadDir dir
351 if dirent_ptr == nullAddr
353 -- readDir__ implicitly performs closedir() when the
357 str <- primGetDirentDName dirent_ptr
358 entry <- primUnpackCString str
360 return (entry:entries)
363 If the operating system has a notion of current directories,
364 @getCurrentDirectory@ returns an absolute path to the
365 current directory of the calling process.
367 The operation may fail with:
369 \item @HardwareFault@
370 A physical I/O error has occurred.
372 \item @isDoesNotExistError@ / @NoSuchThing@
373 There is no path referring to the current directory.
374 @[EPERM, ENOENT, ESTALE...]@
375 \item @isPermissionError@ / @PermissionDenied@
376 The process has insufficient privileges to perform the operation.
378 \item @ResourceExhausted@
379 Insufficient resources are available to perform the operation.
380 \item @UnsupportedOperation@
381 The operating system has no notion of current directory.
385 getCurrentDirectory :: IO FilePath
386 getCurrentDirectory = do
387 str <- primGetCurrentDirectory
390 pwd <- primUnpackCString str
394 constructErrorAndFail "getCurrentDirectory"
397 If the operating system has a notion of current directories,
398 @setCurrentDirectory dir@ changes the current
399 directory of the calling process to {\em dir}.
401 The operation may fail with:
403 \item @HardwareFault@
404 A physical I/O error has occurred.
406 \item @InvalidArgument@
407 The operand is not a valid directory name.
408 @[ENAMETOOLONG, ELOOP]@
409 \item @isDoesNotExistError@ / @NoSuchThing@
410 The directory does not exist.
412 \item @isPermissionError@ / @PermissionDenied@
413 The process has insufficient privileges to perform the operation.
415 \item @UnsupportedOperation@
416 The operating system has no notion of current directory, or the
417 current directory cannot be dynamically changed.
418 \item @InappropriateType@
419 The path refers to an existing non-directory object.
424 setCurrentDirectory :: FilePath -> IO ()
425 setCurrentDirectory path = do
426 rc <- primSetCurrentDirectory (primPackString path)
429 else constructErrorAndFailWithInfo "setCurrentDirectory" path
432 To clarify, @doesDirectoryExist@ returns True if a file system object
433 exist, and it's a directory. @doesFileExist@ returns True if the file
434 system object exist, but it's not a directory (i.e., for every other
435 file system object that is not a directory.)
438 doesDirectoryExist :: FilePath -> IO Bool
439 doesDirectoryExist name =
441 (getFileStatus name >>= \ st -> return (isDirectory st))
442 (\ _ -> return False)
444 doesFileExist :: FilePath -> IO Bool
445 doesFileExist name = do
447 (getFileStatus name >>= \ st -> return (not (isDirectory st)))
448 (\ _ -> return False)
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
460 read <- primAccess (primPackString name) readOK
461 write <- primAccess (primPackString name) writeOK
462 exec <- primAccess (primPackString name) executeOK
466 readable = read == 0,
467 writable = write == 0,
468 executable = not (isDirectory st) && exec == 0,
469 searchable = not (isRegularFile st) && exec == 0
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 ioException (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 ioException (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" "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" "sizeof_stat" unsafe sizeof_stat :: Int
524 foreign import ccall "libHS_cbits" "prim_stat" unsafe
525 primStat :: PrimByteArray -> PrimMutableByteArray RealWorld -> IO Int
527 foreign import ccall "libHS_cbits" "get_stat_st_mode" unsafe fileMode :: FileStatus -> FileMode
528 foreign import ccall "libHS_cbits" "prim_S_ISDIR" unsafe prim_S_ISDIR :: FileMode -> Int
529 foreign import ccall "libHS_cbits" "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" "const_S_IRUSR" unsafe ownerReadMode :: FileMode
540 foreign import ccall "libHS_cbits" "const_S_IWUSR" unsafe ownerWriteMode :: FileMode
541 foreign import ccall "libHS_cbits" "const_S_IXUSR" unsafe ownerExecuteMode :: FileMode
544 emptyFileMode = primIntToWord 0
545 unionFileMode = primOrWord
546 intersectFileMode = primAndWord
548 emptyFileMode = intToWord 0
549 unionFileMode = orWord
550 intersectFileMode = andWord
555 type AccessMode = Word
557 foreign import ccall "libHS_cbits" "const_R_OK" unsafe readOK :: AccessMode
558 foreign import ccall "libHS_cbits" "const_W_OK" unsafe writeOK :: AccessMode
559 foreign import ccall "libHS_cbits" "const_X_OK" unsafe executeOK :: AccessMode
562 Some defns. to allow us to share code.
567 primPackString :: [Char] -> ByteArray Int
568 primPackString = packString
570 primUnpackCString :: Addr -> IO String
571 primUnpackCString a = stToIO (unpackCStringST a)
573 type PrimByteArray = ByteArray Int
574 type PrimMutableByteArray s = MutableByteArray RealWorld Int
575 type CString = PrimByteArray
577 orWord, andWord :: Word -> Word -> Word
578 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
579 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
581 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
582 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
585 foreign import ccall "libHS_cbits" "createDirectory" unsafe primCreateDirectory :: CString -> IO Int
586 foreign import ccall "libHS_cbits" "removeDirectory" unsafe primRemoveDirectory :: CString -> IO Int
587 foreign import ccall "libHS_cbits" "removeFile" unsafe primRemoveFile :: CString -> IO Int
588 foreign import ccall "libHS_cbits" "renameDirectory" unsafe primRenameDirectory :: CString -> CString -> IO Int
589 foreign import ccall "libHS_cbits" "renameFile" unsafe primRenameFile :: CString -> CString -> IO Int
590 foreign import ccall "libHS_cbits" "openDir__" unsafe primOpenDir :: CString -> IO Addr
591 foreign import ccall "libHS_cbits" "readDir__" unsafe primReadDir :: Addr -> IO Addr
592 foreign import ccall "libHS_cbits" "get_dirent_d_name" unsafe primGetDirentDName :: Addr -> IO Addr
593 foreign import ccall "libHS_cbits" "setCurrentDirectory" unsafe primSetCurrentDirectory :: CString -> IO Int
594 foreign import ccall "libHS_cbits" "getCurrentDirectory" unsafe primGetCurrentDirectory :: IO Addr
595 foreign import ccall "libc" "free" unsafe primFree :: Addr -> IO ()
596 foreign import ccall "libc" "malloc" unsafe primMalloc :: Word -> IO Addr
597 foreign import ccall "libc" "chmod" unsafe primChmod :: CString -> Word -> IO Int
599 foreign import ccall "libc" "access" unsafe
600 primAccess :: CString -> Word -> IO Int