[project @ 1998-05-05 10:31:14 by sof]
[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> #-}
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 PrelUnsafe       ( unsafePerformIO )
46 import PrelArr
47 import PrelPack         ( unpackNBytesST )
48 import PrelForeign      ( Word(..) )
49 import PrelAddr
50 import Time             ( ClockTime(..) )
51
52 \end{code}
53
54 %*********************************************************
55 %*                                                      *
56 \subsection{Signatures}
57 %*                                                      *
58 %*********************************************************
59
60 \begin{code}
61 createDirectory         :: FilePath -> IO ()
62 removeDirectory         :: FilePath -> IO ()
63 removeFile              :: FilePath -> IO ()
64 renameDirectory         :: FilePath -> FilePath -> IO ()
65 renameFile              :: FilePath -> FilePath -> IO ()
66 getDirectoryContents    :: FilePath -> IO [FilePath]
67 getCurrentDirectory     :: IO FilePath
68 setCurrentDirectory     :: FilePath -> IO ()
69 doesFileExist           :: FilePath -> IO Bool
70 doesDirectoryExist      :: FilePath -> IO Bool
71 getPermissions          :: FilePath -> IO Permissions
72 setPermissions          :: FilePath -> Permissions -> IO ()
73 getModificationTime     :: FilePath -> IO ClockTime
74 \end{code}
75
76
77 %*********************************************************
78 %*                                                      *
79 \subsection{Permissions}
80 %*                                                      *
81 %*********************************************************
82
83 The @Permissions@ type is used to record whether certain
84 operations are permissible on a file/directory:
85 [to whom? - owner/group/world - the Report don't say much]
86
87 \begin{code}
88 data Permissions
89  = Permissions {
90     readable,   writeable, 
91     executable, searchable :: Bool 
92    } deriving (Eq, Ord, Read, Show)
93 \end{code}
94
95 %*********************************************************
96 %*                                                      *
97 \subsection{Implementation}
98 %*                                                      *
99 %*********************************************************
100
101 @createDirectory dir@ creates a new directory {\em dir} which is
102 initially empty, or as near to empty as the operating system
103 allows.
104
105 The operation may fail with:
106
107 \begin{itemize}
108 \item @isPermissionError@ / @PermissionDenied@
109 The process has insufficient privileges to perform the operation.
110 @[EROFS, EACCES]@
111 \item @isAlreadyExistsError@ / @AlreadyExists@
112 The operand refers to a directory that already exists.  
113 @ [EEXIST]@
114 \item @HardwareFault@
115 A physical I/O error has occurred.
116 @ [EIO]@
117 \item @InvalidArgument@
118 The operand is not a valid directory name.
119 @[ENAMETOOLONG, ELOOP]@
120 \item @NoSuchThing@
121 There is no path to the directory. 
122 @[ENOENT, ENOTDIR]@
123 \item @ResourceExhausted@
124 Insufficient resources (virtual memory, process file descriptors,
125 physical disk space, etc.) are available to perform the operation.
126 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
127 \item @InappropriateType@
128 The path refers to an existing non-directory object.
129 @[EEXIST]@
130 \end{itemize}
131
132 \begin{code}
133 createDirectory path = do
134     rc <- _ccall_ createDirectory 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 path = do
175     rc <- _ccall_ removeDirectory path
176     if rc == 0 then 
177         return ()
178      else 
179         constructErrorAndFailWithInfo "removeDirectory" path
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 = do
212     rc <- _ccall_ removeFile path
213     if rc == 0 then
214         return ()
215      else
216         constructErrorAndFailWithInfo "removeFile" path
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 = do
259     rc <- _ccall_ renameDirectory opath npath
260     if rc == 0 then
261         return ()
262      else
263         constructErrorAndFailWithInfo "renameDirectory" opath
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 = do
304     rc <- _ccall_ renameFile opath npath
305     if rc == 0 then
306         return ()
307      else
308         constructErrorAndFailWithInfo   "renameFile" opath
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 :: FilePath -> IO [FilePath]
338 getDirectoryContents path = do
339     dir <- _ccall_ openDir__ path
340     if dir == ``NULL'' 
341         then constructErrorAndFailWithInfo "getDirectoryContents" path
342         else loop dir
343   where
344     loop :: Addr -> IO [String]
345     loop dir  = do
346       dirent_ptr <- _ccall_ readDir__ dir
347       if (dirent_ptr::Addr) == ``NULL'' 
348        then do
349           -- readDir__ implicitly performs closedir() when the
350           -- end is reached.
351           return [] 
352        else do
353           str     <- _casm_ `` %r=(char*)((struct dirent*)%0)->d_name; '' dirent_ptr
354             -- not using the unpackCString function here, since we have to force
355             -- the unmarshalling of the directory entry right here as subsequent
356             -- calls to readdir() may overwrite it.
357           len     <- _ccall_ strlen str
358           entry   <- stToIO (unpackNBytesST str len)
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 = do
386     str <- _ccall_ getCurrentDirectory
387     if str /= ``NULL'' 
388         then do
389             len <- _ccall_ strlen str
390             pwd <- stToIO (unpackNBytesST str len)
391             _ccall_ free 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 path = do
425     rc <- _ccall_ setCurrentDirectory path
426     if rc == 0 
427         then return ()
428         else constructErrorAndFailWithInfo "setCurrentDirectory" path
429 \end{code}
430
431
432 \begin{code}
433 --doesFileExist :: FilePath -> IO Bool
434 doesFileExist name = do 
435   rc <- _ccall_ access name (``F_OK''::Int)
436   return (rc == 0)
437
438 --doesDirectoryExist :: FilePath -> IO Bool
439 doesDirectoryExist name = 
440  (getFileStatus name >>= \ st -> return (isDirectory st))  
441    `catch` 
442  (\ _ -> return False)
443
444 --getModificationTime :: FilePath -> IO ClockTime
445 getModificationTime name =
446  getFileStatus name >>= \ st ->
447  modificationTime st
448
449 --getPermissions :: FilePath -> IO Permissions
450 getPermissions name =
451   getFileStatus name >>= \ st ->
452   let
453    fm = fileMode st
454    isect v = intersectFileMode v fm == v
455   in
456   return (
457     Permissions {
458       readable   = isect ownerReadMode,
459       writeable  = isect ownerWriteMode,
460       executable = not (isDirectory st)   && isect ownerExecuteMode,
461       searchable = not (isRegularFile st) && isect ownerExecuteMode
462     }
463   )
464
465 --setPermissions :: FilePath -> Permissions -> IO ()
466 setPermissions name (Permissions r w e s) = do
467     let
468      read#  = case (if r then ownerReadMode else ``0'') of { W# x# -> x# }
469      write# = case (if w then ownerWriteMode else ``0'') of { W# x# -> x# }
470      exec#  = case (if e || s then ownerExecuteMode else ``0'') of { W# x# -> x# }
471
472      mode  = I# (word2Int# (read# `or#` write# `or#` exec#))
473
474     rc <- _ccall_ chmod name mode
475     if rc == 0
476         then return ()
477         else 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 = do
489     bytes <- stToIO (newCharArray (0,``sizeof(struct stat)''))
490     rc <- _casm_ ``%r = stat(%0,(struct stat *)%1);'' name bytes
491     if rc == 0 
492         then stToIO (unsafeFreezeByteArray bytes)
493         else fail (IOError Nothing SystemError "Directory.getFileStatus")
494
495 modificationTime :: FileStatus -> IO ClockTime
496 modificationTime stat = do
497     i1 <- malloc1
498     _casm_ ``((unsigned long *)%1)[0] = ((struct stat *)%0)->st_mtime;'' stat i1
499     secs <- cvtUnsigned i1
500     return (TOD secs 0)
501   where
502     malloc1 = IO $ \ s# ->
503         case newIntArray# 1# s# of 
504           StateAndMutableByteArray# s2# barr# -> 
505                 IOok 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           StateAndInt# s2# r# ->
516             if r# ==# 0# then
517                 IOok s2# 0
518             else
519                 case unsafeFreezeByteArray# arr# s2# of
520                   StateAndByteArray# s3# frozen# -> 
521                         IOok s3# (J# 1# 1# frozen#)
522
523 isDirectory :: FileStatus -> Bool
524 isDirectory stat = unsafePerformIO $ do
525     rc <- _casm_ ``%r = S_ISDIR(((struct stat *)%0)->st_mode);'' stat
526     return (rc /= 0)
527
528 isRegularFile :: FileStatus -> Bool
529 isRegularFile stat = unsafePerformIO $ do
530     rc <- _casm_ ``%r = S_ISREG(((struct stat *)%0)->st_mode);'' stat
531     return (rc /= 0)
532 \end{code}
533
534 \begin{code}
535 type FileMode = Word
536 ownerReadMode :: FileMode
537 ownerReadMode = ``S_IRUSR''
538
539 ownerWriteMode :: FileMode
540 ownerWriteMode = ``S_IWUSR''
541
542 ownerExecuteMode :: FileMode
543 ownerExecuteMode = ``S_IXUSR''
544
545 intersectFileMode :: FileMode -> FileMode -> FileMode
546 intersectFileMode (W# m1#) (W# m2#) = W# (m1# `and#` m2#)
547
548 fileMode :: FileStatus -> FileMode
549 fileMode stat = unsafePerformIO (
550         _casm_ ``%r = ((struct stat *)%0)->st_mode;'' stat)
551
552 \end{code}