1 -- -----------------------------------------------------------------------------
2 -- $Id: Directory.hsc,v 1.15 2001/08/21 08:53:22 simonmar Exp $
4 -- (c) The University of Glasgow, 1994-2000
7 -- The Directory Interface
10 A directory contains a series of entries, each of which is a named
11 reference to a file system object (file, directory etc.). Some
12 entries may be hidden, inaccessible, or have some administrative
13 function (e.g. "." or ".." under POSIX), but in this standard all such
14 entries are considered to form part of the directory contents.
15 Entries in sub-directories are not, however, considered to form part
16 of the directory contents.
18 Each file system object is referenced by a {\em path}. There is
19 normally at least one absolute path to each file system object. In
20 some operating systems, it may also be possible to have paths which
21 are relative to the current directory.
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 ()
50 , getModificationTime -- :: FilePath -> IO ClockTime
53 import Prelude -- Just to get it in the dependencies
55 import Time ( ClockTime(..) )
60 import PrelMarshalAlloc
75 -----------------------------------------------------------------------------
78 -- The @Permissions@ type is used to record whether certain
79 -- operations are permissible on a file/directory:
80 -- [to whom? - presumably the "current user"]
85 executable, searchable :: Bool
86 } deriving (Eq, Ord, Read, Show)
88 -----------------------------------------------------------------------------
91 -- @createDirectory dir@ creates a new directory {\em dir} which is
92 -- initially empty, or as near to empty as the operating system
95 -- 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 withCString path $ \s -> do
127 throwErrnoIfMinus1Retry_ "createDirectory" $
128 #if defined(mingw32_TARGET_OS)
135 @removeDirectory dir@ removes an existing directory {\em dir}. The
136 implementation may specify additional constraints which must be
137 satisfied before a directory can be removed (e.g. the directory has to
138 be empty, or may not be in use by other processes). It is not legal
139 for an implementation to partially remove a directory unless the
140 entire directory is removed. A conformant implementation need not
141 support directory removal in all situations (e.g. removal of the root
144 The operation may fail with:
146 \item @HardwareFault@
147 A physical I/O error has occurred.
149 \item @InvalidArgument@
150 The operand is not a valid directory name.
151 @[ENAMETOOLONG, ELOOP]@
152 \item @isDoesNotExist@ / @NoSuchThing@
153 The directory does not exist.
155 \item @isPermissionError@ / @PermissionDenied@
156 The process has insufficient privileges to perform the operation.
157 @[EROFS, EACCES, EPERM]@
158 \item @UnsatisfiedConstraints@
159 Implementation-dependent constraints are not satisfied.
160 @[EBUSY, ENOTEMPTY, EEXIST]@
161 \item @UnsupportedOperation@
162 The implementation does not support removal in this situation.
164 \item @InappropriateType@
165 The operand refers to an existing non-directory object.
170 removeDirectory :: FilePath -> IO ()
171 removeDirectory path = do
172 withCString path $ \s ->
173 throwErrnoIfMinus1Retry_ "removeDirectory" (rmdir s)
176 @Removefile file@ removes the directory entry for an existing file
177 {\em file}, where {\em file} is not itself a directory. The
178 implementation may specify additional constraints which must be
179 satisfied before a file can be removed (e.g. the file may not be in
180 use by other processes).
182 The operation may fail with:
184 \item @HardwareFault@
185 A physical I/O error has occurred.
187 \item @InvalidArgument@
188 The operand is not a valid file name.
189 @[ENAMETOOLONG, ELOOP]@
190 \item @isDoesNotExist@ / @NoSuchThing@
191 The file does not exist.
193 \item @isPermissionError@ / @PermissionDenied@
194 The process has insufficient privileges to perform the operation.
195 @[EROFS, EACCES, EPERM]@
196 \item @UnsatisfiedConstraints@
197 Implementation-dependent constraints are not satisfied.
199 \item @InappropriateType@
200 The operand refers to an existing directory.
205 removeFile :: FilePath -> IO ()
207 withCString path $ \s ->
208 throwErrnoIfMinus1Retry_ "removeFile" (unlink s)
211 @renameDirectory@ {\em old} {\em new} changes the name of an existing
212 directory from {\em old} to {\em new}. If the {\em new} directory
213 already exists, it is atomically replaced by the {\em old} directory.
214 If the {\em new} directory is neither the {\em old} directory nor an
215 alias of the {\em old} directory, it is removed as if by
216 $removeDirectory$. A conformant implementation need not support
217 renaming directories in all situations (e.g. renaming to an existing
218 directory, or across different physical devices), but the constraints
221 The operation may fail with:
223 \item @HardwareFault@
224 A physical I/O error has occurred.
226 \item @InvalidArgument@
227 Either operand is not a valid directory name.
228 @[ENAMETOOLONG, ELOOP]@
229 \item @isDoesNotExistError@ / @NoSuchThing@
230 The original directory does not exist, or there is no path to the target.
232 \item @isPermissionError@ / @PermissionDenied@
233 The process has insufficient privileges to perform the operation.
234 @[EROFS, EACCES, EPERM]@
235 \item @ResourceExhausted@
236 Insufficient resources are available to perform the operation.
237 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
238 \item @UnsatisfiedConstraints@
239 Implementation-dependent constraints are not satisfied.
240 @[EBUSY, ENOTEMPTY, EEXIST]@
241 \item @UnsupportedOperation@
242 The implementation does not support renaming in this situation.
244 \item @InappropriateType@
245 Either path refers to an existing non-directory object.
250 renameDirectory :: FilePath -> FilePath -> IO ()
251 renameDirectory opath npath =
252 withFileStatus opath $ \st -> do
253 is_dir <- isDirectory st
255 then ioException (IOError Nothing InappropriateType "renameDirectory"
256 ("not a directory") (Just opath))
259 withCString opath $ \s1 ->
260 withCString npath $ \s2 ->
261 throwErrnoIfMinus1Retry_ "renameDirectory" (rename s1 s2)
264 @renameFile@ {\em old} {\em new} changes the name of an existing file system
265 object from {\em old} to {\em new}. If the {\em new} object already
266 exists, it is atomically replaced by the {\em old} object. Neither
267 path may refer to an existing directory. A conformant implementation
268 need not support renaming files in all situations (e.g. renaming
269 across different physical devices), but the constraints must be
272 The operation may fail with:
274 \item @HardwareFault@
275 A physical I/O error has occurred.
277 \item @InvalidArgument@
278 Either operand is not a valid file name.
279 @[ENAMETOOLONG, ELOOP]@
280 \item @isDoesNotExistError@ / @NoSuchThing@
281 The original file does not exist, or there is no path to the target.
283 \item @isPermissionError@ / @PermissionDenied@
284 The process has insufficient privileges to perform the operation.
285 @[EROFS, EACCES, EPERM]@
286 \item @ResourceExhausted@
287 Insufficient resources are available to perform the operation.
288 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
289 \item @UnsatisfiedConstraints@
290 Implementation-dependent constraints are not satisfied.
292 \item @UnsupportedOperation@
293 The implementation does not support renaming in this situation.
295 \item @InappropriateType@
296 Either path refers to an existing directory.
297 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
301 renameFile :: FilePath -> FilePath -> IO ()
302 renameFile opath npath =
303 withFileOrSymlinkStatus opath $ \st -> do
304 is_dir <- isDirectory st
306 then ioException (IOError Nothing InappropriateType "renameFile"
307 "is a directory" (Just opath))
310 withCString opath $ \s1 ->
311 withCString npath $ \s2 ->
312 throwErrnoIfMinus1Retry_ "renameFile" (rename s1 s2)
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 p <- withCString path $ \s ->
344 throwErrnoIfNullRetry "getDirectoryContents" (opendir s)
347 loop :: Ptr CDir -> IO [String]
353 #ifndef mingw32_TARGET_OS
354 entry <- peekCString ((#ptr struct dirent,d_name) p)
356 entryp <- (#peek struct dirent,d_name) p
357 entry <- peekCString entryp -- on mingwin it's a char *, not a char []
360 return (entry:entries)
361 else do errno <- getErrno
362 if (errno == eINTR) then loop dir else do
363 throwErrnoIfMinus1_ "getDirectoryContents" $ closedir dir
364 #ifndef mingw32_TARGET_OS
367 if (errno == eNOENT) -- mingwin (20001111) cunningly sets errno to ENOENT when it runs out of files
370 else throwErrno "getDirectoryContents"
373 If the operating system has a notion of current directories,
374 @getCurrentDirectory@ returns an absolute path to the
375 current directory of the calling process.
377 The operation may fail with:
379 \item @HardwareFault@
380 A physical I/O error has occurred.
382 \item @isDoesNotExistError@ / @NoSuchThing@
383 There is no path referring to the current directory.
384 @[EPERM, ENOENT, ESTALE...]@
385 \item @isPermissionError@ / @PermissionDenied@
386 The process has insufficient privileges to perform the operation.
388 \item @ResourceExhausted@
389 Insufficient resources are available to perform the operation.
390 \item @UnsupportedOperation@
391 The operating system has no notion of current directory.
395 getCurrentDirectory :: IO FilePath
396 getCurrentDirectory = do
397 p <- mallocBytes (#const PATH_MAX)
398 go p (#const PATH_MAX)
399 where go p bytes = do
400 p' <- getcwd p (fromIntegral bytes)
402 then do s <- peekCString p'
405 else do errno <- getErrno
407 then do let bytes' = bytes * 2
408 p' <- reallocBytes p bytes'
410 else throwErrno "getCurrentDirectory"
413 If the operating system has a notion of current directories,
414 @setCurrentDirectory dir@ changes the current
415 directory of the calling process to {\em dir}.
417 The operation may fail with:
419 \item @HardwareFault@
420 A physical I/O error has occurred.
422 \item @InvalidArgument@
423 The operand is not a valid directory name.
424 @[ENAMETOOLONG, ELOOP]@
425 \item @isDoesNotExistError@ / @NoSuchThing@
426 The directory does not exist.
428 \item @isPermissionError@ / @PermissionDenied@
429 The process has insufficient privileges to perform the operation.
431 \item @UnsupportedOperation@
432 The operating system has no notion of current directory, or the
433 current directory cannot be dynamically changed.
434 \item @InappropriateType@
435 The path refers to an existing non-directory object.
440 setCurrentDirectory :: FilePath -> IO ()
441 setCurrentDirectory path = do
442 withCString path $ \s ->
443 throwErrnoIfMinus1Retry_ "setCurrentDirectory" (chdir s)
444 -- ToDo: add path to error
447 To clarify, @doesDirectoryExist@ returns True if a file system object
448 exist, and it's a directory. @doesFileExist@ returns True if the file
449 system object exist, but it's not a directory (i.e., for every other
450 file system object that is not a directory.)
453 doesDirectoryExist :: FilePath -> IO Bool
454 doesDirectoryExist name =
456 (withFileStatus name $ \st -> isDirectory st)
457 (\ _ -> return False)
459 doesFileExist :: FilePath -> IO Bool
460 doesFileExist name = do
462 (withFileStatus name $ \st -> do b <- isDirectory st; return (not b))
463 (\ _ -> return False)
465 getModificationTime :: FilePath -> IO ClockTime
466 getModificationTime name =
467 withFileStatus name $ \ st ->
470 getPermissions :: FilePath -> IO Permissions
471 getPermissions name = do
472 withCString name $ \s -> do
473 read <- access s (#const R_OK)
474 write <- access s (#const W_OK)
475 exec <- access s (#const X_OK)
476 withFileStatus name $ \st -> do
477 is_dir <- isDirectory st
478 is_reg <- isRegularFile st
481 readable = read == 0,
482 writable = write == 0,
483 executable = not is_dir && exec == 0,
484 searchable = not is_reg && exec == 0
488 setPermissions :: FilePath -> Permissions -> IO ()
489 setPermissions name (Permissions r w e s) = do
491 read = if r then (#const S_IRUSR) else emptyCMode
492 write = if w then (#const S_IWUSR) else emptyCMode
493 exec = if e || s then (#const S_IXUSR) else emptyCMode
495 mode = read `unionCMode` (write `unionCMode` exec)
497 withCString name $ \s ->
498 throwErrnoIfMinus1_ "setPermissions" $ chmod s mode
500 withFileStatus :: FilePath -> (Ptr CStat -> IO a) -> IO a
501 withFileStatus name f = do
502 allocaBytes (#const sizeof(struct stat)) $ \p ->
503 withCString name $ \s -> do
504 throwErrnoIfMinus1Retry_ "withFileStatus" (stat s p)
507 withFileOrSymlinkStatus :: FilePath -> (Ptr CStat -> IO a) -> IO a
509 withFileOrSymlinkStatus name f = do
510 allocaBytes (#const sizeof(struct stat)) $ \p ->
511 withCString name $ \s -> do
512 throwErrnoIfMinus1Retry_ "withFileOrSymlinkStatus" (lstat s p)
515 withFileOrSymlinkStatus = withFileStatus
518 modificationTime :: Ptr CStat -> IO ClockTime
519 modificationTime stat = do
520 mtime <- (#peek struct stat, st_mtime) stat
521 return (TOD (toInteger (mtime :: CTime)) 0)
523 isDirectory :: Ptr CStat -> IO Bool
524 isDirectory stat = do
525 mode <- (#peek struct stat, st_mode) stat
526 return (s_ISDIR mode /= 0)
528 isRegularFile :: Ptr CStat -> IO Bool
529 isRegularFile stat = do
530 mode <- (#peek struct stat, st_mode) stat
531 return (s_ISREG mode /= 0)
533 foreign import ccall unsafe s_ISDIR :: CMode -> Int
534 #def inline HsInt s_ISDIR(m) {return S_ISDIR(m);}
536 foreign import ccall unsafe s_ISREG :: CMode -> Int
537 #def inline HsInt s_ISREG(m) {return S_ISREG(m);}
542 unionCMode :: CMode -> CMode -> CMode
545 #if defined(mingw32_TARGET_OS)
546 foreign import ccall unsafe mkdir :: CString -> IO CInt
548 foreign import ccall unsafe mkdir :: CString -> CInt -> IO CInt
551 foreign import ccall unsafe chmod :: CString -> CMode -> IO CInt
552 foreign import ccall unsafe access :: CString -> CMode -> IO CInt
553 foreign import ccall unsafe rmdir :: CString -> IO CInt
554 foreign import ccall unsafe chdir :: CString -> IO CInt
555 foreign import ccall unsafe getcwd :: Ptr CChar -> CInt -> IO (Ptr CChar)
556 foreign import ccall unsafe unlink :: CString -> IO CInt
557 foreign import ccall unsafe rename :: CString -> CString -> IO CInt
559 foreign import ccall unsafe opendir :: CString -> IO (Ptr CDir)
560 foreign import ccall unsafe readdir :: Ptr CDir -> IO (Ptr CDirent)
561 foreign import ccall unsafe closedir :: Ptr CDir -> IO CInt
563 foreign import ccall unsafe stat :: CString -> Ptr CStat -> IO CInt
566 foreign import ccall unsafe lstat :: CString -> Ptr CStat -> IO CInt