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