[project @ 2004-07-30 20:33:54 by krasimir]
[haskell-directory.git] / System / Directory.hs
1 -----------------------------------------------------------------------------
2 -- |
3 -- Module      :  System.Directory
4 -- Copyright   :  (c) The University of Glasgow 2001
5 -- License     :  BSD-style (see the file libraries/base/LICENSE)
6 -- 
7 -- Maintainer  :  libraries@haskell.org
8 -- Stability   :  stable
9 -- Portability :  portable
10 --
11 -- System-independent interface to directory manipulation.
12 --
13 -----------------------------------------------------------------------------
14
15 module System.Directory 
16    ( 
17     -- $intro
18
19     -- * Actions on directories
20       createDirectory           -- :: FilePath -> IO ()
21     , removeDirectory           -- :: FilePath -> IO ()
22     , renameDirectory           -- :: FilePath -> FilePath -> IO ()
23
24     , getDirectoryContents      -- :: FilePath -> IO [FilePath]
25     , getCurrentDirectory       -- :: IO FilePath
26     , setCurrentDirectory       -- :: FilePath -> IO ()
27
28     -- * Actions on files
29     , removeFile                -- :: FilePath -> IO ()
30     , renameFile                -- :: FilePath -> FilePath -> IO ()
31     , copyFile                  -- :: FilePath -> FilePath -> IO ()
32
33     -- * Existence tests
34     , doesFileExist             -- :: FilePath -> IO Bool
35     , doesDirectoryExist        -- :: FilePath -> IO Bool
36
37     -- * Permissions
38
39     -- $permissions
40
41     , Permissions(
42         Permissions,
43         readable,               -- :: Permissions -> Bool
44         writable,               -- :: Permissions -> Bool
45         executable,             -- :: Permissions -> Bool
46         searchable              -- :: Permissions -> Bool
47       )
48
49     , getPermissions            -- :: FilePath -> IO Permissions
50     , setPermissions            -- :: FilePath -> Permissions -> IO ()
51
52     -- * Timestamps
53
54     , getModificationTime       -- :: FilePath -> IO ClockTime
55    ) where
56
57 #ifdef __NHC__
58 import Directory
59 #elif defined(__HUGS__)
60 import Hugs.Directory
61 #else
62
63 import Prelude
64
65 import Control.Exception       ( bracket )
66 import Control.Monad           ( when )
67 import System.Posix.Types
68 import System.Posix.Internals
69 import System.Time             ( ClockTime(..) )
70 import System.IO
71 import System.IO.Error
72 import Foreign
73 import Foreign.C
74
75 #ifdef __GLASGOW_HASKELL__
76 import GHC.IOBase       ( IOException(..), IOErrorType(..), ioException )
77 #endif
78
79 {- $intro
80 A directory contains a series of entries, each of which is a named
81 reference to a file system object (file, directory etc.).  Some
82 entries may be hidden, inaccessible, or have some administrative
83 function (e.g. `.' or `..' under POSIX
84 <http://www.opengroup.org/onlinepubs/007904975/toc.htm>), but in 
85 this standard all such entries are considered to form part of the
86 directory contents. Entries in sub-directories are not, however,
87 considered to form part of the directory contents.
88
89 Each file system object is referenced by a /path/.  There is
90 normally at least one absolute path to each file system object.  In
91 some operating systems, it may also be possible to have paths which
92 are relative to the current directory.
93 -}
94
95 -----------------------------------------------------------------------------
96 -- Permissions
97
98 {- $permissions
99
100  The 'Permissions' type is used to record whether certain operations are
101  permissible on a file\/directory. 'getPermissions' and 'setPermissions'
102  get and set these permissions, respectively. Permissions apply both to
103  files and directories. For directories, the executable field will be
104  'False', and for files the searchable field will be 'False'. Note that
105  directories may be searchable without being readable, if permission has
106  been given to use them as part of a path, but not to examine the 
107  directory contents.
108
109 Note that to change some, but not all permissions, a construct on the following lines must be used. 
110
111 >  makeReadable f = do
112 >     p <- getPermissions f
113 >     setPermissions f (p {readable = True})
114
115 -}
116
117 data Permissions
118  = Permissions {
119     readable,   writable, 
120     executable, searchable :: Bool 
121    } deriving (Eq, Ord, Read, Show)
122
123 {- |The 'getPermissions' operation returns the
124 permissions for the file or directory.
125
126 The operation may fail with:
127
128 * 'isPermissionError' if the user is not permitted to access
129   the permissions; or
130
131 * 'isDoesNotExistError' if the file or directory does not exist.
132
133 -}
134
135 getPermissions :: FilePath -> IO Permissions
136 getPermissions name = do
137   withCString name $ \s -> do
138   read  <- c_access s r_OK
139   write <- c_access s w_OK
140   exec  <- c_access s x_OK
141   withFileStatus "getPermissions" name $ \st -> do
142   is_dir <- isDirectory st
143   return (
144     Permissions {
145       readable   = read  == 0,
146       writable   = write == 0,
147       executable = not is_dir && exec == 0,
148       searchable = is_dir && exec == 0
149     }
150    )
151
152 {- |The 'setPermissions' operation sets the
153 permissions for the file or directory.
154
155 The operation may fail with:
156
157 * 'isPermissionError' if the user is not permitted to set
158   the permissions; or
159
160 * 'isDoesNotExistError' if the file or directory does not exist.
161
162 -}
163
164 setPermissions :: FilePath -> Permissions -> IO ()
165 setPermissions name (Permissions r w e s) = do
166   allocaBytes sizeof_stat $ \ p_stat -> do
167   withCString name $ \p_name -> do
168     throwErrnoIfMinus1_ "setPermissions" $ do
169       c_stat p_name p_stat
170       mode <- st_mode p_stat
171       let mode1 = modifyBit r mode s_IRUSR
172       let mode2 = modifyBit w mode1 s_IWUSR
173       let mode3 = modifyBit (e || s) mode2 s_IXUSR
174       c_chmod p_name mode3
175
176  where
177    modifyBit :: Bool -> CMode -> CMode -> CMode
178    modifyBit False m b = m .&. (complement b)
179    modifyBit True  m b = m .|. b
180
181 -----------------------------------------------------------------------------
182 -- Implementation
183
184 {- |@'createDirectory' dir@ creates a new directory @dir@ which is
185 initially empty, or as near to empty as the operating system
186 allows.
187
188 The operation may fail with:
189
190 * 'isPermissionError' \/ 'PermissionDenied'
191 The process has insufficient privileges to perform the operation.
192 @[EROFS, EACCES]@
193
194 * 'isAlreadyExistsError' \/ 'AlreadyExists'
195 The operand refers to a directory that already exists.  
196 @ [EEXIST]@
197
198 * 'HardwareFault'
199 A physical I\/O error has occurred.
200 @[EIO]@
201
202 * 'InvalidArgument'
203 The operand is not a valid directory name.
204 @[ENAMETOOLONG, ELOOP]@
205
206 * 'NoSuchThing'
207 There is no path to the directory. 
208 @[ENOENT, ENOTDIR]@
209
210 * 'ResourceExhausted'
211 Insufficient resources (virtual memory, process file descriptors,
212 physical disk space, etc.) are available to perform the operation.
213 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
214
215 * 'InappropriateType'
216 The path refers to an existing non-directory object.
217 @[EEXIST]@
218
219 -}
220
221 createDirectory :: FilePath -> IO ()
222 createDirectory path = do
223     withCString path $ \s -> do
224       throwErrnoIfMinus1Retry_ "createDirectory" $
225         mkdir s 0o777
226
227 {- | @'removeDirectory' dir@ removes an existing directory /dir/.  The
228 implementation may specify additional constraints which must be
229 satisfied before a directory can be removed (e.g. the directory has to
230 be empty, or may not be in use by other processes).  It is not legal
231 for an implementation to partially remove a directory unless the
232 entire directory is removed. A conformant implementation need not
233 support directory removal in all situations (e.g. removal of the root
234 directory).
235
236 The operation may fail with:
237
238 * 'HardwareFault'
239 A physical I\/O error has occurred.
240 EIO
241
242 * 'InvalidArgument'
243 The operand is not a valid directory name.
244 [ENAMETOOLONG, ELOOP]
245
246 * 'isDoesNotExistError' \/ 'NoSuchThing'
247 The directory does not exist. 
248 @[ENOENT, ENOTDIR]@
249
250 * 'isPermissionError' \/ 'PermissionDenied'
251 The process has insufficient privileges to perform the operation.
252 @[EROFS, EACCES, EPERM]@
253
254 * 'UnsatisfiedConstraints'
255 Implementation-dependent constraints are not satisfied.  
256 @[EBUSY, ENOTEMPTY, EEXIST]@
257
258 * 'UnsupportedOperation'
259 The implementation does not support removal in this situation.
260 @[EINVAL]@
261
262 * 'InappropriateType'
263 The operand refers to an existing non-directory object.
264 @[ENOTDIR]@
265
266 -}
267
268 removeDirectory :: FilePath -> IO ()
269 removeDirectory path = do
270   modifyIOError (`ioeSetFileName` path) $
271     withCString path $ \s ->
272        throwErrnoIfMinus1Retry_ "removeDirectory" (c_rmdir s)
273
274 {- |'removeFile' /file/ removes the directory entry for an existing file
275 /file/, where /file/ is not itself a directory. The
276 implementation may specify additional constraints which must be
277 satisfied before a file can be removed (e.g. the file may not be in
278 use by other processes).
279
280 The operation may fail with:
281
282 * 'HardwareFault'
283 A physical I\/O error has occurred.
284 @[EIO]@
285
286 * 'InvalidArgument'
287 The operand is not a valid file name.
288 @[ENAMETOOLONG, ELOOP]@
289
290 * 'isDoesNotExistError' \/ 'NoSuchThing'
291 The file does not exist. 
292 @[ENOENT, ENOTDIR]@
293
294 * 'isPermissionError' \/ 'PermissionDenied'
295 The process has insufficient privileges to perform the operation.
296 @[EROFS, EACCES, EPERM]@
297
298 * 'UnsatisfiedConstraints'
299 Implementation-dependent constraints are not satisfied.  
300 @[EBUSY]@
301
302 * 'InappropriateType'
303 The operand refers to an existing directory.
304 @[EPERM, EINVAL]@
305
306 -}
307
308 removeFile :: FilePath -> IO ()
309 removeFile path = do
310   modifyIOError (`ioeSetFileName` path) $
311     withCString path $ \s ->
312       throwErrnoIfMinus1Retry_ "removeFile" (c_unlink s)
313
314 {- |@'renameDirectory' old new@ changes the name of an existing
315 directory from /old/ to /new/.  If the /new/ directory
316 already exists, it is atomically replaced by the /old/ directory.
317 If the /new/ directory is neither the /old/ directory nor an
318 alias of the /old/ directory, it is removed as if by
319 'removeDirectory'.  A conformant implementation need not support
320 renaming directories in all situations (e.g. renaming to an existing
321 directory, or across different physical devices), but the constraints
322 must be documented.
323
324 On Win32 platforms, @renameDirectory@ fails if the /new/ directory already
325 exists.
326
327 The operation may fail with:
328
329 * 'HardwareFault'
330 A physical I\/O error has occurred.
331 @[EIO]@
332
333 * 'InvalidArgument'
334 Either operand is not a valid directory name.
335 @[ENAMETOOLONG, ELOOP]@
336
337 * 'isDoesNotExistError' \/ 'NoSuchThing'
338 The original directory does not exist, or there is no path to the target.
339 @[ENOENT, ENOTDIR]@
340
341 * 'isPermissionError' \/ 'PermissionDenied'
342 The process has insufficient privileges to perform the operation.
343 @[EROFS, EACCES, EPERM]@
344
345 * 'ResourceExhausted'
346 Insufficient resources are available to perform the operation.  
347 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
348
349 * 'UnsatisfiedConstraints'
350 Implementation-dependent constraints are not satisfied.
351 @[EBUSY, ENOTEMPTY, EEXIST]@
352
353 * 'UnsupportedOperation'
354 The implementation does not support renaming in this situation.
355 @[EINVAL, EXDEV]@
356
357 * 'InappropriateType'
358 Either path refers to an existing non-directory object.
359 @[ENOTDIR, EISDIR]@
360
361 -}
362
363 renameDirectory :: FilePath -> FilePath -> IO ()
364 renameDirectory opath npath =
365    withFileStatus "renameDirectory" opath $ \st -> do
366    is_dir <- isDirectory st
367    if (not is_dir)
368         then ioException (IOError Nothing InappropriateType "renameDirectory"
369                             ("not a directory") (Just opath))
370         else do
371
372    withCString opath $ \s1 ->
373      withCString npath $ \s2 ->
374         throwErrnoIfMinus1Retry_ "renameDirectory" (c_rename s1 s2)
375
376 {- |@'renameFile' old new@ changes the name of an existing file system
377 object from /old/ to /new/.  If the /new/ object already
378 exists, it is atomically replaced by the /old/ object.  Neither
379 path may refer to an existing directory.  A conformant implementation
380 need not support renaming files in all situations (e.g. renaming
381 across different physical devices), but the constraints must be
382 documented.
383
384 The operation may fail with:
385
386 * 'HardwareFault'
387 A physical I\/O error has occurred.
388 @[EIO]@
389
390 * 'InvalidArgument'
391 Either operand is not a valid file name.
392 @[ENAMETOOLONG, ELOOP]@
393
394 * 'isDoesNotExistError' \/ 'NoSuchThing'
395 The original file does not exist, or there is no path to the target.
396 @[ENOENT, ENOTDIR]@
397
398 * 'isPermissionError' \/ 'PermissionDenied'
399 The process has insufficient privileges to perform the operation.
400 @[EROFS, EACCES, EPERM]@
401
402 * 'ResourceExhausted'
403 Insufficient resources are available to perform the operation.  
404 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
405
406 * 'UnsatisfiedConstraints'
407 Implementation-dependent constraints are not satisfied.
408 @[EBUSY]@
409
410 * 'UnsupportedOperation'
411 The implementation does not support renaming in this situation.
412 @[EXDEV]@
413
414 * 'InappropriateType'
415 Either path refers to an existing directory.
416 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
417
418 -}
419
420 renameFile :: FilePath -> FilePath -> IO ()
421 renameFile opath npath =
422    withFileOrSymlinkStatus "renameFile" opath $ \st -> do
423    is_dir <- isDirectory st
424    if is_dir
425         then ioException (IOError Nothing InappropriateType "renameFile"
426                            "is a directory" (Just opath))
427         else do
428
429     withCString opath $ \s1 ->
430       withCString npath $ \s2 ->
431          throwErrnoIfMinus1Retry_ "renameFile" (c_rename s1 s2)
432
433 {- |@'copyFile' old new@ copies the existing file from /old/ to /new/.
434 If the /new/ file already exists, it is atomically replaced by the /old/ file.
435 Neither path may refer to an existing directory.
436 -}
437 copyFile :: FilePath -> FilePath -> IO ()
438 copyFile fromFPath toFPath = handle (changeFunName) $
439         (bracket (openBinaryFile fromFPath ReadMode) hClose $ \hFrom ->
440          bracket (openBinaryFile toFPath WriteMode) hClose $ \hTo ->
441          allocaBytes bufferSize $ \buffer ->
442                 copyContents hFrom hTo buffer) `catch` (ioError . changeFunName)
443         where
444                 bufferSize = 1024
445                 
446                 changeFunName (IOError h iot fun str mb_fp) = IOError h iot "copyFile" str mb_fp
447                 changeFunName e                             = e
448                 
449                 copyContents hFrom hTo buffer = do
450                         count <- hGetBuf hFrom buffer bufferSize
451                         when (count > 0) $ do
452                                 hPutBuf hTo buffer count
453                                 copyContents hFrom hTo buffer
454
455
456 {- |@'getDirectoryContents' dir@ returns a list of /all/ entries
457 in /dir/. 
458
459 The operation may fail with:
460
461 * 'HardwareFault'
462 A physical I\/O error has occurred.
463 @[EIO]@
464
465 * 'InvalidArgument'
466 The operand is not a valid directory name.
467 @[ENAMETOOLONG, ELOOP]@
468
469 * 'isDoesNotExistError' \/ 'NoSuchThing'
470 The directory does not exist.
471 @[ENOENT, ENOTDIR]@
472
473 * 'isPermissionError' \/ 'PermissionDenied'
474 The process has insufficient privileges to perform the operation.
475 @[EACCES]@
476
477 * 'ResourceExhausted'
478 Insufficient resources are available to perform the operation.
479 @[EMFILE, ENFILE]@
480
481 * 'InappropriateType'
482 The path refers to an existing non-directory object.
483 @[ENOTDIR]@
484
485 -}
486
487 getDirectoryContents :: FilePath -> IO [FilePath]
488 getDirectoryContents path = do
489   modifyIOError (`ioeSetFileName` path) $
490    alloca $ \ ptr_dEnt ->
491      bracket
492         (withCString path $ \s -> 
493            throwErrnoIfNullRetry desc (c_opendir s))
494         (\p -> throwErrnoIfMinus1_ desc (c_closedir p))
495         (\p -> loop ptr_dEnt p)
496   where
497     desc = "getDirectoryContents"
498
499     loop :: Ptr (Ptr CDirent) -> Ptr CDir -> IO [String]
500     loop ptr_dEnt dir = do
501       resetErrno
502       r <- readdir dir ptr_dEnt
503       if (r == 0)
504          then do
505                  dEnt    <- peek ptr_dEnt
506                  if (dEnt == nullPtr)
507                    then return []
508                    else do
509                     entry   <- (d_name dEnt >>= peekCString)
510                     freeDirEnt dEnt
511                     entries <- loop ptr_dEnt dir
512                     return (entry:entries)
513          else do errno <- getErrno
514                  if (errno == eINTR) then loop ptr_dEnt dir else do
515                  let (Errno eo) = errno
516                  if (eo == end_of_dir)
517                     then return []
518                     else throwErrno desc
519
520
521
522 {- |If the operating system has a notion of current directories,
523 'getCurrentDirectory' returns an absolute path to the
524 current directory of the calling process.
525
526 The operation may fail with:
527
528 * 'HardwareFault'
529 A physical I\/O error has occurred.
530 @[EIO]@
531
532 * 'isDoesNotExistError' \/ 'NoSuchThing'
533 There is no path referring to the current directory.
534 @[EPERM, ENOENT, ESTALE...]@
535
536 * 'isPermissionError' \/ 'PermissionDenied'
537 The process has insufficient privileges to perform the operation.
538 @[EACCES]@
539
540 * 'ResourceExhausted'
541 Insufficient resources are available to perform the operation.
542
543 * 'UnsupportedOperation'
544 The operating system has no notion of current directory.
545
546 -}
547
548 getCurrentDirectory :: IO FilePath
549 getCurrentDirectory = do
550   p <- mallocBytes long_path_size
551   go p long_path_size
552   where go p bytes = do
553           p' <- c_getcwd p (fromIntegral bytes)
554           if p' /= nullPtr 
555              then do s <- peekCString p'
556                      free p'
557                      return s
558              else do errno <- getErrno
559                      if errno == eRANGE
560                         then do let bytes' = bytes * 2
561                                 p' <- reallocBytes p bytes'
562                                 go p' bytes'
563                         else throwErrno "getCurrentDirectory"
564
565 {- |If the operating system has a notion of current directories,
566 @'setCurrentDirectory' dir@ changes the current
567 directory of the calling process to /dir/.
568
569 The operation may fail with:
570
571 * 'HardwareFault'
572 A physical I\/O error has occurred.
573 @[EIO]@
574
575 * 'InvalidArgument'
576 The operand is not a valid directory name.
577 @[ENAMETOOLONG, ELOOP]@
578
579 * 'isDoesNotExistError' \/ 'NoSuchThing'
580 The directory does not exist.
581 @[ENOENT, ENOTDIR]@
582
583 * 'isPermissionError' \/ 'PermissionDenied'
584 The process has insufficient privileges to perform the operation.
585 @[EACCES]@
586
587 * 'UnsupportedOperation'
588 The operating system has no notion of current directory, or the
589 current directory cannot be dynamically changed.
590
591 * 'InappropriateType'
592 The path refers to an existing non-directory object.
593 @[ENOTDIR]@
594
595 -}
596
597 setCurrentDirectory :: FilePath -> IO ()
598 setCurrentDirectory path = do
599   modifyIOError (`ioeSetFileName` path) $
600     withCString path $ \s -> 
601        throwErrnoIfMinus1Retry_ "setCurrentDirectory" (c_chdir s)
602         -- ToDo: add path to error
603
604 {- |The operation 'doesDirectoryExist' returns 'True' if the argument file
605 exists and is a directory, and 'False' otherwise.
606 -}
607
608 doesDirectoryExist :: FilePath -> IO Bool
609 doesDirectoryExist name = 
610  catch
611    (withFileStatus "doesDirectoryExist" name $ \st -> isDirectory st)
612    (\ _ -> return False)
613
614 {- |The operation 'doesFileExist' returns 'True'
615 if the argument file exists and is not a directory, and 'False' otherwise.
616 -}
617
618 doesFileExist :: FilePath -> IO Bool
619 doesFileExist name = do 
620  catch
621    (withFileStatus "doesFileExist" name $ \st -> do b <- isDirectory st; return (not b))
622    (\ _ -> return False)
623
624 {- |The 'getModificationTime' operation returns the
625 clock time at which the file or directory was last modified.
626
627 The operation may fail with:
628
629 * 'isPermissionError' if the user is not permitted to access
630   the modification time; or
631
632 * 'isDoesNotExistError' if the file or directory does not exist.
633
634 -}
635
636 getModificationTime :: FilePath -> IO ClockTime
637 getModificationTime name =
638  withFileStatus "getModificationTime" name $ \ st ->
639  modificationTime st
640
641 withFileStatus :: String -> FilePath -> (Ptr CStat -> IO a) -> IO a
642 withFileStatus loc name f = do
643   modifyIOError (`ioeSetFileName` name) $
644     allocaBytes sizeof_stat $ \p ->
645       withCString (fileNameEndClean name) $ \s -> do
646         throwErrnoIfMinus1Retry_ loc (c_stat s p)
647         f p
648
649 withFileOrSymlinkStatus :: String -> FilePath -> (Ptr CStat -> IO a) -> IO a
650 withFileOrSymlinkStatus loc name f = do
651   modifyIOError (`ioeSetFileName` name) $
652     allocaBytes sizeof_stat $ \p ->
653       withCString name $ \s -> do
654         throwErrnoIfMinus1Retry_ loc (lstat s p)
655         f p
656
657 modificationTime :: Ptr CStat -> IO ClockTime
658 modificationTime stat = do
659     mtime <- st_mtime stat
660     let realToInteger = round . realToFrac :: Real a => a -> Integer
661     return (TOD (realToInteger (mtime :: CTime)) 0)
662     
663 isDirectory :: Ptr CStat -> IO Bool
664 isDirectory stat = do
665   mode <- st_mode stat
666   return (s_isdir mode)
667
668 fileNameEndClean :: String -> String
669 fileNameEndClean name = 
670   if i > 0 && (ec == '\\' || ec == '/') then 
671      fileNameEndClean (take i name)
672    else
673      name
674   where
675       i  = (length name) - 1
676       ec = name !! i
677
678 foreign import ccall unsafe "__hscore_long_path_size"
679   long_path_size :: Int
680
681 foreign import ccall unsafe "__hscore_R_OK" r_OK :: CMode
682 foreign import ccall unsafe "__hscore_W_OK" w_OK :: CMode
683 foreign import ccall unsafe "__hscore_X_OK" x_OK :: CMode
684
685 foreign import ccall unsafe "__hscore_S_IRUSR" s_IRUSR :: CMode
686 foreign import ccall unsafe "__hscore_S_IWUSR" s_IWUSR :: CMode
687 foreign import ccall unsafe "__hscore_S_IXUSR" s_IXUSR :: CMode
688
689 #endif