[project @ 1997-03-17 20:34:25 by simonpj]
[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 ArrBase
47 import PackedString     ( packCBytesST, unpackPS, psToByteArrayST )
48 import Time             ( ClockTime(..) )
49
50 \end{code}
51
52 %*********************************************************
53 %*                                                      *
54 \subsection{Signatures}
55 %*                                                      *
56 %*********************************************************
57
58 \begin{code}
59 createDirectory         :: FilePath -> IO ()
60 removeDirectory         :: FilePath -> IO ()
61 removeFile              :: FilePath -> IO ()
62 renameDirectory         :: FilePath -> FilePath -> IO ()
63 renameFile              :: FilePath -> FilePath -> IO ()
64 getDirectoryContents    :: FilePath -> IO [FilePath]
65 getCurrentDirectory     :: IO FilePath
66 setCurrentDirectory     :: FilePath -> IO ()
67 doesFileExist           :: FilePath -> IO Bool
68 doesDirectoryExist      :: FilePath -> IO Bool
69 getPermissions          :: FilePath -> IO Permissions
70 setPermissions          :: FilePath -> Permissions -> IO ()
71 getModificationTime     :: FilePath -> IO ClockTime
72 \end{code}
73
74
75 %*********************************************************
76 %*                                                      *
77 \subsection{Permissions}
78 %*                                                      *
79 %*********************************************************
80
81 The @Permissions@ type is used to record whether certain
82 operations are permissible on a file/directory:
83 [to whom? - owner/group/world - the Report don't say much]
84
85 \begin{code}
86 data Permissions
87  = Permissions {
88     readable,   writeable, 
89     executable, searchable :: Bool 
90    } deriving (Eq, Ord, Read, Show)
91 \end{code}
92
93 %*********************************************************
94 %*                                                      *
95 \subsection{Implementation}
96 %*                                                      *
97 %*********************************************************
98
99 @createDirectory dir@ creates a new directory {\em dir} which is
100 initially empty, or as near to empty as the operating system
101 allows.
102
103 The operation may fail with:
104
105 \begin{itemize}
106 \item @isPermissionError@ / @PermissionDenied@
107 The process has insufficient privileges to perform the operation.
108 @[EROFS, EACCES]@
109 \item @isAlreadyExistsError@ / @AlreadyExists@
110 The operand refers to a directory that already exists.  
111 @ [EEXIST]@
112 \item @HardwareFault@
113 A physical I/O error has occurred.
114 @ [EIO]@
115 \item @InvalidArgument@
116 The operand is not a valid directory name.
117 @[ENAMETOOLONG, ELOOP]@
118 \item @NoSuchThing@
119 There is no path to the directory. 
120 @[ENOENT, ENOTDIR]@
121 \item @ResourceExhausted@
122 Insufficient resources (virtual memory, process file descriptors,
123 physical disk space, etc.) are available to perform the operation.
124 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
125 \item @InappropriateType@
126 The path refers to an existing non-directory object.
127 @[EEXIST]@
128 \end{itemize}
129
130 \begin{code}
131 createDirectory path =
132     _ccall_ createDirectory path    `thenIO_Prim` \ rc ->
133     if rc == 0 then
134         return ()
135     else
136         constructErrorAndFail "createDirectory"
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 path =
175     _ccall_ removeDirectory path    `thenIO_Prim` \ rc ->
176     if rc == 0 then
177         return ()
178     else
179         constructErrorAndFail "removeDirectory"
180 \end{code}
181
182 @removeFile file@ removes the directory entry for an existing file
183 {\em file}, where {\em file} is not itself a directory. The
184 implementation may specify additional constraints which must be
185 satisfied before a file can be removed (e.g. the file may not be in
186 use by other processes).
187
188 The operation may fail with:
189 \begin{itemize}
190 \item @HardwareFault@
191 A physical I/O error has occurred.
192 @[EIO]@
193 \item @InvalidArgument@
194 The operand is not a valid file name.
195 @[ENAMETOOLONG, ELOOP]@
196 \item @isDoesNotExist@ / @NoSuchThing@
197 The file does not exist. 
198 @[ENOENT, ENOTDIR]@
199 \item @isPermissionError@ / @PermissionDenied@
200 The process has insufficient privileges to perform the operation.
201 @[EROFS, EACCES, EPERM]@
202 \item @UnsatisfiedConstraints@
203 Implementation-dependent constraints are not satisfied.  
204 @[EBUSY]@
205 \item @InappropriateType@
206 The operand refers to an existing directory.
207 @[EPERM, EINVAL]@
208 \end{itemize}
209
210 \begin{code}
211 removeFile path =
212     _ccall_ removeFile path `thenIO_Prim` \ rc ->
213     if rc == 0 then
214         return ()
215     else
216         constructErrorAndFail "removeFile"
217 \end{code}
218
219 @renameDirectory old@ {\em new} changes the name of an existing
220 directory from {\em old} to {\em new}.  If the {\em new} directory
221 already exists, it is atomically replaced by the {\em old} directory.
222 If the {\em new} directory is neither the {\em old} directory nor an
223 alias of the {\em old} directory, it is removed as if by
224 $removeDirectory$.  A conformant implementation need not support
225 renaming directories in all situations (e.g. renaming to an existing
226 directory, or across different physical devices), but the constraints
227 must be documented.
228
229 The operation may fail with:
230 \begin{itemize}
231 \item @HardwareFault@
232 A physical I/O error has occurred.
233 @[EIO]@
234 \item @InvalidArgument@
235 Either operand is not a valid directory name.
236 @[ENAMETOOLONG, ELOOP]@
237 \item @isDoesNotExistError@ / @NoSuchThing@
238 The original directory does not exist, or there is no path to the target.
239 @[ENOENT, ENOTDIR]@
240 \item @isPermissionError@ / @PermissionDenied@
241 The process has insufficient privileges to perform the operation.
242 @[EROFS, EACCES, EPERM]@
243 \item @ResourceExhausted@
244 Insufficient resources are available to perform the operation.  
245 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
246 \item @UnsatisfiedConstraints@
247 Implementation-dependent constraints are not satisfied.
248 @[EBUSY, ENOTEMPTY, EEXIST]@
249 \item @UnsupportedOperation@
250 The implementation does not support renaming in this situation.
251 @[EINVAL, EXDEV]@
252 \item @InappropriateType@
253 Either path refers to an existing non-directory object.
254 @[ENOTDIR, EISDIR]@
255 \end{itemize}
256
257 \begin{code}
258 renameDirectory opath npath =
259     _ccall_ renameDirectory opath npath `thenIO_Prim` \ rc ->
260     if rc == 0 then
261         return ()
262     else
263         constructErrorAndFail "renameDirectory"
264 \end{code}
265
266 @renameFile old@ {\em new} changes the name of an existing file system
267 object from {\em old} to {\em new}.  If the {\em new} object already
268 exists, it is atomically replaced by the {\em old} object.  Neither
269 path may refer to an existing directory.  A conformant implementation
270 need not support renaming files in all situations (e.g. renaming
271 across different physical devices), but the constraints must be
272 documented.
273
274 The operation may fail with:
275 \begin{itemize}
276 \item @HardwareFault@
277 A physical I/O error has occurred.
278 @[EIO]@
279 \item @InvalidArgument@
280 Either operand is not a valid file name.
281 @[ENAMETOOLONG, ELOOP]@
282 \item @isDoesNotExistError@ / @NoSuchThing@
283 The original file does not exist, or there is no path to the target.
284 @[ENOENT, ENOTDIR]@
285 \item @isPermissionError@ / @PermissionDenied@
286 The process has insufficient privileges to perform the operation.
287 @[EROFS, EACCES, EPERM]@
288 \item @ResourceExhausted@
289 Insufficient resources are available to perform the operation.  
290 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
291 \item @UnsatisfiedConstraints@
292 Implementation-dependent constraints are not satisfied.
293 @[EBUSY]@
294 \item @UnsupportedOperation@
295 The implementation does not support renaming in this situation.
296 @[EXDEV]@
297 \item @InappropriateType@
298 Either path refers to an existing directory.
299 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
300 \end{itemize}
301
302 \begin{code}
303 renameFile opath npath =
304     _ccall_ renameFile opath npath  `thenIO_Prim` \ rc ->
305     if rc == 0 then
306         return ()
307     else
308         constructErrorAndFail   "renameFile"
309 \end{code}
310
311 @getDirectoryContents dir@ returns a list of {\em all} entries
312 in {\em dir}. 
313
314 The operation may fail with:
315 \begin{itemize}
316 \item @HardwareFault@
317 A physical I/O error has occurred.
318 @[EIO]@
319 \item @InvalidArgument@
320 The operand is not a valid directory name.
321 @[ENAMETOOLONG, ELOOP]@
322 \item @isDoesNotExistError@ / @NoSuchThing@
323 The directory does not exist.
324 @[ENOENT, ENOTDIR]@
325 \item @isPermissionError@ / @PermissionDenied@
326 The process has insufficient privileges to perform the operation.
327 @[EACCES]@
328 \item @ResourceExhausted@
329 Insufficient resources are available to perform the operation.
330 @[EMFILE, ENFILE]@
331 \item @InappropriateType@
332 The path refers to an existing non-directory object.
333 @[ENOTDIR]@
334 \end{itemize}
335
336 \begin{code}
337 getDirectoryContents path =
338     _ccall_ getDirectoryContents path   `thenIO_Prim` \ ptr ->
339     if ptr == ``NULL'' then
340         constructErrorAndFail "getDirectoryContents"
341     else
342         stToIO (getEntries ptr 0)       >>= \ entries ->
343         _ccall_ free ptr                `thenIO_Prim` \ () ->
344         return entries
345   where
346     getEntries :: Addr -> Int -> PrimIO [FilePath]
347     getEntries ptr n =
348         _casm_ ``%r = ((char **)%0)[%1];'' ptr n    >>= \ str ->
349         if str == ``NULL'' then 
350             return []
351         else
352             _ccall_ strlen str                      >>= \ len ->
353             packCBytesST len str                    >>= \ entry ->
354             _ccall_ free str                        >>= \ () ->
355             getEntries ptr (n+1)                    >>= \ entries ->
356             return (unpackPS entry : entries)
357 \end{code}
358
359 If the operating system has a notion of current directories,
360 @getCurrentDirectory@ returns an absolute path to the
361 current directory of the calling process.
362
363 The operation may fail with:
364 \begin{itemize}
365 \item @HardwareFault@
366 A physical I/O error has occurred.
367 @[EIO]@
368 \item @isDoesNotExistError@ / @NoSuchThing@
369 There is no path referring to the current directory.
370 @[EPERM, ENOENT, ESTALE...]@
371 \item @isPermissionError@ / @PermissionDenied@
372 The process has insufficient privileges to perform the operation.
373 @[EACCES]@
374 \item @ResourceExhausted@
375 Insufficient resources are available to perform the operation.
376 \item @UnsupportedOperation@
377 The operating system has no notion of current directory.
378 \end{itemize}
379
380 \begin{code}
381 getCurrentDirectory =
382     _ccall_ getCurrentDirectory     `thenIO_Prim` \ str ->
383     if str /= ``NULL'' then
384         _ccall_ strlen str              `thenIO_Prim` \ len ->
385         stToIO (packCBytesST len str)   >>=         \ pwd ->
386         _ccall_ free str                `thenIO_Prim` \ () ->
387         return (unpackPS pwd)
388     else
389         constructErrorAndFail "getCurrentDirectory"
390 \end{code}
391
392 If the operating system has a notion of current directories,
393 @setCurrentDirectory dir@ changes the current
394 directory of the calling process to {\em dir}.
395
396 The operation may fail with:
397 \begin{itemize}
398 \item @HardwareFault@
399 A physical I/O error has occurred.
400 @[EIO]@
401 \item @InvalidArgument@
402 The operand is not a valid directory name.
403 @[ENAMETOOLONG, ELOOP]@
404 \item @isDoesNotExistError@ / @NoSuchThing@
405 The directory does not exist.
406 @[ENOENT, ENOTDIR]@
407 \item @isPermissionError@ / @PermissionDenied@
408 The process has insufficient privileges to perform the operation.
409 @[EACCES]@
410 \item @UnsupportedOperation@
411 The operating system has no notion of current directory, or the
412 current directory cannot be dynamically changed.
413 \item @InappropriateType@
414 The path refers to an existing non-directory object.
415 @[ENOTDIR]@
416 \end{itemize}
417
418 \begin{code}
419 setCurrentDirectory path =
420     _ccall_ setCurrentDirectory path    `thenIO_Prim` \ rc ->
421     if rc == 0 then
422         return ()
423     else
424         constructErrorAndFail "setCurrentDirectory"
425 \end{code}
426
427
428
429 \begin{code}
430 --doesFileExist :: FilePath -> IO Bool
431 doesFileExist name =
432   psToByteArrayST name                      `thenIO_Prim` \ path ->
433   _ccall_ access path (``F_OK''::Int)       `thenIO_Prim` \ rc ->
434   return (rc == 0)
435
436 --doesDirectoryExist :: FilePath -> IO Bool
437 doesDirectoryExist name = 
438  (getFileStatus name >>= \ st -> return (isDirectory st))  
439    `catch` 
440  (\ _ -> return False)
441
442 --getModificationTime :: FilePath -> IO ClockTime
443 getModificationTime name =
444  getFileStatus name >>= \ st ->
445  modificationTime st
446
447 --getPermissions :: FilePath -> IO Permissions
448 getPermissions name =
449   getFileStatus name >>= \ st ->
450   let
451    fm = fileMode st
452    isect v = intersectFileMode v fm == v
453   in
454   return (
455     Permissions {
456       readable   = isect ownerReadMode,
457       writeable  = isect ownerWriteMode,
458       executable = not (isDirectory st)   && isect ownerExecuteMode,
459       searchable = not (isRegularFile st) && isect ownerExecuteMode
460     }
461   )
462
463 --setPermissions :: FilePath -> Permissions -> IO ()
464 setPermissions name (Permissions r w e s) = 
465     let
466      read#  = case (if r then ownerReadMode else ``0'') of { W# x# -> x# }
467      write# = case (if w then ownerWriteMode else ``0'') of { W# x# -> x# }
468      exec#  = case (if e || s then ownerExecuteMode else ``0'') of { W# x# -> x# }
469
470      mode  = I# (word2Int# (read# `or#` write# `or#` exec#))
471     in
472     psToByteArrayST name                            `thenIO_Prim` \ path ->
473     _ccall_ chmod path mode                         `thenIO_Prim` \ rc ->
474     if rc == 0 then
475         return ()
476     else
477         fail (IOError Nothing SystemError "Directory.setPermissions")
478
479 \end{code}
480
481
482 (Sigh)..copied from Posix.Files to avoid dep. on posix library
483
484 \begin{code}
485 type FileStatus = ByteArray Int
486
487 getFileStatus :: FilePath -> IO FileStatus
488 getFileStatus name =
489     psToByteArrayST name                            `thenIO_Prim` \ path ->
490     newCharArray (0,``sizeof(struct stat)'')        `thenIO_Prim` \ bytes ->
491     _casm_ ``%r = stat(%0,(struct stat *)%1);'' path bytes
492                                                     `thenIO_Prim` \ rc ->
493     if rc == 0 then
494         unsafeFreezeByteArray bytes                 `thenIO_Prim` \ stat ->
495         return stat
496     else
497         fail (IOError Nothing SystemError "Directory.getFileStatus")
498
499 modificationTime :: FileStatus -> IO ClockTime
500 modificationTime stat =
501     malloc1                                                `thenIO_Prim` \ i1 ->
502     _casm_ ``((unsigned long *)%1)[0] = ((struct stat *)%0)->st_mtime;'' stat i1 `thenIO_Prim` \ () ->
503     cvtUnsigned i1                                         `thenIO_Prim` \ secs ->
504     return (TOD secs 0)
505   where
506     malloc1 = ST $ \ (S# s#) ->
507         case newIntArray# 1# s# of 
508           StateAndMutableByteArray# s2# barr# -> (MutableByteArray bnds barr#, S# s2#)
509
510     bnds = (0,1)
511     -- The C routine fills in an unsigned word.  We don't have `unsigned2Integer#,'
512     -- so we freeze the data bits and use them for an MP_INT structure.  Note that
513     -- zero is still handled specially, although (J# 1# 1# (ptr to 0#)) is probably
514     -- acceptable to gmp.
515
516     cvtUnsigned (MutableByteArray _ arr#) = ST $ \ (S# s#) ->
517         case readIntArray# arr# 0# s# of 
518           StateAndInt# s2# r# ->
519             if r# ==# 0# then
520                 (0, S# s2#)
521             else
522                 case unsafeFreezeByteArray# arr# s2# of
523                   StateAndByteArray# s3# frozen# -> (J# 1# 1# frozen#, S# s3#)
524
525 isDirectory :: FileStatus -> Bool
526 isDirectory stat = unsafePerformPrimIO $
527     _casm_ ``%r = S_ISDIR(((struct stat *)%0)->st_mode);'' stat >>= \ rc ->
528     return (rc /= 0)
529
530 isRegularFile :: FileStatus -> Bool
531 isRegularFile stat = unsafePerformPrimIO $
532     _casm_ ``%r = S_ISREG(((struct stat *)%0)->st_mode);'' stat >>= \ rc ->
533     return (rc /= 0)
534
535
536 \end{code}
537
538 \begin{code}
539 type FileMode = Word
540 ownerReadMode :: FileMode
541 ownerReadMode = ``S_IRUSR''
542
543 ownerWriteMode :: FileMode
544 ownerWriteMode = ``S_IWUSR''
545
546 ownerExecuteMode :: FileMode
547 ownerExecuteMode = ``S_IXUSR''
548
549 intersectFileMode :: FileMode -> FileMode -> FileMode
550 intersectFileMode (W# m1#) (W# m2#) = W# (m1# `and#` m2#)
551
552 fileMode :: FileStatus -> FileMode
553 fileMode stat = unsafePerformPrimIO $
554     _casm_ ``%r = ((struct stat *)%0)->st_mode;'' stat >>= \ mode ->
555     return mode
556
557 \end{code}