72c254f48b7d34086f0f22597d98d2fecb642efb
[ghc-hetmet.git] / ghc / lib / required / 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> #-}
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 Foreign
44 import IOBase
45 import STBase
46 import UnsafeST         ( unsafePerformPrimIO )
47 import ArrBase
48 import PackedString     ( packCBytesST, unpackPS, psToByteArrayST )
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 =
133     _ccall_ createDirectory path    `thenIO_Prim` \ rc ->
134     if rc == 0 then
135         return ()
136     else
137         constructErrorAndFail "createDirectory"
138 \end{code}
139
140 @removeDirectory dir@ removes an existing directory {\em dir}.  The
141 implementation may specify additional constraints which must be
142 satisfied before a directory can be removed (e.g. the directory has to
143 be empty, or may not be in use by other processes).  It is not legal
144 for an implementation to partially remove a directory unless the
145 entire directory is removed. A conformant implementation need not
146 support directory removal in all situations (e.g. removal of the root
147 directory).
148
149 The operation may fail with:
150 \begin{itemize}
151 \item @HardwareFault@
152 A physical I/O error has occurred.
153 [@EIO@]
154 \item @InvalidArgument@
155 The operand is not a valid directory name.
156 @[ENAMETOOLONG, ELOOP]@
157 \item @isDoesNotExist@ / @NoSuchThing@
158 The directory does not exist. 
159 @[ENOENT, ENOTDIR]@
160 \item @isPermissionError@ / @PermissionDenied@
161 The process has insufficient privileges to perform the operation.
162 @[EROFS, EACCES, EPERM]@
163 \item @UnsatisfiedConstraints@
164 Implementation-dependent constraints are not satisfied.  
165 @[EBUSY, ENOTEMPTY, EEXIST]@
166 \item @UnsupportedOperation@
167 The implementation does not support removal in this situation.
168 @[EINVAL]@
169 \item @InappropriateType@
170 The operand refers to an existing non-directory object.
171 @[ENOTDIR]@
172 \end{itemize}
173
174 \begin{code}
175 removeDirectory path =
176     _ccall_ removeDirectory path    `thenIO_Prim` \ rc ->
177     if rc == 0 then
178         return ()
179     else
180         constructErrorAndFail "removeDirectory"
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 path =
213     _ccall_ removeFile path `thenIO_Prim` \ rc ->
214     if rc == 0 then
215         return ()
216     else
217         constructErrorAndFail "removeFile"
218 \end{code}
219
220 @renameDirectory old@ {\em new} changes the name of an existing
221 directory from {\em old} to {\em new}.  If the {\em new} directory
222 already exists, it is atomically replaced by the {\em old} directory.
223 If the {\em new} directory is neither the {\em old} directory nor an
224 alias of the {\em old} directory, it is removed as if by
225 $removeDirectory$.  A conformant implementation need not support
226 renaming directories in all situations (e.g. renaming to an existing
227 directory, or across different physical devices), but the constraints
228 must be documented.
229
230 The operation may fail with:
231 \begin{itemize}
232 \item @HardwareFault@
233 A physical I/O error has occurred.
234 @[EIO]@
235 \item @InvalidArgument@
236 Either operand is not a valid directory name.
237 @[ENAMETOOLONG, ELOOP]@
238 \item @isDoesNotExistError@ / @NoSuchThing@
239 The original directory does not exist, or there is no path to the target.
240 @[ENOENT, ENOTDIR]@
241 \item @isPermissionError@ / @PermissionDenied@
242 The process has insufficient privileges to perform the operation.
243 @[EROFS, EACCES, EPERM]@
244 \item @ResourceExhausted@
245 Insufficient resources are available to perform the operation.  
246 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
247 \item @UnsatisfiedConstraints@
248 Implementation-dependent constraints are not satisfied.
249 @[EBUSY, ENOTEMPTY, EEXIST]@
250 \item @UnsupportedOperation@
251 The implementation does not support renaming in this situation.
252 @[EINVAL, EXDEV]@
253 \item @InappropriateType@
254 Either path refers to an existing non-directory object.
255 @[ENOTDIR, EISDIR]@
256 \end{itemize}
257
258 \begin{code}
259 renameDirectory opath npath =
260     _ccall_ renameDirectory opath npath `thenIO_Prim` \ rc ->
261     if rc == 0 then
262         return ()
263     else
264         constructErrorAndFail "renameDirectory"
265 \end{code}
266
267 @renameFile old@ {\em new} changes the name of an existing file system
268 object from {\em old} to {\em new}.  If the {\em new} object already
269 exists, it is atomically replaced by the {\em old} object.  Neither
270 path may refer to an existing directory.  A conformant implementation
271 need not support renaming files in all situations (e.g. renaming
272 across different physical devices), but the constraints must be
273 documented.
274
275 The operation may fail with:
276 \begin{itemize}
277 \item @HardwareFault@
278 A physical I/O error has occurred.
279 @[EIO]@
280 \item @InvalidArgument@
281 Either operand is not a valid file name.
282 @[ENAMETOOLONG, ELOOP]@
283 \item @isDoesNotExistError@ / @NoSuchThing@
284 The original file does not exist, or there is no path to the target.
285 @[ENOENT, ENOTDIR]@
286 \item @isPermissionError@ / @PermissionDenied@
287 The process has insufficient privileges to perform the operation.
288 @[EROFS, EACCES, EPERM]@
289 \item @ResourceExhausted@
290 Insufficient resources are available to perform the operation.  
291 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
292 \item @UnsatisfiedConstraints@
293 Implementation-dependent constraints are not satisfied.
294 @[EBUSY]@
295 \item @UnsupportedOperation@
296 The implementation does not support renaming in this situation.
297 @[EXDEV]@
298 \item @InappropriateType@
299 Either path refers to an existing directory.
300 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
301 \end{itemize}
302
303 \begin{code}
304 renameFile opath npath =
305     _ccall_ renameFile opath npath  `thenIO_Prim` \ rc ->
306     if rc == 0 then
307         return ()
308     else
309         constructErrorAndFail   "renameFile"
310 \end{code}
311
312 @getDirectoryContents dir@ returns a list of {\em all} entries
313 in {\em dir}. 
314
315 The operation may fail with:
316 \begin{itemize}
317 \item @HardwareFault@
318 A physical I/O error has occurred.
319 @[EIO]@
320 \item @InvalidArgument@
321 The operand is not a valid directory name.
322 @[ENAMETOOLONG, ELOOP]@
323 \item @isDoesNotExistError@ / @NoSuchThing@
324 The directory does not exist.
325 @[ENOENT, ENOTDIR]@
326 \item @isPermissionError@ / @PermissionDenied@
327 The process has insufficient privileges to perform the operation.
328 @[EACCES]@
329 \item @ResourceExhausted@
330 Insufficient resources are available to perform the operation.
331 @[EMFILE, ENFILE]@
332 \item @InappropriateType@
333 The path refers to an existing non-directory object.
334 @[ENOTDIR]@
335 \end{itemize}
336
337 \begin{code}
338 getDirectoryContents path =
339     _ccall_ getDirectoryContents path   `thenIO_Prim` \ ptr ->
340     if ptr == ``NULL'' then
341         constructErrorAndFail "getDirectoryContents"
342     else
343         stToIO (getEntries ptr 0)       >>= \ entries ->
344         _ccall_ free ptr                `thenIO_Prim` \ () ->
345         return entries
346   where
347     getEntries :: Addr -> Int -> PrimIO [FilePath]
348     getEntries ptr n =
349         _casm_ ``%r = ((char **)%0)[%1];'' ptr n    >>= \ str ->
350         if str == ``NULL'' then 
351             return []
352         else
353             _ccall_ strlen str                      >>= \ len ->
354             packCBytesST len str                    >>= \ entry ->
355             _ccall_ free str                        >>= \ () ->
356             getEntries ptr (n+1)                    >>= \ entries ->
357             return (unpackPS entry : entries)
358 \end{code}
359
360 If the operating system has a notion of current directories,
361 @getCurrentDirectory@ returns an absolute path to the
362 current directory of the calling process.
363
364 The operation may fail with:
365 \begin{itemize}
366 \item @HardwareFault@
367 A physical I/O error has occurred.
368 @[EIO]@
369 \item @isDoesNotExistError@ / @NoSuchThing@
370 There is no path referring to the current directory.
371 @[EPERM, ENOENT, ESTALE...]@
372 \item @isPermissionError@ / @PermissionDenied@
373 The process has insufficient privileges to perform the operation.
374 @[EACCES]@
375 \item @ResourceExhausted@
376 Insufficient resources are available to perform the operation.
377 \item @UnsupportedOperation@
378 The operating system has no notion of current directory.
379 \end{itemize}
380
381 \begin{code}
382 getCurrentDirectory =
383     _ccall_ getCurrentDirectory     `thenIO_Prim` \ str ->
384     if str /= ``NULL'' then
385         _ccall_ strlen str              `thenIO_Prim` \ len ->
386         stToIO (packCBytesST len str)   >>=         \ pwd ->
387         _ccall_ free str                `thenIO_Prim` \ () ->
388         return (unpackPS pwd)
389     else
390         constructErrorAndFail "getCurrentDirectory"
391 \end{code}
392
393 If the operating system has a notion of current directories,
394 @setCurrentDirectory dir@ changes the current
395 directory of the calling process to {\em dir}.
396
397 The operation may fail with:
398 \begin{itemize}
399 \item @HardwareFault@
400 A physical I/O error has occurred.
401 @[EIO]@
402 \item @InvalidArgument@
403 The operand is not a valid directory name.
404 @[ENAMETOOLONG, ELOOP]@
405 \item @isDoesNotExistError@ / @NoSuchThing@
406 The directory does not exist.
407 @[ENOENT, ENOTDIR]@
408 \item @isPermissionError@ / @PermissionDenied@
409 The process has insufficient privileges to perform the operation.
410 @[EACCES]@
411 \item @UnsupportedOperation@
412 The operating system has no notion of current directory, or the
413 current directory cannot be dynamically changed.
414 \item @InappropriateType@
415 The path refers to an existing non-directory object.
416 @[ENOTDIR]@
417 \end{itemize}
418
419 \begin{code}
420 setCurrentDirectory path =
421     _ccall_ setCurrentDirectory path    `thenIO_Prim` \ rc ->
422     if rc == 0 then
423         return ()
424     else
425         constructErrorAndFail "setCurrentDirectory"
426 \end{code}
427
428
429
430 \begin{code}
431 --doesFileExist :: FilePath -> IO Bool
432 doesFileExist name =
433   psToByteArrayST name                      `thenIO_Prim` \ path ->
434   _ccall_ access path (``F_OK''::Int)       `thenIO_Prim` \ rc ->
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) = 
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     in
473     psToByteArrayST name                            `thenIO_Prim` \ path ->
474     _ccall_ chmod path mode                         `thenIO_Prim` \ rc ->
475     if rc == 0 then
476         return ()
477     else
478         fail (IOError Nothing SystemError "Directory.setPermissions")
479
480 \end{code}
481
482
483 (Sigh)..copied from Posix.Files to avoid dep. on posix library
484
485 \begin{code}
486 type FileStatus = ByteArray Int
487
488 getFileStatus :: FilePath -> IO FileStatus
489 getFileStatus name =
490     psToByteArrayST name                            `thenIO_Prim` \ path ->
491     newCharArray (0,``sizeof(struct stat)'')        `thenIO_Prim` \ bytes ->
492     _casm_ ``%r = stat(%0,(struct stat *)%1);'' path bytes
493                                                     `thenIO_Prim` \ rc ->
494     if rc == 0 then
495         unsafeFreezeByteArray bytes                 `thenIO_Prim` \ stat ->
496         return stat
497     else
498         fail (IOError Nothing SystemError "Directory.getFileStatus")
499
500 modificationTime :: FileStatus -> IO ClockTime
501 modificationTime stat =
502     malloc1                                                `thenIO_Prim` \ i1 ->
503     _casm_ ``((unsigned long *)%1)[0] = ((struct stat *)%0)->st_mtime;'' stat i1 `thenIO_Prim` \ () ->
504     cvtUnsigned i1                                         `thenIO_Prim` \ secs ->
505     return (TOD secs 0)
506   where
507     malloc1 = ST $ \ (S# s#) ->
508         case newIntArray# 1# s# of 
509           StateAndMutableByteArray# s2# barr# -> (MutableByteArray bnds barr#, S# s2#)
510
511     bnds = (0,1)
512     -- The C routine fills in an unsigned word.  We don't have `unsigned2Integer#,'
513     -- so we freeze the data bits and use them for an MP_INT structure.  Note that
514     -- zero is still handled specially, although (J# 1# 1# (ptr to 0#)) is probably
515     -- acceptable to gmp.
516
517     cvtUnsigned (MutableByteArray _ arr#) = ST $ \ (S# s#) ->
518         case readIntArray# arr# 0# s# of 
519           StateAndInt# s2# r# ->
520             if r# ==# 0# then
521                 (0, S# s2#)
522             else
523                 case unsafeFreezeByteArray# arr# s2# of
524                   StateAndByteArray# s3# frozen# -> (J# 1# 1# frozen#, S# s3#)
525
526 isDirectory :: FileStatus -> Bool
527 isDirectory stat = unsafePerformPrimIO $
528     _casm_ ``%r = S_ISDIR(((struct stat *)%0)->st_mode);'' stat >>= \ rc ->
529     return (rc /= 0)
530
531 isRegularFile :: FileStatus -> Bool
532 isRegularFile stat = unsafePerformPrimIO $
533     _casm_ ``%r = S_ISREG(((struct stat *)%0)->st_mode);'' stat >>= \ rc ->
534     return (rc /= 0)
535
536
537 \end{code}
538
539 \begin{code}
540 type FileMode = Word
541 ownerReadMode :: FileMode
542 ownerReadMode = ``S_IRUSR''
543
544 ownerWriteMode :: FileMode
545 ownerWriteMode = ``S_IWUSR''
546
547 ownerExecuteMode :: FileMode
548 ownerExecuteMode = ``S_IXUSR''
549
550 intersectFileMode :: FileMode -> FileMode -> FileMode
551 intersectFileMode (W# m1#) (W# m2#) = W# (m1# `and#` m2#)
552
553 fileMode :: FileStatus -> FileMode
554 fileMode stat = unsafePerformPrimIO $
555     _casm_ ``%r = ((struct stat *)%0)->st_mode;'' stat >>= \ mode ->
556     return mode
557
558 \end{code}