[project @ 2000-08-24 10:27:01 by simonmar]
[ghc-hetmet.git] / ghc / lib / std / Directory.lhs
1 % -----------------------------------------------------------------------------
2 % $Id: Directory.lhs,v 1.20 2000/08/24 10:27:01 simonmar Exp $
3 %
4 % (c) The University of Glasgow, 1994-2000
5 %
6
7 \section[Directory]{Directory interface}
8
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.
16
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.
21
22 \begin{code}
23 {-# OPTIONS -#include <sys/stat.h> -#include <dirent.h> -#include "cbits/stgio.h" #-}
24 module Directory 
25    ( 
26       Permissions               -- abstract
27       
28     , readable                  -- :: Permissions -> Bool
29     , writable                  -- :: Permissions -> Bool
30     , executable                -- :: Permissions -> Bool
31     , searchable                -- :: Permissions -> Bool
32
33     , createDirectory           -- :: FilePath -> IO ()
34     , removeDirectory           -- :: FilePath -> IO ()
35     , renameDirectory           -- :: FilePath -> FilePath -> IO ()
36
37     , getDirectoryContents      -- :: FilePath -> IO [FilePath]
38     , getCurrentDirectory       -- :: IO FilePath
39     , setCurrentDirectory       -- :: FilePath -> IO ()
40
41     , removeFile                -- :: FilePath -> IO ()
42     , renameFile                -- :: FilePath -> FilePath -> IO ()
43
44     , doesFileExist             -- :: FilePath -> IO Bool
45     , doesDirectoryExist        -- :: FilePath -> IO Bool
46
47     , getPermissions            -- :: FilePath -> IO Permissions
48     , setPermissions            -- :: FilePath -> Permissions -> IO ()
49
50
51 #ifndef __HUGS__
52     , getModificationTime       -- :: FilePath -> IO ClockTime
53 #endif
54    ) where
55
56 #ifdef __HUGS__
57 --import PreludeBuiltin
58 #else
59
60 import Prelude          -- Just to get it in the dependencies
61
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 )
72 #endif
73
74 \end{code}
75
76 %*********************************************************
77 %*                                                      *
78 \subsection{Permissions}
79 %*                                                      *
80 %*********************************************************
81
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]
85
86 \begin{code}
87 data Permissions
88  = Permissions {
89     readable,   writable, 
90     executable, searchable :: Bool 
91    } deriving (Eq, Ord, Read, Show)
92 \end{code}
93
94 %*********************************************************
95 %*                                                      *
96 \subsection{Implementation}
97 %*                                                      *
98 %*********************************************************
99
100 @createDirectory dir@ creates a new directory {\em dir} which is
101 initially empty, or as near to empty as the operating system
102 allows.
103
104 The operation may fail with:
105
106 \begin{itemize}
107 \item @isPermissionError@ / @PermissionDenied@
108 The process has insufficient privileges to perform the operation.
109 @[EROFS, EACCES]@
110 \item @isAlreadyExistsError@ / @AlreadyExists@
111 The operand refers to a directory that already exists.  
112 @ [EEXIST]@
113 \item @HardwareFault@
114 A physical I/O error has occurred.
115 @ [EIO]@
116 \item @InvalidArgument@
117 The operand is not a valid directory name.
118 @[ENAMETOOLONG, ELOOP]@
119 \item @NoSuchThing@
120 There is no path to the directory. 
121 @[ENOENT, ENOTDIR]@
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.
128 @[EEXIST]@
129 \end{itemize}
130
131 \begin{code}
132 createDirectory :: FilePath -> IO ()
133 createDirectory path = do
134     rc <- primCreateDirectory (primPackString path)
135     if rc == 0 then return () else
136         constructErrorAndFailWithInfo "createDirectory" path
137 \end{code}
138
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
146 directory).
147
148 The operation may fail with:
149 \begin{itemize}
150 \item @HardwareFault@
151 A physical I/O error has occurred.
152 [@EIO@]
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. 
158 @[ENOENT, ENOTDIR]@
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.
167 @[EINVAL]@
168 \item @InappropriateType@
169 The operand refers to an existing non-directory object.
170 @[ENOTDIR]@
171 \end{itemize}
172
173 \begin{code}
174 removeDirectory :: FilePath -> IO ()
175 removeDirectory path = do
176     rc <- primRemoveDirectory (primPackString path)
177     if rc == 0 then 
178         return ()
179      else 
180         constructErrorAndFailWithInfo "removeDirectory" path
181 \end{code}
182
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).
188
189 The operation may fail with:
190 \begin{itemize}
191 \item @HardwareFault@
192 A physical I/O error has occurred.
193 @[EIO]@
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. 
199 @[ENOENT, ENOTDIR]@
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.  
205 @[EBUSY]@
206 \item @InappropriateType@
207 The operand refers to an existing directory.
208 @[EPERM, EINVAL]@
209 \end{itemize}
210
211 \begin{code}
212 removeFile :: FilePath -> IO ()
213 removeFile path = do
214     rc <- primRemoveFile (primPackString path)
215     if rc == 0 then
216         return ()
217      else
218         constructErrorAndFailWithInfo "removeFile" path
219 \end{code}
220
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
229 must be documented.
230
231 The operation may fail with:
232 \begin{itemize}
233 \item @HardwareFault@
234 A physical I/O error has occurred.
235 @[EIO]@
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.
241 @[ENOENT, ENOTDIR]@
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.
253 @[EINVAL, EXDEV]@
254 \item @InappropriateType@
255 Either path refers to an existing non-directory object.
256 @[ENOTDIR, EISDIR]@
257 \end{itemize}
258
259 \begin{code}
260 renameDirectory :: FilePath -> FilePath -> IO ()
261 renameDirectory opath npath = do
262     rc <- primRenameDirectory (primPackString opath) (primPackString npath)
263     if rc == 0 then
264         return ()
265      else
266         constructErrorAndFailWithInfo "renameDirectory" ("old: " ++ opath ++ ",new: " ++ npath)
267 \end{code}
268
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
275 documented.
276
277 The operation may fail with:
278 \begin{itemize}
279 \item @HardwareFault@
280 A physical I/O error has occurred.
281 @[EIO]@
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.
287 @[ENOENT, ENOTDIR]@
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.
296 @[EBUSY]@
297 \item @UnsupportedOperation@
298 The implementation does not support renaming in this situation.
299 @[EXDEV]@
300 \item @InappropriateType@
301 Either path refers to an existing directory.
302 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
303 \end{itemize}
304
305 \begin{code}
306 renameFile :: FilePath -> FilePath -> IO ()
307 renameFile opath npath = do
308     rc <- primRenameFile (primPackString opath) (primPackString npath)
309     if rc == 0 then
310         return ()
311      else
312         constructErrorAndFailWithInfo   "renameFile" opath
313 \end{code}
314
315 @getDirectoryContents dir@ returns a list of {\em all} entries
316 in {\em dir}. 
317
318 The operation may fail with:
319 \begin{itemize}
320 \item @HardwareFault@
321 A physical I/O error has occurred.
322 @[EIO]@
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.
328 @[ENOENT, ENOTDIR]@
329 \item @isPermissionError@ / @PermissionDenied@
330 The process has insufficient privileges to perform the operation.
331 @[EACCES]@
332 \item @ResourceExhausted@
333 Insufficient resources are available to perform the operation.
334 @[EMFILE, ENFILE]@
335 \item @InappropriateType@
336 The path refers to an existing non-directory object.
337 @[ENOTDIR]@
338 \end{itemize}
339
340 \begin{code}
341 getDirectoryContents :: FilePath -> IO [FilePath]
342 getDirectoryContents path = do
343     dir <- primOpenDir (primPackString path)
344     if dir == nullAddr
345         then constructErrorAndFailWithInfo "getDirectoryContents" path
346         else loop dir
347   where
348     loop :: Addr -> IO [String]
349     loop dir  = do
350       dirent_ptr <- primReadDir dir
351       if dirent_ptr == nullAddr
352        then do
353           -- readDir__ implicitly performs closedir() when the
354           -- end is reached.
355           return [] 
356        else do
357           str     <- primGetDirentDName dirent_ptr
358           entry   <- primUnpackCString str
359           entries <- loop dir
360           return (entry:entries)
361 \end{code}
362
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.
366
367 The operation may fail with:
368 \begin{itemize}
369 \item @HardwareFault@
370 A physical I/O error has occurred.
371 @[EIO]@
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.
377 @[EACCES]@
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.
382 \end{itemize}
383
384 \begin{code}
385 getCurrentDirectory :: IO FilePath
386 getCurrentDirectory = do
387     str <- primGetCurrentDirectory
388     if str /= nullAddr
389         then do
390             pwd <- primUnpackCString str
391             primFree str
392             return pwd
393         else
394             constructErrorAndFail "getCurrentDirectory"
395 \end{code}
396
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}.
400
401 The operation may fail with:
402 \begin{itemize}
403 \item @HardwareFault@
404 A physical I/O error has occurred.
405 @[EIO]@
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.
411 @[ENOENT, ENOTDIR]@
412 \item @isPermissionError@ / @PermissionDenied@
413 The process has insufficient privileges to perform the operation.
414 @[EACCES]@
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.
420 @[ENOTDIR]@
421 \end{itemize}
422
423 \begin{code}
424 setCurrentDirectory :: FilePath -> IO ()
425 setCurrentDirectory path = do
426     rc <- primSetCurrentDirectory (primPackString path)
427     if rc == 0 
428         then return ()
429         else constructErrorAndFailWithInfo "setCurrentDirectory" path
430 \end{code}
431
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.) 
436
437 \begin{code}
438 doesDirectoryExist :: FilePath -> IO Bool
439 doesDirectoryExist name = 
440  catch
441    (getFileStatus name >>= \ st -> return (isDirectory st))
442    (\ _ -> return False)
443
444 doesFileExist :: FilePath -> IO Bool
445 doesFileExist name = do 
446  catch
447    (getFileStatus name >>= \ st -> return (not (isDirectory st)))
448    (\ _ -> return False)
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   read  <- primAccess (primPackString name) readOK
461   write <- primAccess (primPackString name) writeOK
462   exec  <- primAccess (primPackString name) executeOK
463
464   return (
465     Permissions {
466       readable   = read  == 0,
467       writable   = write == 0,
468       executable = not (isDirectory st)   && exec == 0,
469       searchable = not (isRegularFile st) && exec == 0
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 ioException (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 ioException (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" "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" "sizeof_stat" unsafe sizeof_stat :: Int
524 foreign import ccall "libHS_cbits" "prim_stat"   unsafe
525   primStat :: PrimByteArray -> PrimMutableByteArray RealWorld -> IO Int
526
527 foreign import ccall "libHS_cbits" "get_stat_st_mode" unsafe fileMode     :: FileStatus -> FileMode
528 foreign import ccall "libHS_cbits" "prim_S_ISDIR"     unsafe prim_S_ISDIR :: FileMode -> Int
529 foreign import ccall "libHS_cbits" "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" "const_S_IRUSR" unsafe ownerReadMode    :: FileMode
540 foreign import ccall "libHS_cbits" "const_S_IWUSR" unsafe ownerWriteMode   :: FileMode
541 foreign import ccall "libHS_cbits" "const_S_IXUSR" unsafe ownerExecuteMode :: FileMode
542
543 #ifdef __HUGS__
544 emptyFileMode     = primIntToWord 0
545 unionFileMode     = primOrWord
546 intersectFileMode = primAndWord
547 #else
548 emptyFileMode     = intToWord 0
549 unionFileMode     = orWord
550 intersectFileMode = andWord
551 #endif
552 \end{code}
553
554 \begin{code}
555 type AccessMode = Word
556
557 foreign import ccall "libHS_cbits" "const_R_OK" unsafe readOK       :: AccessMode
558 foreign import ccall "libHS_cbits" "const_W_OK" unsafe writeOK      :: AccessMode
559 foreign import ccall "libHS_cbits" "const_X_OK" unsafe executeOK    :: AccessMode
560 \end{code}
561
562 Some defns. to allow us to share code.
563
564 \begin{code}
565 #ifndef __HUGS__
566
567 primPackString :: [Char] -> ByteArray Int
568 primPackString    = packString
569 --ToDo: fix.
570 primUnpackCString :: Addr -> IO String
571 primUnpackCString a = stToIO (unpackCStringST a)
572
573 type PrimByteArray = ByteArray Int
574 type PrimMutableByteArray s = MutableByteArray RealWorld Int
575 type CString = PrimByteArray
576
577 orWord, andWord :: Word -> Word -> Word
578 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
579 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
580
581 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
582 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
583 #endif
584
585 foreign import ccall "libHS_cbits" "createDirectory"    unsafe primCreateDirectory     :: CString -> IO Int
586 foreign import ccall "libHS_cbits" "removeDirectory"    unsafe primRemoveDirectory     :: CString -> IO Int
587 foreign import ccall "libHS_cbits" "removeFile"         unsafe primRemoveFile          :: CString -> IO Int
588 foreign import ccall "libHS_cbits" "renameDirectory"    unsafe primRenameDirectory     :: CString -> CString -> IO Int
589 foreign import ccall "libHS_cbits" "renameFile"         unsafe primRenameFile          :: CString -> CString -> IO Int
590 foreign import ccall "libHS_cbits" "openDir__"          unsafe primOpenDir      :: CString -> IO Addr
591 foreign import ccall "libHS_cbits" "readDir__"          unsafe primReadDir      :: Addr -> IO Addr
592 foreign import ccall "libHS_cbits" "get_dirent_d_name"   unsafe primGetDirentDName      :: Addr -> IO Addr
593 foreign import ccall "libHS_cbits" "setCurrentDirectory" unsafe primSetCurrentDirectory :: CString -> IO Int
594 foreign import ccall "libHS_cbits" "getCurrentDirectory" unsafe primGetCurrentDirectory :: IO Addr
595 foreign import ccall "libc"        "free"                unsafe primFree                :: Addr -> IO ()
596 foreign import ccall "libc"        "malloc"              unsafe primMalloc              :: Word -> IO Addr
597 foreign import ccall "libc"        "chmod"               unsafe primChmod               :: CString -> Word -> IO Int
598
599 foreign import ccall "libc" "access" unsafe 
600         primAccess :: CString -> Word -> IO Int
601 \end{code}
602