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