ffb75a078e23c5cb51304c252819f7cada01b6de
[ghc-hetmet.git] / ghc / lib / std / Directory.lhs
1 % -----------------------------------------------------------------------------
2 % $Id: Directory.lhs,v 1.19 2000/07/07 11:03:57 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 foreign import ccall "libHS_cbits" "const_F_OK" unsafe const_F_OK  :: Int
451
452 #ifndef __HUGS__
453 getModificationTime :: FilePath -> IO ClockTime
454 getModificationTime name =
455  getFileStatus name >>= \ st ->
456  modificationTime st
457 #endif
458
459 getPermissions :: FilePath -> IO Permissions
460 getPermissions name = do
461   st <- getFileStatus name
462   let
463    fm = fileMode st
464    isect v = intersectFileMode v fm == v
465
466   return (
467     Permissions {
468       readable   = isect ownerReadMode,
469       writable   = isect ownerWriteMode,
470       executable = not (isDirectory st)   && isect ownerExecuteMode,
471       searchable = not (isRegularFile st) && isect ownerExecuteMode
472     }
473    )
474
475 setPermissions :: FilePath -> Permissions -> IO ()
476 setPermissions name (Permissions r w e s) = do
477     let
478      read  = if r      then ownerReadMode    else emptyFileMode
479      write = if w      then ownerWriteMode   else emptyFileMode
480      exec  = if e || s then ownerExecuteMode else emptyFileMode
481
482      mode  = read `unionFileMode` (write `unionFileMode` exec)
483
484     rc <- primChmod (primPackString name) mode
485     if rc == 0
486         then return ()
487         else ioException (IOError Nothing SystemError "setPermissions" "insufficient permissions")
488 \end{code}
489
490 (Sigh)..copied from Posix.Files to avoid dep. on posix library
491
492 \begin{code}
493 type FileStatus = PrimByteArray
494
495 getFileStatus :: FilePath -> IO FileStatus
496 getFileStatus name = do
497     bytes <- primNewByteArray sizeof_stat
498     rc <- primStat (primPackString name) bytes
499     if rc == 0 
500 #ifdef __HUGS__
501         then primUnsafeFreezeByteArray bytes
502 #else
503         then stToIO (unsafeFreezeByteArray bytes)
504 #endif
505         else ioException (IOError Nothing SystemError "getFileStatus" "")
506
507 #ifndef __HUGS__
508 modificationTime :: FileStatus -> IO ClockTime
509 modificationTime stat = do
510     i1 <- stToIO (newWordArray (0,1))
511     setFileMode i1 stat
512     secs <- stToIO (readWordArray i1 0)
513     return (TOD (toInteger (wordToInt secs)) 0)
514
515 foreign import ccall "libHS_cbits" "set_stat_st_mtime" unsafe
516    setFileMode :: PrimMutableByteArray RealWorld -> FileStatus -> IO ()
517 #endif
518
519 isDirectory :: FileStatus -> Bool
520 isDirectory stat = prim_S_ISDIR (fileMode stat) /= 0
521
522 isRegularFile :: FileStatus -> Bool
523 isRegularFile stat = prim_S_ISREG (fileMode stat) /= 0
524
525 foreign import ccall "libHS_cbits" "sizeof_stat" unsafe sizeof_stat :: Int
526 foreign import ccall "libHS_cbits" "prim_stat"   unsafe
527   primStat :: PrimByteArray -> PrimMutableByteArray RealWorld -> IO Int
528
529 foreign import ccall "libHS_cbits" "get_stat_st_mode" unsafe fileMode     :: FileStatus -> FileMode
530 foreign import ccall "libHS_cbits" "prim_S_ISDIR"     unsafe prim_S_ISDIR :: FileMode -> Int
531 foreign import ccall "libHS_cbits" "prim_S_ISREG"     unsafe prim_S_ISREG :: FileMode -> Int
532 \end{code}
533
534 \begin{code}
535 type FileMode = Word
536
537 emptyFileMode     :: FileMode
538 unionFileMode     :: FileMode -> FileMode -> FileMode
539 intersectFileMode :: FileMode -> FileMode -> FileMode
540
541 foreign import ccall "libHS_cbits" "const_S_IRUSR" unsafe ownerReadMode    :: FileMode
542 foreign import ccall "libHS_cbits" "const_S_IWUSR" unsafe ownerWriteMode   :: FileMode
543 foreign import ccall "libHS_cbits" "const_S_IXUSR" unsafe ownerExecuteMode :: FileMode
544
545 #ifdef __HUGS__
546 emptyFileMode     = primIntToWord 0
547 unionFileMode     = primOrWord
548 intersectFileMode = primAndWord
549 #else
550 emptyFileMode     = intToWord 0
551 unionFileMode     = orWord
552 intersectFileMode = andWord
553 #endif
554
555 \end{code}
556
557 Some defns. to allow us to share code.
558
559 \begin{code}
560 #ifndef __HUGS__
561
562 primPackString :: [Char] -> ByteArray Int
563 primPackString    = packString
564 --ToDo: fix.
565 primUnpackCString :: Addr -> IO String
566 primUnpackCString a = stToIO (unpackCStringST a)
567
568 type PrimByteArray = ByteArray Int
569 type PrimMutableByteArray s = MutableByteArray RealWorld Int
570 type CString = PrimByteArray
571
572 orWord, andWord :: Word -> Word -> Word
573 orWord (W# x#) (W# y#) = W# (x# `or#` y#)
574 andWord (W# x#) (W# y#) = W# (x# `and#` y#)
575
576 primNewByteArray :: Int -> IO (PrimMutableByteArray s)
577 primNewByteArray sz_in_bytes = stToIO (newCharArray (0,sz_in_bytes))
578 #endif
579
580 foreign import ccall "libHS_cbits" "createDirectory"    unsafe primCreateDirectory     :: CString -> IO Int
581 foreign import ccall "libHS_cbits" "removeDirectory"    unsafe primRemoveDirectory     :: CString -> IO Int
582 foreign import ccall "libHS_cbits" "removeFile"         unsafe primRemoveFile          :: CString -> IO Int
583 foreign import ccall "libHS_cbits" "renameDirectory"    unsafe primRenameDirectory     :: CString -> CString -> IO Int
584 foreign import ccall "libHS_cbits" "renameFile"         unsafe primRenameFile          :: CString -> CString -> IO Int
585 foreign import ccall "libHS_cbits" "openDir__"          unsafe primOpenDir      :: CString -> IO Addr
586 foreign import ccall "libHS_cbits" "readDir__"          unsafe primReadDir      :: Addr -> IO Addr
587 foreign import ccall "libHS_cbits" "get_dirent_d_name"   unsafe primGetDirentDName      :: Addr -> IO Addr
588 foreign import ccall "libHS_cbits" "setCurrentDirectory" unsafe primSetCurrentDirectory :: CString -> IO Int
589 foreign import ccall "libHS_cbits" "getCurrentDirectory" unsafe primGetCurrentDirectory :: IO Addr
590 foreign import ccall "libc"        "free"                unsafe primFree                :: Addr -> IO ()
591 foreign import ccall "libc"        "malloc"              unsafe primMalloc              :: Word -> IO Addr
592 foreign import ccall "libc"        "chmod"               unsafe primChmod               :: CString -> Word -> IO Int
593 \end{code}
594