1 -- -----------------------------------------------------------------------------
2 -- $Id: Directory.hsc,v 1.1 2001/01/11 17:25:57 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(..) )
59 import PrelMarshalAlloc
73 -----------------------------------------------------------------------------
76 -- The @Permissions@ type is used to record whether certain
77 -- operations are permissible on a file/directory:
78 -- [to whom? - presumably the "current user"]
83 executable, searchable :: Bool
84 } deriving (Eq, Ord, Read, Show)
86 -----------------------------------------------------------------------------
89 -- @createDirectory dir@ creates a new directory {\em dir} which is
90 -- initially empty, or as near to empty as the operating system
93 -- The operation may fail with:
97 \item @isPermissionError@ / @PermissionDenied@
98 The process has insufficient privileges to perform the operation.
100 \item @isAlreadyExistsError@ / @AlreadyExists@
101 The operand refers to a directory that already exists.
103 \item @HardwareFault@
104 A physical I/O error has occurred.
106 \item @InvalidArgument@
107 The operand is not a valid directory name.
108 @[ENAMETOOLONG, ELOOP]@
110 There is no path to the directory.
112 \item @ResourceExhausted@
113 Insufficient resources (virtual memory, process file descriptors,
114 physical disk space, etc.) are available to perform the operation.
115 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
116 \item @InappropriateType@
117 The path refers to an existing non-directory object.
122 createDirectory :: FilePath -> IO ()
123 createDirectory path = do
124 withUnsafeCString path $ \s -> do
125 throwErrnoIfMinus1Retry_ "createDirectory" $
126 #if defined(mingw32_TARGET_OS)
133 @removeDirectory dir@ removes an existing directory {\em dir}. The
134 implementation may specify additional constraints which must be
135 satisfied before a directory can be removed (e.g. the directory has to
136 be empty, or may not be in use by other processes). It is not legal
137 for an implementation to partially remove a directory unless the
138 entire directory is removed. A conformant implementation need not
139 support directory removal in all situations (e.g. removal of the root
142 The operation may fail with:
144 \item @HardwareFault@
145 A physical I/O error has occurred.
147 \item @InvalidArgument@
148 The operand is not a valid directory name.
149 @[ENAMETOOLONG, ELOOP]@
150 \item @isDoesNotExist@ / @NoSuchThing@
151 The directory does not exist.
153 \item @isPermissionError@ / @PermissionDenied@
154 The process has insufficient privileges to perform the operation.
155 @[EROFS, EACCES, EPERM]@
156 \item @UnsatisfiedConstraints@
157 Implementation-dependent constraints are not satisfied.
158 @[EBUSY, ENOTEMPTY, EEXIST]@
159 \item @UnsupportedOperation@
160 The implementation does not support removal in this situation.
162 \item @InappropriateType@
163 The operand refers to an existing non-directory object.
168 removeDirectory :: FilePath -> IO ()
169 removeDirectory path = do
170 withUnsafeCString path $ \s ->
171 throwErrnoIfMinus1Retry_ "removeDirectory" (rmdir s)
174 @Removefile file@ removes the directory entry for an existing file
175 {\em file}, where {\em file} is not itself a directory. The
176 implementation may specify additional constraints which must be
177 satisfied before a file can be removed (e.g. the file may not be in
178 use by other processes).
180 The operation may fail with:
182 \item @HardwareFault@
183 A physical I/O error has occurred.
185 \item @InvalidArgument@
186 The operand is not a valid file name.
187 @[ENAMETOOLONG, ELOOP]@
188 \item @isDoesNotExist@ / @NoSuchThing@
189 The file does not exist.
191 \item @isPermissionError@ / @PermissionDenied@
192 The process has insufficient privileges to perform the operation.
193 @[EROFS, EACCES, EPERM]@
194 \item @UnsatisfiedConstraints@
195 Implementation-dependent constraints are not satisfied.
197 \item @InappropriateType@
198 The operand refers to an existing directory.
203 removeFile :: FilePath -> IO ()
205 withUnsafeCString path $ \s ->
206 throwErrnoIfMinus1Retry_ "removeFile" (unlink s)
209 @renameDirectory old@ {\em new} changes the name of an existing
210 directory from {\em old} to {\em new}. If the {\em new} directory
211 already exists, it is atomically replaced by the {\em old} directory.
212 If the {\em new} directory is neither the {\em old} directory nor an
213 alias of the {\em old} directory, it is removed as if by
214 $removeDirectory$. A conformant implementation need not support
215 renaming directories in all situations (e.g. renaming to an existing
216 directory, or across different physical devices), but the constraints
219 The operation may fail with:
221 \item @HardwareFault@
222 A physical I/O error has occurred.
224 \item @InvalidArgument@
225 Either operand is not a valid directory name.
226 @[ENAMETOOLONG, ELOOP]@
227 \item @isDoesNotExistError@ / @NoSuchThing@
228 The original directory does not exist, or there is no path to the target.
230 \item @isPermissionError@ / @PermissionDenied@
231 The process has insufficient privileges to perform the operation.
232 @[EROFS, EACCES, EPERM]@
233 \item @ResourceExhausted@
234 Insufficient resources are available to perform the operation.
235 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
236 \item @UnsatisfiedConstraints@
237 Implementation-dependent constraints are not satisfied.
238 @[EBUSY, ENOTEMPTY, EEXIST]@
239 \item @UnsupportedOperation@
240 The implementation does not support renaming in this situation.
242 \item @InappropriateType@
243 Either path refers to an existing non-directory object.
248 renameDirectory :: FilePath -> FilePath -> IO ()
249 renameDirectory opath npath =
250 withFileStatus opath $ \st -> do
251 is_dir <- isDirectory st
253 then ioException (IOError Nothing InappropriateType "renameDirectory"
254 ("not a directory") (Just opath))
257 withUnsafeCString opath $ \s1 ->
258 withUnsafeCString npath $ \s2 ->
259 throwErrnoIfMinus1Retry_ "renameDirectory" (rename s1 s2)
262 @renameFile@ {\em old} {\em new} changes the name of an existing file system
263 object from {\em old} to {\em new}. If the {\em new} object already
264 exists, it is atomically replaced by the {\em old} object. Neither
265 path may refer to an existing directory. A conformant implementation
266 need not support renaming files in all situations (e.g. renaming
267 across different physical devices), but the constraints must be
270 The operation may fail with:
272 \item @HardwareFault@
273 A physical I/O error has occurred.
275 \item @InvalidArgument@
276 Either operand is not a valid file name.
277 @[ENAMETOOLONG, ELOOP]@
278 \item @isDoesNotExistError@ / @NoSuchThing@
279 The original file does not exist, or there is no path to the target.
281 \item @isPermissionError@ / @PermissionDenied@
282 The process has insufficient privileges to perform the operation.
283 @[EROFS, EACCES, EPERM]@
284 \item @ResourceExhausted@
285 Insufficient resources are available to perform the operation.
286 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
287 \item @UnsatisfiedConstraints@
288 Implementation-dependent constraints are not satisfied.
290 \item @UnsupportedOperation@
291 The implementation does not support renaming in this situation.
293 \item @InappropriateType@
294 Either path refers to an existing directory.
295 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
299 renameFile :: FilePath -> FilePath -> IO ()
300 renameFile opath npath =
301 withFileStatus opath $ \st -> do
302 is_dir <- isDirectory st
304 then ioException (IOError Nothing InappropriateType "renameFile"
305 "is a directory" (Just opath))
308 withUnsafeCString opath $ \s1 ->
309 withUnsafeCString npath $ \s2 ->
310 throwErrnoIfMinus1Retry_ "renameFile" (rename s1 s2)
313 @getDirectoryContents dir@ returns a list of {\em all} entries
316 The operation may fail with:
318 \item @HardwareFault@
319 A physical I/O error has occurred.
321 \item @InvalidArgument@
322 The operand is not a valid directory name.
323 @[ENAMETOOLONG, ELOOP]@
324 \item @isDoesNotExistError@ / @NoSuchThing@
325 The directory does not exist.
327 \item @isPermissionError@ / @PermissionDenied@
328 The process has insufficient privileges to perform the operation.
330 \item @ResourceExhausted@
331 Insufficient resources are available to perform the operation.
333 \item @InappropriateType@
334 The path refers to an existing non-directory object.
339 getDirectoryContents :: FilePath -> IO [FilePath]
340 getDirectoryContents path = do
341 p <- withUnsafeCString path $ \s ->
342 throwErrnoIfNullRetry "getDirectoryContents" (opendir s)
345 loop :: Ptr CDir -> IO [String]
349 then do entry <- peekCString ((#ptr struct dirent,d_name) p)
351 return (entry:entries)
352 else do errno <- getErrno
353 if (errno == eINTR) then loop dir else do
354 throwErrnoIfMinus1_ "getDirectoryContents" $ closedir dir
355 if (isValidErrno errno) -- EOF
356 then throwErrno "getDirectoryContents"
360 If the operating system has a notion of current directories,
361 @getCurrentDirectory@ returns an absolute path to the
362 current directory of the calling process.
364 The operation may fail with:
366 \item @HardwareFault@
367 A physical I/O error has occurred.
369 \item @isDoesNotExistError@ / @NoSuchThing@
370 There is no path referring to the current directory.
371 @[EPERM, ENOENT, ESTALE...]@
372 \item @isPermissionError@ / @PermissionDenied@
373 The process has insufficient privileges to perform the operation.
375 \item @ResourceExhausted@
376 Insufficient resources are available to perform the operation.
377 \item @UnsupportedOperation@
378 The operating system has no notion of current directory.
382 getCurrentDirectory :: IO FilePath
383 getCurrentDirectory = do
384 p <- mallocBytes (#const PATH_MAX)
385 go p (#const PATH_MAX)
386 where go p bytes = do
387 p' <- getcwd p (fromIntegral bytes)
389 then do s <- peekCString p'
392 else do errno <- getErrno
394 then do let bytes' = bytes * 2
395 p' <- reallocBytes p bytes'
397 else throwErrno "getCurrentDirectory"
400 If the operating system has a notion of current directories,
401 @setCurrentDirectory dir@ changes the current
402 directory of the calling process to {\em dir}.
404 The operation may fail with:
406 \item @HardwareFault@
407 A physical I/O error has occurred.
409 \item @InvalidArgument@
410 The operand is not a valid directory name.
411 @[ENAMETOOLONG, ELOOP]@
412 \item @isDoesNotExistError@ / @NoSuchThing@
413 The directory does not exist.
415 \item @isPermissionError@ / @PermissionDenied@
416 The process has insufficient privileges to perform the operation.
418 \item @UnsupportedOperation@
419 The operating system has no notion of current directory, or the
420 current directory cannot be dynamically changed.
421 \item @InappropriateType@
422 The path refers to an existing non-directory object.
427 setCurrentDirectory :: FilePath -> IO ()
428 setCurrentDirectory path = do
429 withUnsafeCString path $ \s ->
430 throwErrnoIfMinus1Retry_ "setCurrentDirectory" (chdir s)
431 -- ToDo: add path to error
434 To clarify, @doesDirectoryExist@ returns True if a file system object
435 exist, and it's a directory. @doesFileExist@ returns True if the file
436 system object exist, but it's not a directory (i.e., for every other
437 file system object that is not a directory.)
440 doesDirectoryExist :: FilePath -> IO Bool
441 doesDirectoryExist name =
443 (withFileStatus name $ \st -> isDirectory st)
444 (\ _ -> return False)
446 doesFileExist :: FilePath -> IO Bool
447 doesFileExist name = do
449 (withFileStatus name $ \st -> do b <- isDirectory st; return (not b))
450 (\ _ -> return False)
452 getModificationTime :: FilePath -> IO ClockTime
453 getModificationTime name =
454 withFileStatus name $ \ st ->
457 getPermissions :: FilePath -> IO Permissions
458 getPermissions name = do
459 withUnsafeCString name $ \s -> do
460 read <- access s (#const R_OK)
461 write <- access s (#const W_OK)
462 exec <- access s (#const X_OK)
463 withFileStatus name $ \st -> do
464 is_dir <- isDirectory st
465 is_reg <- isRegularFile st
468 readable = read == 0,
469 writable = write == 0,
470 executable = not is_dir && exec == 0,
471 searchable = not is_reg && exec == 0
475 setPermissions :: FilePath -> Permissions -> IO ()
476 setPermissions name (Permissions r w e s) = do
478 read = if r then (#const S_IRUSR) else emptyCMode
479 write = if w then (#const S_IWUSR) else emptyCMode
480 exec = if e || s then (#const S_IXUSR) else emptyCMode
482 mode = read `unionCMode` (write `unionCMode` exec)
484 withUnsafeCString name $ \s ->
485 throwErrnoIfMinus1_ "setPermissions" $ chmod s mode
487 withFileStatus :: FilePath -> (Ptr CStat -> IO a) -> IO a
488 withFileStatus name f = do
489 allocaBytes (#const sizeof(struct stat)) $ \p ->
490 withUnsafeCString name $ \s -> do
491 throwErrnoIfMinus1Retry_ "withFileStatus" (stat s p)
494 modificationTime :: Ptr CStat -> IO ClockTime
495 modificationTime stat = do
496 mtime <- (#peek struct stat, st_mtime) stat
497 return (TOD (toInteger (mtime :: CTime)) 0)
499 isDirectory :: Ptr CStat -> IO Bool
500 isDirectory stat = do
501 mode <- (#peek struct stat, st_mode) stat
502 return (s_ISDIR mode /= 0)
504 isRegularFile :: Ptr CStat -> IO Bool
505 isRegularFile stat = do
506 mode <- (#peek struct stat, st_mode) stat
507 return (s_ISREG mode /= 0)
509 foreign import ccall unsafe s_ISDIR :: CMode -> Int
510 #def inline HsInt s_ISDIR(m) {return S_ISDIR(m);}
512 foreign import ccall unsafe s_ISREG :: CMode -> Int
513 #def inline HsInt s_ISREG(m) {return S_ISREG(m);}
518 unionCMode :: CMode -> CMode -> CMode
521 type UCString = UnsafeCString
523 #if defined(mingw32_TARGET_OS)
524 foreign import ccall unsafe mkdir :: UCString -> IO CInt
526 foreign import ccall unsafe mkdir :: UCString -> CInt -> IO CInt
529 foreign import ccall unsafe chmod :: UCString -> CMode -> IO CInt
530 foreign import ccall unsafe access :: UCString -> CMode -> IO CInt
531 foreign import ccall unsafe rmdir :: UCString -> IO CInt
532 foreign import ccall unsafe chdir :: UCString -> IO CInt
533 foreign import ccall unsafe getcwd :: Ptr CChar -> CInt -> IO (Ptr CChar)
534 foreign import ccall unsafe unlink :: UCString -> IO CInt
535 foreign import ccall unsafe rename :: UCString -> UCString -> IO CInt
537 foreign import ccall unsafe opendir :: UCString -> IO (Ptr CDir)
538 foreign import ccall unsafe readdir :: Ptr CDir -> IO (Ptr CDirent)
539 foreign import ccall unsafe closedir :: Ptr CDir -> IO CInt
541 foreign import ccall unsafe stat :: UCString -> Ptr CStat -> IO CInt