1 % -----------------------------------------------------------------------------
2 % $Id: Directory.lhs,v 1.21 2001/01/11 07:04:16 qrczak 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
486 "setPermissions" "insufficient permissions" (Just name))
489 (Sigh)..copied from Posix.Files to avoid dep. on posix library
492 type FileStatus = PrimByteArray
494 getFileStatus :: FilePath -> IO FileStatus
495 getFileStatus name = do
496 bytes <- primNewByteArray sizeof_stat
497 rc <- primStat (primPackString name) bytes
500 then primUnsafeFreezeByteArray bytes
502 then stToIO (unsafeFreezeByteArray bytes)
504 else ioException (IOError Nothing SystemError
505 "getFileStatus" "" (Just name))
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 type AccessMode = Word
559 foreign import ccall "libHS_cbits" "const_R_OK" unsafe readOK :: AccessMode
560 foreign import ccall "libHS_cbits" "const_W_OK" unsafe writeOK :: AccessMode
561 foreign import ccall "libHS_cbits" "const_X_OK" unsafe executeOK :: AccessMode
564 Some defns. to allow us to share code.
569 primPackString :: [Char] -> ByteArray Int
570 primPackString = packString
572 primUnpackCString :: Addr -> IO String
573 primUnpackCString a = stToIO (unpackCStringST a)
575 type PrimByteArray = ByteArray Int
576 type PrimMutableByteArray s = MutableByteArray RealWorld Int
577 type CString = PrimByteArray
579 orWord, andWord :: Word -> Word -> Word
580 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
581 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
583 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
584 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
587 foreign import ccall "libHS_cbits" "createDirectory" unsafe primCreateDirectory :: CString -> IO Int
588 foreign import ccall "libHS_cbits" "removeDirectory" unsafe primRemoveDirectory :: CString -> IO Int
589 foreign import ccall "libHS_cbits" "removeFile" unsafe primRemoveFile :: CString -> IO Int
590 foreign import ccall "libHS_cbits" "renameDirectory" unsafe primRenameDirectory :: CString -> CString -> IO Int
591 foreign import ccall "libHS_cbits" "renameFile" unsafe primRenameFile :: CString -> CString -> IO Int
592 foreign import ccall "libHS_cbits" "openDir__" unsafe primOpenDir :: CString -> IO Addr
593 foreign import ccall "libHS_cbits" "readDir__" unsafe primReadDir :: Addr -> IO Addr
594 foreign import ccall "libHS_cbits" "get_dirent_d_name" unsafe primGetDirentDName :: Addr -> IO Addr
595 foreign import ccall "libHS_cbits" "setCurrentDirectory" unsafe primSetCurrentDirectory :: CString -> IO Int
596 foreign import ccall "libHS_cbits" "getCurrentDirectory" unsafe primGetCurrentDirectory :: IO Addr
597 foreign import ccall "libc" "free" unsafe primFree :: Addr -> IO ()
598 foreign import ccall "libc" "malloc" unsafe primMalloc :: Word -> IO Addr
599 foreign import ccall "libc" "chmod" unsafe primChmod :: CString -> Word -> IO Int
601 foreign import ccall "libc" "access" unsafe
602 primAccess :: CString -> Word -> IO Int