1 % -----------------------------------------------------------------------------
2 % $Id: Directory.lhs,v 1.19 2000/07/07 11:03:57 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)
450 foreign import ccall "libHS_cbits" "const_F_OK" unsafe const_F_OK :: Int
453 getModificationTime :: FilePath -> IO ClockTime
454 getModificationTime name =
455 getFileStatus name >>= \ st ->
459 getPermissions :: FilePath -> IO Permissions
460 getPermissions name = do
461 st <- getFileStatus name
464 isect v = intersectFileMode v fm == v
468 readable = isect ownerReadMode,
469 writable = isect ownerWriteMode,
470 executable = not (isDirectory st) && isect ownerExecuteMode,
471 searchable = not (isRegularFile st) && isect ownerExecuteMode
475 setPermissions :: FilePath -> Permissions -> IO ()
476 setPermissions name (Permissions r w e s) = do
478 read = if r then ownerReadMode else emptyFileMode
479 write = if w then ownerWriteMode else emptyFileMode
480 exec = if e || s then ownerExecuteMode else emptyFileMode
482 mode = read `unionFileMode` (write `unionFileMode` exec)
484 rc <- primChmod (primPackString name) mode
487 else ioException (IOError Nothing SystemError "setPermissions" "insufficient permissions")
490 (Sigh)..copied from Posix.Files to avoid dep. on posix library
493 type FileStatus = PrimByteArray
495 getFileStatus :: FilePath -> IO FileStatus
496 getFileStatus name = do
497 bytes <- primNewByteArray sizeof_stat
498 rc <- primStat (primPackString name) bytes
501 then primUnsafeFreezeByteArray bytes
503 then stToIO (unsafeFreezeByteArray bytes)
505 else ioException (IOError Nothing SystemError "getFileStatus" "")
508 modificationTime :: FileStatus -> IO ClockTime
509 modificationTime stat = do
510 i1 <- stToIO (newWordArray (0,1))
512 secs <- stToIO (readWordArray i1 0)
513 return (TOD (toInteger (wordToInt secs)) 0)
515 foreign import ccall "libHS_cbits" "set_stat_st_mtime" unsafe
516 setFileMode :: PrimMutableByteArray RealWorld -> FileStatus -> IO ()
519 isDirectory :: FileStatus -> Bool
520 isDirectory stat = prim_S_ISDIR (fileMode stat) /= 0
522 isRegularFile :: FileStatus -> Bool
523 isRegularFile stat = prim_S_ISREG (fileMode stat) /= 0
525 foreign import ccall "libHS_cbits" "sizeof_stat" unsafe sizeof_stat :: Int
526 foreign import ccall "libHS_cbits" "prim_stat" unsafe
527 primStat :: PrimByteArray -> PrimMutableByteArray RealWorld -> IO Int
529 foreign import ccall "libHS_cbits" "get_stat_st_mode" unsafe fileMode :: FileStatus -> FileMode
530 foreign import ccall "libHS_cbits" "prim_S_ISDIR" unsafe prim_S_ISDIR :: FileMode -> Int
531 foreign import ccall "libHS_cbits" "prim_S_ISREG" unsafe prim_S_ISREG :: FileMode -> Int
537 emptyFileMode :: FileMode
538 unionFileMode :: FileMode -> FileMode -> FileMode
539 intersectFileMode :: FileMode -> FileMode -> FileMode
541 foreign import ccall "libHS_cbits" "const_S_IRUSR" unsafe ownerReadMode :: FileMode
542 foreign import ccall "libHS_cbits" "const_S_IWUSR" unsafe ownerWriteMode :: FileMode
543 foreign import ccall "libHS_cbits" "const_S_IXUSR" unsafe ownerExecuteMode :: FileMode
546 emptyFileMode = primIntToWord 0
547 unionFileMode = primOrWord
548 intersectFileMode = primAndWord
550 emptyFileMode = intToWord 0
551 unionFileMode = orWord
552 intersectFileMode = andWord
557 Some defns. to allow us to share code.
562 primPackString :: [Char] -> ByteArray Int
563 primPackString = packString
565 primUnpackCString :: Addr -> IO String
566 primUnpackCString a = stToIO (unpackCStringST a)
568 type PrimByteArray = ByteArray Int
569 type PrimMutableByteArray s = MutableByteArray RealWorld Int
570 type CString = PrimByteArray
572 orWord, andWord :: Word -> Word -> Word
573 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
574 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
576 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
577 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
580 foreign import ccall "libHS_cbits" "createDirectory" unsafe primCreateDirectory :: CString -> IO Int
581 foreign import ccall "libHS_cbits" "removeDirectory" unsafe primRemoveDirectory :: CString -> IO Int
582 foreign import ccall "libHS_cbits" "removeFile" unsafe primRemoveFile :: CString -> IO Int
583 foreign import ccall "libHS_cbits" "renameDirectory" unsafe primRenameDirectory :: CString -> CString -> IO Int
584 foreign import ccall "libHS_cbits" "renameFile" unsafe primRenameFile :: CString -> CString -> IO Int
585 foreign import ccall "libHS_cbits" "openDir__" unsafe primOpenDir :: CString -> IO Addr
586 foreign import ccall "libHS_cbits" "readDir__" unsafe primReadDir :: Addr -> IO Addr
587 foreign import ccall "libHS_cbits" "get_dirent_d_name" unsafe primGetDirentDName :: Addr -> IO Addr
588 foreign import ccall "libHS_cbits" "setCurrentDirectory" unsafe primSetCurrentDirectory :: CString -> IO Int
589 foreign import ccall "libHS_cbits" "getCurrentDirectory" unsafe primGetCurrentDirectory :: IO Addr
590 foreign import ccall "libc" "free" unsafe primFree :: Addr -> IO ()
591 foreign import ccall "libc" "malloc" unsafe primMalloc :: Word -> IO Addr
592 foreign import ccall "libc" "chmod" unsafe primChmod :: CString -> Word -> IO Int