[project @ 1999-12-20 10:34:27 by simonpj]
[ghc-hetmet.git] / ghc / lib / std / Directory.lhs
1 %
2 % (c) The AQUA Project, Glasgow University, 1994-1999
3 %
4 \section[Directory]{Directory interface}
5
6 A directory contains a series of entries, each of which is a named
7 reference to a file system object (file, directory etc.).  Some
8 entries may be hidden, inaccessible, or have some administrative
9 function (e.g. "." or ".." under POSIX), but in this standard all such
10 entries are considered to form part of the directory contents.
11 Entries in sub-directories are not, however, considered to form part
12 of the directory contents.
13
14 Each file system object is referenced by a {\em path}.  There is
15 normally at least one absolute path to each file system object.  In
16 some operating systems, it may also be possible to have paths which
17 are relative to the current directory.
18
19 \begin{code}
20 {-# OPTIONS -#include <sys/stat.h> -#include <dirent.h> -#include "cbits/stgio.h" #-}
21 module Directory 
22    ( 
23       Permissions               -- abstract
24       
25     , readable                  -- :: Permissions -> Bool
26     , writable                  -- :: Permissions -> Bool
27     , executable                -- :: Permissions -> Bool
28     , searchable                -- :: Permissions -> Bool
29
30     , createDirectory           -- :: FilePath -> IO ()
31     , removeDirectory           -- :: FilePath -> IO ()
32     , renameDirectory           -- :: FilePath -> FilePath -> IO ()
33
34     , getDirectoryContents      -- :: FilePath -> IO [FilePath]
35     , getCurrentDirectory       -- :: IO FilePath
36     , setCurrentDirectory       -- :: FilePath -> IO ()
37
38     , removeFile                -- :: FilePath -> IO ()
39     , renameFile                -- :: FilePath -> FilePath -> IO ()
40
41     , doesFileExist             -- :: FilePath -> IO Bool
42     , doesDirectoryExist        -- :: FilePath -> IO Bool
43
44     , getPermissions            -- :: FilePath -> IO Permissions
45     , setPermissions            -- :: FilePath -> Permissions -> IO ()
46
47
48 #ifndef __HUGS__
49     , getModificationTime       -- :: FilePath -> IO ClockTime
50 #endif
51    ) where
52
53 #ifdef __HUGS__
54 --import PreludeBuiltin
55 #else
56
57 import Prelude          -- Just to get it in the dependencies
58
59 import PrelGHC          ( RealWorld, int2Word#, or#, and# )
60 import PrelByteArr      ( ByteArray, MutableByteArray,
61                           newWordArray, readWordArray, newCharArray,
62                           unsafeFreezeByteArray
63                         )
64 import PrelPack         ( unpackNBytesST, packString, unpackCStringST )
65 import PrelIOBase       ( stToIO,
66                           constructErrorAndFail, constructErrorAndFailWithInfo,
67                           IOError(IOError), IOErrorType(SystemError) )
68 import Time             ( ClockTime(..) )
69 import PrelAddr         ( Addr, nullAddr, Word(..), wordToInt )
70 #endif
71
72 \end{code}
73
74 %*********************************************************
75 %*                                                      *
76 \subsection{Permissions}
77 %*                                                      *
78 %*********************************************************
79
80 The @Permissions@ type is used to record whether certain
81 operations are permissible on a file/directory:
82 [to whom? - owner/group/world - the Report don't say much]
83
84 \begin{code}
85 data Permissions
86  = Permissions {
87     readable,   writable, 
88     executable, searchable :: Bool 
89    } deriving (Eq, Ord, Read, Show)
90 \end{code}
91
92 %*********************************************************
93 %*                                                      *
94 \subsection{Implementation}
95 %*                                                      *
96 %*********************************************************
97
98 @createDirectory dir@ creates a new directory {\em dir} which is
99 initially empty, or as near to empty as the operating system
100 allows.
101
102 The operation may fail with:
103
104 \begin{itemize}
105 \item @isPermissionError@ / @PermissionDenied@
106 The process has insufficient privileges to perform the operation.
107 @[EROFS, EACCES]@
108 \item @isAlreadyExistsError@ / @AlreadyExists@
109 The operand refers to a directory that already exists.  
110 @ [EEXIST]@
111 \item @HardwareFault@
112 A physical I/O error has occurred.
113 @ [EIO]@
114 \item @InvalidArgument@
115 The operand is not a valid directory name.
116 @[ENAMETOOLONG, ELOOP]@
117 \item @NoSuchThing@
118 There is no path to the directory. 
119 @[ENOENT, ENOTDIR]@
120 \item @ResourceExhausted@
121 Insufficient resources (virtual memory, process file descriptors,
122 physical disk space, etc.) are available to perform the operation.
123 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
124 \item @InappropriateType@
125 The path refers to an existing non-directory object.
126 @[EEXIST]@
127 \end{itemize}
128
129 \begin{code}
130 createDirectory :: FilePath -> IO ()
131 createDirectory path = do
132     rc <- primCreateDirectory (primPackString path)
133     if rc == 0 then return () else
134         constructErrorAndFailWithInfo "createDirectory" path
135 \end{code}
136
137 @removeDirectory dir@ removes an existing directory {\em dir}.  The
138 implementation may specify additional constraints which must be
139 satisfied before a directory can be removed (e.g. the directory has to
140 be empty, or may not be in use by other processes).  It is not legal
141 for an implementation to partially remove a directory unless the
142 entire directory is removed. A conformant implementation need not
143 support directory removal in all situations (e.g. removal of the root
144 directory).
145
146 The operation may fail with:
147 \begin{itemize}
148 \item @HardwareFault@
149 A physical I/O error has occurred.
150 [@EIO@]
151 \item @InvalidArgument@
152 The operand is not a valid directory name.
153 @[ENAMETOOLONG, ELOOP]@
154 \item @isDoesNotExist@ / @NoSuchThing@
155 The directory does not exist. 
156 @[ENOENT, ENOTDIR]@
157 \item @isPermissionError@ / @PermissionDenied@
158 The process has insufficient privileges to perform the operation.
159 @[EROFS, EACCES, EPERM]@
160 \item @UnsatisfiedConstraints@
161 Implementation-dependent constraints are not satisfied.  
162 @[EBUSY, ENOTEMPTY, EEXIST]@
163 \item @UnsupportedOperation@
164 The implementation does not support removal in this situation.
165 @[EINVAL]@
166 \item @InappropriateType@
167 The operand refers to an existing non-directory object.
168 @[ENOTDIR]@
169 \end{itemize}
170
171 \begin{code}
172 removeDirectory :: FilePath -> IO ()
173 removeDirectory path = do
174     rc <- primRemoveDirectory (primPackString path)
175     if rc == 0 then 
176         return ()
177      else 
178         constructErrorAndFailWithInfo "removeDirectory" path
179 \end{code}
180
181 @removeFile file@ removes the directory entry for an existing file
182 {\em file}, where {\em file} is not itself a directory. The
183 implementation may specify additional constraints which must be
184 satisfied before a file can be removed (e.g. the file may not be in
185 use by other processes).
186
187 The operation may fail with:
188 \begin{itemize}
189 \item @HardwareFault@
190 A physical I/O error has occurred.
191 @[EIO]@
192 \item @InvalidArgument@
193 The operand is not a valid file name.
194 @[ENAMETOOLONG, ELOOP]@
195 \item @isDoesNotExist@ / @NoSuchThing@
196 The file does not exist. 
197 @[ENOENT, ENOTDIR]@
198 \item @isPermissionError@ / @PermissionDenied@
199 The process has insufficient privileges to perform the operation.
200 @[EROFS, EACCES, EPERM]@
201 \item @UnsatisfiedConstraints@
202 Implementation-dependent constraints are not satisfied.  
203 @[EBUSY]@
204 \item @InappropriateType@
205 The operand refers to an existing directory.
206 @[EPERM, EINVAL]@
207 \end{itemize}
208
209 \begin{code}
210 removeFile :: FilePath -> IO ()
211 removeFile path = do
212     rc <- primRemoveFile (primPackString path)
213     if rc == 0 then
214         return ()
215      else
216         constructErrorAndFailWithInfo "removeFile" path
217 \end{code}
218
219 @renameDirectory old@ {\em new} changes the name of an existing
220 directory from {\em old} to {\em new}.  If the {\em new} directory
221 already exists, it is atomically replaced by the {\em old} directory.
222 If the {\em new} directory is neither the {\em old} directory nor an
223 alias of the {\em old} directory, it is removed as if by
224 $removeDirectory$.  A conformant implementation need not support
225 renaming directories in all situations (e.g. renaming to an existing
226 directory, or across different physical devices), but the constraints
227 must be documented.
228
229 The operation may fail with:
230 \begin{itemize}
231 \item @HardwareFault@
232 A physical I/O error has occurred.
233 @[EIO]@
234 \item @InvalidArgument@
235 Either operand is not a valid directory name.
236 @[ENAMETOOLONG, ELOOP]@
237 \item @isDoesNotExistError@ / @NoSuchThing@
238 The original directory does not exist, or there is no path to the target.
239 @[ENOENT, ENOTDIR]@
240 \item @isPermissionError@ / @PermissionDenied@
241 The process has insufficient privileges to perform the operation.
242 @[EROFS, EACCES, EPERM]@
243 \item @ResourceExhausted@
244 Insufficient resources are available to perform the operation.  
245 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
246 \item @UnsatisfiedConstraints@
247 Implementation-dependent constraints are not satisfied.
248 @[EBUSY, ENOTEMPTY, EEXIST]@
249 \item @UnsupportedOperation@
250 The implementation does not support renaming in this situation.
251 @[EINVAL, EXDEV]@
252 \item @InappropriateType@
253 Either path refers to an existing non-directory object.
254 @[ENOTDIR, EISDIR]@
255 \end{itemize}
256
257 \begin{code}
258 renameDirectory :: FilePath -> FilePath -> IO ()
259 renameDirectory opath npath = do
260     rc <- primRenameDirectory (primPackString opath) (primPackString npath)
261     if rc == 0 then
262         return ()
263      else
264         constructErrorAndFailWithInfo "renameDirectory" ("old: " ++ opath ++ ",new: " ++ npath)
265 \end{code}
266
267 @renameFile old@ {\em new} changes the name of an existing file system
268 object from {\em old} to {\em new}.  If the {\em new} object already
269 exists, it is atomically replaced by the {\em old} object.  Neither
270 path may refer to an existing directory.  A conformant implementation
271 need not support renaming files in all situations (e.g. renaming
272 across different physical devices), but the constraints must be
273 documented.
274
275 The operation may fail with:
276 \begin{itemize}
277 \item @HardwareFault@
278 A physical I/O error has occurred.
279 @[EIO]@
280 \item @InvalidArgument@
281 Either operand is not a valid file name.
282 @[ENAMETOOLONG, ELOOP]@
283 \item @isDoesNotExistError@ / @NoSuchThing@
284 The original file does not exist, or there is no path to the target.
285 @[ENOENT, ENOTDIR]@
286 \item @isPermissionError@ / @PermissionDenied@
287 The process has insufficient privileges to perform the operation.
288 @[EROFS, EACCES, EPERM]@
289 \item @ResourceExhausted@
290 Insufficient resources are available to perform the operation.  
291 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
292 \item @UnsatisfiedConstraints@
293 Implementation-dependent constraints are not satisfied.
294 @[EBUSY]@
295 \item @UnsupportedOperation@
296 The implementation does not support renaming in this situation.
297 @[EXDEV]@
298 \item @InappropriateType@
299 Either path refers to an existing directory.
300 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
301 \end{itemize}
302
303 \begin{code}
304 renameFile :: FilePath -> FilePath -> IO ()
305 renameFile opath npath = do
306     rc <- primRenameFile (primPackString opath) (primPackString npath)
307     if rc == 0 then
308         return ()
309      else
310         constructErrorAndFailWithInfo   "renameFile" opath
311 \end{code}
312
313 @getDirectoryContents dir@ returns a list of {\em all} entries
314 in {\em dir}. 
315
316 The operation may fail with:
317 \begin{itemize}
318 \item @HardwareFault@
319 A physical I/O error has occurred.
320 @[EIO]@
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.
326 @[ENOENT, ENOTDIR]@
327 \item @isPermissionError@ / @PermissionDenied@
328 The process has insufficient privileges to perform the operation.
329 @[EACCES]@
330 \item @ResourceExhausted@
331 Insufficient resources are available to perform the operation.
332 @[EMFILE, ENFILE]@
333 \item @InappropriateType@
334 The path refers to an existing non-directory object.
335 @[ENOTDIR]@
336 \end{itemize}
337
338 \begin{code}
339 getDirectoryContents :: FilePath -> IO [FilePath]
340 getDirectoryContents path = do
341     dir <- primOpenDir (primPackString path)
342     if dir == nullAddr
343         then constructErrorAndFailWithInfo "getDirectoryContents" path
344         else loop dir
345   where
346     loop :: Addr -> IO [String]
347     loop dir  = do
348       dirent_ptr <- primReadDir dir
349       if dirent_ptr == nullAddr
350        then do
351           -- readDir__ implicitly performs closedir() when the
352           -- end is reached.
353           return [] 
354        else do
355           str     <- primGetDirentDName dirent_ptr
356           entry   <- primUnpackCString str
357           entries <- loop dir
358           return (entry:entries)
359 \end{code}
360
361 If the operating system has a notion of current directories,
362 @getCurrentDirectory@ returns an absolute path to the
363 current directory of the calling process.
364
365 The operation may fail with:
366 \begin{itemize}
367 \item @HardwareFault@
368 A physical I/O error has occurred.
369 @[EIO]@
370 \item @isDoesNotExistError@ / @NoSuchThing@
371 There is no path referring to the current directory.
372 @[EPERM, ENOENT, ESTALE...]@
373 \item @isPermissionError@ / @PermissionDenied@
374 The process has insufficient privileges to perform the operation.
375 @[EACCES]@
376 \item @ResourceExhausted@
377 Insufficient resources are available to perform the operation.
378 \item @UnsupportedOperation@
379 The operating system has no notion of current directory.
380 \end{itemize}
381
382 \begin{code}
383 getCurrentDirectory :: IO FilePath
384 getCurrentDirectory = do
385     str <- primGetCurrentDirectory
386     if str /= nullAddr
387         then do
388             pwd <- primUnpackCString str
389             primFree str
390             return pwd
391         else
392             constructErrorAndFail "getCurrentDirectory"
393 \end{code}
394
395 If the operating system has a notion of current directories,
396 @setCurrentDirectory dir@ changes the current
397 directory of the calling process to {\em dir}.
398
399 The operation may fail with:
400 \begin{itemize}
401 \item @HardwareFault@
402 A physical I/O error has occurred.
403 @[EIO]@
404 \item @InvalidArgument@
405 The operand is not a valid directory name.
406 @[ENAMETOOLONG, ELOOP]@
407 \item @isDoesNotExistError@ / @NoSuchThing@
408 The directory does not exist.
409 @[ENOENT, ENOTDIR]@
410 \item @isPermissionError@ / @PermissionDenied@
411 The process has insufficient privileges to perform the operation.
412 @[EACCES]@
413 \item @UnsupportedOperation@
414 The operating system has no notion of current directory, or the
415 current directory cannot be dynamically changed.
416 \item @InappropriateType@
417 The path refers to an existing non-directory object.
418 @[ENOTDIR]@
419 \end{itemize}
420
421 \begin{code}
422 setCurrentDirectory :: FilePath -> IO ()
423 setCurrentDirectory path = do
424     rc <- primSetCurrentDirectory (primPackString path)
425     if rc == 0 
426         then return ()
427         else constructErrorAndFailWithInfo "setCurrentDirectory" path
428 \end{code}
429
430 To clarify, @doesDirectoryExist@ returns True if a file system object
431 exist, and it's a directory. @doesFileExist@ returns True if the file
432 system object exist, but it's not a directory (i.e., for every other 
433 file system object that is not a directory.) 
434
435 \begin{code}
436 doesDirectoryExist :: FilePath -> IO Bool
437 doesDirectoryExist name = 
438  catch
439    (getFileStatus name >>= \ st -> return (isDirectory st))
440    (\ _ -> return False)
441
442 doesFileExist :: FilePath -> IO Bool
443 doesFileExist name = do 
444  catch
445    (getFileStatus name >>= \ st -> return (not (isDirectory st)))
446    (\ _ -> return False)
447
448 foreign import ccall "libHS_cbits.so" "const_F_OK" unsafe const_F_OK  :: Int
449
450 #ifndef __HUGS__
451 getModificationTime :: FilePath -> IO ClockTime
452 getModificationTime name =
453  getFileStatus name >>= \ st ->
454  modificationTime st
455 #endif
456
457 getPermissions :: FilePath -> IO Permissions
458 getPermissions name = do
459   st <- getFileStatus name
460   let
461    fm = fileMode st
462    isect v = intersectFileMode v fm == v
463
464   return (
465     Permissions {
466       readable   = isect ownerReadMode,
467       writable   = isect ownerWriteMode,
468       executable = not (isDirectory st)   && isect ownerExecuteMode,
469       searchable = not (isRegularFile st) && isect ownerExecuteMode
470     }
471    )
472
473 setPermissions :: FilePath -> Permissions -> IO ()
474 setPermissions name (Permissions r w e s) = do
475     let
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
479
480      mode  = read `unionFileMode` (write `unionFileMode` exec)
481
482     rc <- primChmod (primPackString name) mode
483     if rc == 0
484         then return ()
485         else ioError (IOError Nothing SystemError "setPermissions" "insufficient permissions")
486 \end{code}
487
488 (Sigh)..copied from Posix.Files to avoid dep. on posix library
489
490 \begin{code}
491 type FileStatus = PrimByteArray
492
493 getFileStatus :: FilePath -> IO FileStatus
494 getFileStatus name = do
495     bytes <- primNewByteArray sizeof_stat
496     rc <- primStat (primPackString name) bytes
497     if rc == 0 
498 #ifdef __HUGS__
499         then primUnsafeFreezeByteArray bytes
500 #else
501         then stToIO (unsafeFreezeByteArray bytes)
502 #endif
503         else ioError (IOError Nothing SystemError "getFileStatus" "")
504
505 #ifndef __HUGS__
506 modificationTime :: FileStatus -> IO ClockTime
507 modificationTime stat = do
508     i1 <- stToIO (newWordArray (0,1))
509     setFileMode i1 stat
510     secs <- stToIO (readWordArray i1 0)
511     return (TOD (toInteger (wordToInt secs)) 0)
512
513 foreign import ccall "libHS_cbits.so" "set_stat_st_mtime" unsafe
514    setFileMode :: PrimMutableByteArray RealWorld -> FileStatus -> IO ()
515 #endif
516
517 isDirectory :: FileStatus -> Bool
518 isDirectory stat = prim_S_ISDIR (fileMode stat) /= 0
519
520 isRegularFile :: FileStatus -> Bool
521 isRegularFile stat = prim_S_ISREG (fileMode stat) /= 0
522
523 foreign import ccall "libHS_cbits.so" "sizeof_stat" unsafe sizeof_stat :: Int
524 foreign import ccall "libHS_cbits.so" "prim_stat"   unsafe
525   primStat :: PrimByteArray -> PrimMutableByteArray RealWorld -> IO Int
526
527 foreign import ccall "libHS_cbits.so" "get_stat_st_mode" unsafe fileMode     :: FileStatus -> FileMode
528 foreign import ccall "libHS_cbits.so" "prim_S_ISDIR"     unsafe prim_S_ISDIR :: FileMode -> Int
529 foreign import ccall "libHS_cbits.so" "prim_S_ISREG"     unsafe prim_S_ISREG :: FileMode -> Int
530 \end{code}
531
532 \begin{code}
533 type FileMode = Word
534
535 emptyFileMode     :: FileMode
536 unionFileMode     :: FileMode -> FileMode -> FileMode
537 intersectFileMode :: FileMode -> FileMode -> FileMode
538
539 foreign import ccall "libHS_cbits.so" "const_S_IRUSR" unsafe ownerReadMode    :: FileMode
540 foreign import ccall "libHS_cbits.so" "const_S_IWUSR" unsafe ownerWriteMode   :: FileMode
541 foreign import ccall "libHS_cbits.so" "const_S_IXUSR" unsafe ownerExecuteMode :: FileMode
542
543 #ifdef __HUGS__
544 emptyFileMode     = primIntToWord 0
545 unionFileMode     = primOrWord
546 intersectFileMode = primAndWord
547 #else
548 --ToDo: tidy up.
549 emptyFileMode     = W# (int2Word# 0#)
550 unionFileMode     = orWord
551 intersectFileMode = andWord
552 #endif
553
554 \end{code}
555
556 Some defns. to allow us to share code.
557
558 \begin{code}
559 #ifndef __HUGS__
560
561 primPackString :: [Char] -> ByteArray Int
562 primPackString    = packString
563 --ToDo: fix.
564 primUnpackCString :: Addr -> IO String
565 primUnpackCString a = stToIO (unpackCStringST a)
566
567 type PrimByteArray = ByteArray Int
568 type PrimMutableByteArray s = MutableByteArray RealWorld Int
569 type CString = PrimByteArray
570
571 orWord, andWord :: Word -> Word -> Word
572 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
573 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
574
575 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
576 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
577 #endif
578
579 foreign import ccall "libHS_cbits.so" "createDirectory"         unsafe primCreateDirectory     :: CString -> IO Int
580 foreign import ccall "libHS_cbits.so" "removeDirectory"         unsafe primRemoveDirectory     :: CString -> IO Int
581 foreign import ccall "libHS_cbits.so" "removeFile"              unsafe primRemoveFile          :: CString -> IO Int
582 foreign import ccall "libHS_cbits.so" "renameDirectory"         unsafe primRenameDirectory     :: CString -> CString -> IO Int
583 foreign import ccall "libHS_cbits.so" "renameFile"              unsafe primRenameFile          :: CString -> CString -> IO Int
584 foreign import ccall "libHS_cbits.so" "openDir__"               unsafe primOpenDir      :: CString -> IO Addr
585 foreign import ccall "libHS_cbits.so" "readDir__"               unsafe primReadDir      :: Addr -> IO Addr
586 foreign import ccall "libHS_cbits.so" "get_dirent_d_name"   unsafe primGetDirentDName      :: Addr -> IO Addr
587 foreign import ccall "libHS_cbits.so" "setCurrentDirectory" unsafe primSetCurrentDirectory :: CString -> IO Int
588 foreign import ccall "libHS_cbits.so" "getCurrentDirectory" unsafe primGetCurrentDirectory :: IO Addr
589 foreign import ccall "libc.so.6"        "free"                unsafe primFree                :: Addr -> IO ()
590 foreign import ccall "libc.so.6"        "malloc"              unsafe primMalloc              :: Word -> IO Addr
591 foreign import ccall "libc.so.6"        "chmod"               unsafe primChmod               :: CString -> Word -> IO Int
592 \end{code}
593