47705ddae2cc7f7ad9cfd61fa23a627ca80a6a47
[ghc-hetmet.git] / ghc / lib / std / Directory.lhs
1 %
2 % (c) The AQUA Project, Glasgow University, 1994-1997
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),
24
25     createDirectory, 
26     removeDirectory, 
27     renameDirectory, 
28     getDirectoryContents,
29     getCurrentDirectory, 
30     setCurrentDirectory,
31
32     removeFile, 
33     renameFile, 
34
35     doesFileExist,
36     doesDirectoryExist,
37     getPermissions, 
38     setPermissions,
39     getModificationTime
40    ) where
41
42 import PrelBase
43 import PrelIOBase
44 import PrelST
45 import PrelArr
46 import PrelPack         ( unpackNBytesST )
47 import PrelCCall        ( Word(..) )
48 import PrelAddr
49 import Time             ( ClockTime(..) )
50
51 \end{code}
52
53 %*********************************************************
54 %*                                                      *
55 \subsection{Signatures}
56 %*                                                      *
57 %*********************************************************
58
59 \begin{code}
60 createDirectory         :: FilePath -> IO ()
61 removeDirectory         :: FilePath -> IO ()
62 removeFile              :: FilePath -> IO ()
63 renameDirectory         :: FilePath -> FilePath -> IO ()
64 renameFile              :: FilePath -> FilePath -> IO ()
65 getDirectoryContents    :: FilePath -> IO [FilePath]
66 getCurrentDirectory     :: IO FilePath
67 setCurrentDirectory     :: FilePath -> IO ()
68 doesFileExist           :: FilePath -> IO Bool
69 doesDirectoryExist      :: FilePath -> IO Bool
70 getPermissions          :: FilePath -> IO Permissions
71 setPermissions          :: FilePath -> Permissions -> IO ()
72 getModificationTime     :: FilePath -> IO ClockTime
73 \end{code}
74
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,   writeable, 
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 path = do
133     rc <- _ccall_ createDirectory path
134     if rc == 0 then return () else
135         constructErrorAndFailWithInfo "createDirectory" path
136 \end{code}
137
138 @removeDirectory dir@ removes an existing directory {\em dir}.  The
139 implementation may specify additional constraints which must be
140 satisfied before a directory can be removed (e.g. the directory has to
141 be empty, or may not be in use by other processes).  It is not legal
142 for an implementation to partially remove a directory unless the
143 entire directory is removed. A conformant implementation need not
144 support directory removal in all situations (e.g. removal of the root
145 directory).
146
147 The operation may fail with:
148 \begin{itemize}
149 \item @HardwareFault@
150 A physical I/O error has occurred.
151 [@EIO@]
152 \item @InvalidArgument@
153 The operand is not a valid directory name.
154 @[ENAMETOOLONG, ELOOP]@
155 \item @isDoesNotExist@ / @NoSuchThing@
156 The directory does not exist. 
157 @[ENOENT, ENOTDIR]@
158 \item @isPermissionError@ / @PermissionDenied@
159 The process has insufficient privileges to perform the operation.
160 @[EROFS, EACCES, EPERM]@
161 \item @UnsatisfiedConstraints@
162 Implementation-dependent constraints are not satisfied.  
163 @[EBUSY, ENOTEMPTY, EEXIST]@
164 \item @UnsupportedOperation@
165 The implementation does not support removal in this situation.
166 @[EINVAL]@
167 \item @InappropriateType@
168 The operand refers to an existing non-directory object.
169 @[ENOTDIR]@
170 \end{itemize}
171
172 \begin{code}
173 removeDirectory path = do
174     rc <- _ccall_ removeDirectory path
175     if rc == 0 then 
176         return ()
177      else 
178         constructErrorAndFailWithInfo "removeDirectory" path
179 \end{code}
180
181 @removeFile file@ removes the directory entry for an existing file
182 {\em file}, where {\em file} is not itself a directory. The
183 implementation may specify additional constraints which must be
184 satisfied before a file can be removed (e.g. the file may not be in
185 use by other processes).
186
187 The operation may fail with:
188 \begin{itemize}
189 \item @HardwareFault@
190 A physical I/O error has occurred.
191 @[EIO]@
192 \item @InvalidArgument@
193 The operand is not a valid file name.
194 @[ENAMETOOLONG, ELOOP]@
195 \item @isDoesNotExist@ / @NoSuchThing@
196 The file does not exist. 
197 @[ENOENT, ENOTDIR]@
198 \item @isPermissionError@ / @PermissionDenied@
199 The process has insufficient privileges to perform the operation.
200 @[EROFS, EACCES, EPERM]@
201 \item @UnsatisfiedConstraints@
202 Implementation-dependent constraints are not satisfied.  
203 @[EBUSY]@
204 \item @InappropriateType@
205 The operand refers to an existing directory.
206 @[EPERM, EINVAL]@
207 \end{itemize}
208
209 \begin{code}
210 removeFile path = do
211     rc <- _ccall_ removeFile path
212     if rc == 0 then
213         return ()
214      else
215         constructErrorAndFailWithInfo "removeFile" path
216 \end{code}
217
218 @renameDirectory old@ {\em new} changes the name of an existing
219 directory from {\em old} to {\em new}.  If the {\em new} directory
220 already exists, it is atomically replaced by the {\em old} directory.
221 If the {\em new} directory is neither the {\em old} directory nor an
222 alias of the {\em old} directory, it is removed as if by
223 $removeDirectory$.  A conformant implementation need not support
224 renaming directories in all situations (e.g. renaming to an existing
225 directory, or across different physical devices), but the constraints
226 must be documented.
227
228 The operation may fail with:
229 \begin{itemize}
230 \item @HardwareFault@
231 A physical I/O error has occurred.
232 @[EIO]@
233 \item @InvalidArgument@
234 Either operand is not a valid directory name.
235 @[ENAMETOOLONG, ELOOP]@
236 \item @isDoesNotExistError@ / @NoSuchThing@
237 The original directory does not exist, or there is no path to the target.
238 @[ENOENT, ENOTDIR]@
239 \item @isPermissionError@ / @PermissionDenied@
240 The process has insufficient privileges to perform the operation.
241 @[EROFS, EACCES, EPERM]@
242 \item @ResourceExhausted@
243 Insufficient resources are available to perform the operation.  
244 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
245 \item @UnsatisfiedConstraints@
246 Implementation-dependent constraints are not satisfied.
247 @[EBUSY, ENOTEMPTY, EEXIST]@
248 \item @UnsupportedOperation@
249 The implementation does not support renaming in this situation.
250 @[EINVAL, EXDEV]@
251 \item @InappropriateType@
252 Either path refers to an existing non-directory object.
253 @[ENOTDIR, EISDIR]@
254 \end{itemize}
255
256 \begin{code}
257 renameDirectory opath npath = do
258     rc <- _ccall_ renameDirectory opath npath
259     if rc == 0 then
260         return ()
261      else
262         constructErrorAndFailWithInfo "renameDirectory" ("old: " ++ opath ++ ",new: " ++ npath)
263 \end{code}
264
265 @renameFile old@ {\em new} changes the name of an existing file system
266 object from {\em old} to {\em new}.  If the {\em new} object already
267 exists, it is atomically replaced by the {\em old} object.  Neither
268 path may refer to an existing directory.  A conformant implementation
269 need not support renaming files in all situations (e.g. renaming
270 across different physical devices), but the constraints must be
271 documented.
272
273 The operation may fail with:
274 \begin{itemize}
275 \item @HardwareFault@
276 A physical I/O error has occurred.
277 @[EIO]@
278 \item @InvalidArgument@
279 Either operand is not a valid file name.
280 @[ENAMETOOLONG, ELOOP]@
281 \item @isDoesNotExistError@ / @NoSuchThing@
282 The original file does not exist, or there is no path to the target.
283 @[ENOENT, ENOTDIR]@
284 \item @isPermissionError@ / @PermissionDenied@
285 The process has insufficient privileges to perform the operation.
286 @[EROFS, EACCES, EPERM]@
287 \item @ResourceExhausted@
288 Insufficient resources are available to perform the operation.  
289 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
290 \item @UnsatisfiedConstraints@
291 Implementation-dependent constraints are not satisfied.
292 @[EBUSY]@
293 \item @UnsupportedOperation@
294 The implementation does not support renaming in this situation.
295 @[EXDEV]@
296 \item @InappropriateType@
297 Either path refers to an existing directory.
298 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
299 \end{itemize}
300
301 \begin{code}
302 renameFile opath npath = do
303     rc <- _ccall_ renameFile opath npath
304     if rc == 0 then
305         return ()
306      else
307         constructErrorAndFailWithInfo   "renameFile" opath
308 \end{code}
309
310 @getDirectoryContents dir@ returns a list of {\em all} entries
311 in {\em dir}. 
312
313 The operation may fail with:
314 \begin{itemize}
315 \item @HardwareFault@
316 A physical I/O error has occurred.
317 @[EIO]@
318 \item @InvalidArgument@
319 The operand is not a valid directory name.
320 @[ENAMETOOLONG, ELOOP]@
321 \item @isDoesNotExistError@ / @NoSuchThing@
322 The directory does not exist.
323 @[ENOENT, ENOTDIR]@
324 \item @isPermissionError@ / @PermissionDenied@
325 The process has insufficient privileges to perform the operation.
326 @[EACCES]@
327 \item @ResourceExhausted@
328 Insufficient resources are available to perform the operation.
329 @[EMFILE, ENFILE]@
330 \item @InappropriateType@
331 The path refers to an existing non-directory object.
332 @[ENOTDIR]@
333 \end{itemize}
334
335 \begin{code}
336 --getDirectoryContents :: FilePath -> IO [FilePath]
337 getDirectoryContents path = do
338     dir <- _ccall_ openDir__ path
339     if dir == ``NULL'' 
340         then constructErrorAndFailWithInfo "getDirectoryContents" path
341         else loop dir
342   where
343     loop :: Addr -> IO [String]
344     loop dir  = do
345       dirent_ptr <- _ccall_ readDir__ dir
346       if (dirent_ptr::Addr) == ``NULL'' 
347        then do
348           -- readDir__ implicitly performs closedir() when the
349           -- end is reached.
350           return [] 
351        else do
352           str     <- _casm_ `` %r=(char*)((struct dirent*)%0)->d_name; '' dirent_ptr
353             -- not using the unpackCString function here, since we have to force
354             -- the unmarshalling of the directory entry right here as subsequent
355             -- calls to readdir() may overwrite it.
356           len     <- _ccall_ strlen str
357           entry   <- stToIO (unpackNBytesST str len)
358           entries <- loop dir
359           return (entry:entries)
360 \end{code}
361
362 If the operating system has a notion of current directories,
363 @getCurrentDirectory@ returns an absolute path to the
364 current directory of the calling process.
365
366 The operation may fail with:
367 \begin{itemize}
368 \item @HardwareFault@
369 A physical I/O error has occurred.
370 @[EIO]@
371 \item @isDoesNotExistError@ / @NoSuchThing@
372 There is no path referring to the current directory.
373 @[EPERM, ENOENT, ESTALE...]@
374 \item @isPermissionError@ / @PermissionDenied@
375 The process has insufficient privileges to perform the operation.
376 @[EACCES]@
377 \item @ResourceExhausted@
378 Insufficient resources are available to perform the operation.
379 \item @UnsupportedOperation@
380 The operating system has no notion of current directory.
381 \end{itemize}
382
383 \begin{code}
384 getCurrentDirectory = do
385     str <- _ccall_ getCurrentDirectory
386     if str /= ``NULL'' 
387         then do
388             len <- _ccall_ strlen str
389             pwd <- stToIO (unpackNBytesST str len)
390             _ccall_ free str
391             return pwd
392         else
393             constructErrorAndFail "getCurrentDirectory"
394 \end{code}
395
396 If the operating system has a notion of current directories,
397 @setCurrentDirectory dir@ changes the current
398 directory of the calling process to {\em dir}.
399
400 The operation may fail with:
401 \begin{itemize}
402 \item @HardwareFault@
403 A physical I/O error has occurred.
404 @[EIO]@
405 \item @InvalidArgument@
406 The operand is not a valid directory name.
407 @[ENAMETOOLONG, ELOOP]@
408 \item @isDoesNotExistError@ / @NoSuchThing@
409 The directory does not exist.
410 @[ENOENT, ENOTDIR]@
411 \item @isPermissionError@ / @PermissionDenied@
412 The process has insufficient privileges to perform the operation.
413 @[EACCES]@
414 \item @UnsupportedOperation@
415 The operating system has no notion of current directory, or the
416 current directory cannot be dynamically changed.
417 \item @InappropriateType@
418 The path refers to an existing non-directory object.
419 @[ENOTDIR]@
420 \end{itemize}
421
422 \begin{code}
423 setCurrentDirectory path = do
424     rc <- _ccall_ setCurrentDirectory path
425     if rc == 0 
426         then return ()
427         else constructErrorAndFailWithInfo "setCurrentDirectory" path
428 \end{code}
429
430
431 \begin{code}
432 --doesFileExist :: FilePath -> IO Bool
433 doesFileExist name = do 
434   rc <- _ccall_ access name (``F_OK''::Int)
435   return (rc == 0)
436
437 --doesDirectoryExist :: FilePath -> IO Bool
438 doesDirectoryExist name = 
439  (getFileStatus name >>= \ st -> return (isDirectory st))  
440    `catch` 
441  (\ _ -> return False)
442
443 --getModificationTime :: FilePath -> IO ClockTime
444 getModificationTime name =
445  getFileStatus name >>= \ st ->
446  modificationTime st
447
448 --getPermissions :: FilePath -> IO Permissions
449 getPermissions name =
450   getFileStatus name >>= \ st ->
451   let
452    fm = fileMode st
453    isect v = intersectFileMode v fm == v
454   in
455   return (
456     Permissions {
457       readable   = isect ownerReadMode,
458       writeable  = isect ownerWriteMode,
459       executable = not (isDirectory st)   && isect ownerExecuteMode,
460       searchable = not (isRegularFile st) && isect ownerExecuteMode
461     }
462   )
463
464 --setPermissions :: FilePath -> Permissions -> IO ()
465 setPermissions name (Permissions r w e s) = do
466     let
467      read#  = case (if r then ownerReadMode else ``0'') of { W# x# -> x# }
468      write# = case (if w then ownerWriteMode else ``0'') of { W# x# -> x# }
469      exec#  = case (if e || s then ownerExecuteMode else ``0'') of { W# x# -> x# }
470
471      mode  = I# (word2Int# (read# `or#` write# `or#` exec#))
472
473     rc <- _ccall_ chmod name mode
474     if rc == 0
475         then return ()
476         else fail (IOError Nothing SystemError "setPermissions" "insufficient permissions")
477
478 \end{code}
479
480
481 (Sigh)..copied from Posix.Files to avoid dep. on posix library
482
483 \begin{code}
484 type FileStatus = ByteArray Int
485
486 getFileStatus :: FilePath -> IO FileStatus
487 getFileStatus name = do
488     bytes <- stToIO (newCharArray (0,``sizeof(struct stat)''))
489     rc <- _casm_ ``%r = stat(%0,(struct stat *)%1);'' name bytes
490     if rc == 0 
491         then stToIO (unsafeFreezeByteArray bytes)
492         else fail (IOError Nothing SystemError "getFileStatus" "")
493
494 modificationTime :: FileStatus -> IO ClockTime
495 modificationTime stat = do
496     i1 <- malloc1
497     _casm_ ``((unsigned long *)%1)[0] = ((struct stat *)%0)->st_mtime;'' stat i1
498     secs <- cvtUnsigned i1
499     return (TOD secs 0)
500   where
501     malloc1 = IO $ \ s# ->
502         case newIntArray# 1# s# of 
503           StateAndMutableByteArray# s2# barr# -> 
504                 IOok s2# (MutableByteArray bnds barr#)
505
506     bnds = (0,1)
507     -- The C routine fills in an unsigned word.  We don't have `unsigned2Integer#,'
508     -- so we freeze the data bits and use them for an MP_INT structure.  Note that
509     -- zero is still handled specially, although (J# 1# 1# (ptr to 0#)) is probably
510     -- acceptable to gmp.
511
512     cvtUnsigned (MutableByteArray _ arr#) = IO $ \ s# ->
513         case readIntArray# arr# 0# s# of 
514           StateAndInt# s2# r# ->
515             if r# ==# 0# then
516                 IOok s2# 0
517             else
518                 case unsafeFreezeByteArray# arr# s2# of
519                   StateAndByteArray# s3# frozen# -> 
520                         IOok s3# (J# 1# 1# frozen#)
521
522 isDirectory :: FileStatus -> Bool
523 isDirectory stat = unsafePerformIO $ do
524     rc <- _casm_ ``%r = S_ISDIR(((struct stat *)%0)->st_mode);'' stat
525     return (rc /= 0)
526
527 isRegularFile :: FileStatus -> Bool
528 isRegularFile stat = unsafePerformIO $ do
529     rc <- _casm_ ``%r = S_ISREG(((struct stat *)%0)->st_mode);'' stat
530     return (rc /= 0)
531 \end{code}
532
533 \begin{code}
534 type FileMode = Word
535 ownerReadMode :: FileMode
536 ownerReadMode = ``S_IRUSR''
537
538 ownerWriteMode :: FileMode
539 ownerWriteMode = ``S_IWUSR''
540
541 ownerExecuteMode :: FileMode
542 ownerExecuteMode = ``S_IXUSR''
543
544 intersectFileMode :: FileMode -> FileMode -> FileMode
545 intersectFileMode (W# m1#) (W# m2#) = W# (m1# `and#` m2#)
546
547 fileMode :: FileStatus -> FileMode
548 fileMode stat = unsafePerformIO (
549         _casm_ ``%r = ((struct stat *)%0)->st_mode;'' stat)
550
551 \end{code}