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