2 -- XXX We get some warnings on Windows
4 -----------------------------------------------------------------------------
6 -- Module : System.Directory
7 -- Copyright : (c) The University of Glasgow 2001
8 -- License : BSD-style (see the file libraries/base/LICENSE)
10 -- Maintainer : libraries@haskell.org
12 -- Portability : portable
14 -- System-independent interface to directory manipulation.
16 -----------------------------------------------------------------------------
18 module System.Directory
22 -- * Actions on directories
23 createDirectory -- :: FilePath -> IO ()
24 , createDirectoryIfMissing -- :: Bool -> FilePath -> IO ()
25 , removeDirectory -- :: FilePath -> IO ()
26 , removeDirectoryRecursive -- :: FilePath -> IO ()
27 , renameDirectory -- :: FilePath -> FilePath -> IO ()
29 , getDirectoryContents -- :: FilePath -> IO [FilePath]
30 , getCurrentDirectory -- :: IO FilePath
31 , setCurrentDirectory -- :: FilePath -> IO ()
33 -- * Pre-defined directories
35 , getAppUserDataDirectory
36 , getUserDocumentsDirectory
37 , getTemporaryDirectory
40 , removeFile -- :: FilePath -> IO ()
41 , renameFile -- :: FilePath -> FilePath -> IO ()
42 , copyFile -- :: FilePath -> FilePath -> IO ()
45 , makeRelativeToCurrentDirectory
49 , doesFileExist -- :: FilePath -> IO Bool
50 , doesDirectoryExist -- :: FilePath -> IO Bool
58 readable, -- :: Permissions -> Bool
59 writable, -- :: Permissions -> Bool
60 executable, -- :: Permissions -> Bool
61 searchable -- :: Permissions -> Bool
64 , getPermissions -- :: FilePath -> IO Permissions
65 , setPermissions -- :: FilePath -> Permissions -> IO ()
69 , getModificationTime -- :: FilePath -> IO ClockTime
72 import Prelude hiding ( catch )
73 import qualified Prelude
75 import System.Environment ( getEnv )
76 import System.FilePath
78 import System.IO.Error hiding ( catch, try )
79 import Control.Monad ( when, unless )
80 import Control.Exception.Base
84 import System (system)
94 {-# CFILES cbits/directory.c #-}
96 #ifdef __GLASGOW_HASKELL__
97 import System.Posix.Types
98 import System.Posix.Internals
99 import System.Time ( ClockTime(..) )
101 import GHC.IOBase ( IOException(..), IOErrorType(..), ioException )
103 #ifdef mingw32_HOST_OS
104 import qualified System.Win32
106 import qualified System.Posix
110 A directory contains a series of entries, each of which is a named
111 reference to a file system object (file, directory etc.). Some
112 entries may be hidden, inaccessible, or have some administrative
113 function (e.g. `.' or `..' under POSIX
114 <http://www.opengroup.org/onlinepubs/009695399/>), but in
115 this standard all such entries are considered to form part of the
116 directory contents. Entries in sub-directories are not, however,
117 considered to form part of the directory contents.
119 Each file system object is referenced by a /path/. There is
120 normally at least one absolute path to each file system object. In
121 some operating systems, it may also be possible to have paths which
122 are relative to the current directory.
125 -----------------------------------------------------------------------------
130 The 'Permissions' type is used to record whether certain operations are
131 permissible on a file\/directory. 'getPermissions' and 'setPermissions'
132 get and set these permissions, respectively. Permissions apply both to
133 files and directories. For directories, the executable field will be
134 'False', and for files the searchable field will be 'False'. Note that
135 directories may be searchable without being readable, if permission has
136 been given to use them as part of a path, but not to examine the
139 Note that to change some, but not all permissions, a construct on the following lines must be used.
141 > makeReadable f = do
142 > p <- getPermissions f
143 > setPermissions f (p {readable = True})
150 executable, searchable :: Bool
151 } deriving (Eq, Ord, Read, Show)
153 {- |The 'getPermissions' operation returns the
154 permissions for the file or directory.
156 The operation may fail with:
158 * 'isPermissionError' if the user is not permitted to access
161 * 'isDoesNotExistError' if the file or directory does not exist.
165 getPermissions :: FilePath -> IO Permissions
166 getPermissions name = do
167 withCString name $ \s -> do
168 #ifdef mingw32_HOST_OS
169 -- stat() does a better job of guessing the permissions on Windows
170 -- than access() does. e.g. for execute permission, it looks at the
171 -- filename extension :-)
173 -- I tried for a while to do this properly, using the Windows security API,
174 -- and eventually gave up. getPermissions is a flawed API anyway. -- SimonM
175 allocaBytes sizeof_stat $ \ p_stat -> do
176 throwErrnoIfMinus1_ "getPermissions" $ c_stat s p_stat
177 mode <- st_mode p_stat
178 let usr_read = mode .&. s_IRUSR
179 let usr_write = mode .&. s_IWUSR
180 let usr_exec = mode .&. s_IXUSR
181 let is_dir = mode .&. s_IFDIR
184 readable = usr_read /= 0,
185 writable = usr_write /= 0,
186 executable = is_dir == 0 && usr_exec /= 0,
187 searchable = is_dir /= 0 && usr_exec /= 0
191 read_ok <- c_access s r_OK
192 write_ok <- c_access s w_OK
193 exec_ok <- c_access s x_OK
194 withFileStatus "getPermissions" name $ \st -> do
195 is_dir <- isDirectory st
198 readable = read_ok == 0,
199 writable = write_ok == 0,
200 executable = not is_dir && exec_ok == 0,
201 searchable = is_dir && exec_ok == 0
206 {- |The 'setPermissions' operation sets the
207 permissions for the file or directory.
209 The operation may fail with:
211 * 'isPermissionError' if the user is not permitted to set
214 * 'isDoesNotExistError' if the file or directory does not exist.
218 setPermissions :: FilePath -> Permissions -> IO ()
219 setPermissions name (Permissions r w e s) = do
220 allocaBytes sizeof_stat $ \ p_stat -> do
221 withCString name $ \p_name -> do
222 throwErrnoIfMinus1_ "setPermissions" $ do
224 mode <- st_mode p_stat
225 let mode1 = modifyBit r mode s_IRUSR
226 let mode2 = modifyBit w mode1 s_IWUSR
227 let mode3 = modifyBit (e || s) mode2 s_IXUSR
231 modifyBit :: Bool -> CMode -> CMode -> CMode
232 modifyBit False m b = m .&. (complement b)
233 modifyBit True m b = m .|. b
236 copyPermissions :: FilePath -> FilePath -> IO ()
237 copyPermissions source dest = do
238 allocaBytes sizeof_stat $ \ p_stat -> do
239 withCString source $ \p_source -> do
240 withCString dest $ \p_dest -> do
241 throwErrnoIfMinus1_ "copyPermissions" $ c_stat p_source p_stat
242 mode <- st_mode p_stat
243 throwErrnoIfMinus1_ "copyPermissions" $ c_chmod p_dest mode
245 -----------------------------------------------------------------------------
248 {- |@'createDirectory' dir@ creates a new directory @dir@ which is
249 initially empty, or as near to empty as the operating system
252 The operation may fail with:
254 * 'isPermissionError' \/ 'PermissionDenied'
255 The process has insufficient privileges to perform the operation.
258 * 'isAlreadyExistsError' \/ 'AlreadyExists'
259 The operand refers to a directory that already exists.
263 A physical I\/O error has occurred.
267 The operand is not a valid directory name.
268 @[ENAMETOOLONG, ELOOP]@
271 There is no path to the directory.
274 * 'ResourceExhausted'
275 Insufficient resources (virtual memory, process file descriptors,
276 physical disk space, etc.) are available to perform the operation.
277 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
279 * 'InappropriateType'
280 The path refers to an existing non-directory object.
285 createDirectory :: FilePath -> IO ()
286 createDirectory path = do
287 #ifdef mingw32_HOST_OS
288 System.Win32.createDirectory path Nothing
290 System.Posix.createDirectory path 0o777
293 #else /* !__GLASGOW_HASKELL__ */
295 copyPermissions :: FilePath -> FilePath -> IO ()
296 copyPermissions fromFPath toFPath
297 = getPermissions fromFPath >>= setPermissions toFPath
301 -- | @'createDirectoryIfMissing' parents dir@ creates a new directory
302 -- @dir@ if it doesn\'t exist. If the first argument is 'True'
303 -- the function will also create all parent directories if they are missing.
304 createDirectoryIfMissing :: Bool -- ^ Create its parents too?
305 -> FilePath -- ^ The path to the directory you want to make
307 createDirectoryIfMissing parents file = do
308 b <- doesDirectoryExist file
309 case (b,parents, file) of
310 (_, _, "") -> return ()
311 (True, _, _) -> return ()
312 (_, True, _) -> mapM_ (createDirectoryIfMissing False) $ mkParents file
313 (_, False, _) -> createDirectory file
314 where mkParents = scanl1 (</>) . splitDirectories . normalise
316 #if __GLASGOW_HASKELL__
317 {- | @'removeDirectory' dir@ removes an existing directory /dir/. The
318 implementation may specify additional constraints which must be
319 satisfied before a directory can be removed (e.g. the directory has to
320 be empty, or may not be in use by other processes). It is not legal
321 for an implementation to partially remove a directory unless the
322 entire directory is removed. A conformant implementation need not
323 support directory removal in all situations (e.g. removal of the root
326 The operation may fail with:
329 A physical I\/O error has occurred.
333 The operand is not a valid directory name.
334 [ENAMETOOLONG, ELOOP]
336 * 'isDoesNotExistError' \/ 'NoSuchThing'
337 The directory does not exist.
340 * 'isPermissionError' \/ 'PermissionDenied'
341 The process has insufficient privileges to perform the operation.
342 @[EROFS, EACCES, EPERM]@
344 * 'UnsatisfiedConstraints'
345 Implementation-dependent constraints are not satisfied.
346 @[EBUSY, ENOTEMPTY, EEXIST]@
348 * 'UnsupportedOperation'
349 The implementation does not support removal in this situation.
352 * 'InappropriateType'
353 The operand refers to an existing non-directory object.
358 removeDirectory :: FilePath -> IO ()
359 removeDirectory path =
360 #ifdef mingw32_HOST_OS
361 System.Win32.removeDirectory path
363 System.Posix.removeDirectory path
368 -- | @'removeDirectoryRecursive' dir@ removes an existing directory /dir/
369 -- together with its content and all subdirectories. Be careful,
370 -- if the directory contains symlinks, the function will follow them.
371 removeDirectoryRecursive :: FilePath -> IO ()
372 removeDirectoryRecursive startLoc = do
373 cont <- getDirectoryContents startLoc
374 sequence_ [rm (startLoc </> x) | x <- cont, x /= "." && x /= ".."]
375 removeDirectory startLoc
377 rm :: FilePath -> IO ()
378 rm f = do temp <- try (removeFile f)
380 Left e -> do isDir <- doesDirectoryExist f
381 -- If f is not a directory, re-throw the error
382 unless isDir $ throw (e :: SomeException)
383 removeDirectoryRecursive f
386 #if __GLASGOW_HASKELL__
387 {- |'removeFile' /file/ removes the directory entry for an existing file
388 /file/, where /file/ is not itself a directory. The
389 implementation may specify additional constraints which must be
390 satisfied before a file can be removed (e.g. the file may not be in
391 use by other processes).
393 The operation may fail with:
396 A physical I\/O error has occurred.
400 The operand is not a valid file name.
401 @[ENAMETOOLONG, ELOOP]@
403 * 'isDoesNotExistError' \/ 'NoSuchThing'
404 The file does not exist.
407 * 'isPermissionError' \/ 'PermissionDenied'
408 The process has insufficient privileges to perform the operation.
409 @[EROFS, EACCES, EPERM]@
411 * 'UnsatisfiedConstraints'
412 Implementation-dependent constraints are not satisfied.
415 * 'InappropriateType'
416 The operand refers to an existing directory.
421 removeFile :: FilePath -> IO ()
424 System.Win32.deleteFile path
426 System.Posix.removeLink path
429 {- |@'renameDirectory' old new@ changes the name of an existing
430 directory from /old/ to /new/. If the /new/ directory
431 already exists, it is atomically replaced by the /old/ directory.
432 If the /new/ directory is neither the /old/ directory nor an
433 alias of the /old/ directory, it is removed as if by
434 'removeDirectory'. A conformant implementation need not support
435 renaming directories in all situations (e.g. renaming to an existing
436 directory, or across different physical devices), but the constraints
439 On Win32 platforms, @renameDirectory@ fails if the /new/ directory already
442 The operation may fail with:
445 A physical I\/O error has occurred.
449 Either operand is not a valid directory name.
450 @[ENAMETOOLONG, ELOOP]@
452 * 'isDoesNotExistError' \/ 'NoSuchThing'
453 The original directory does not exist, or there is no path to the target.
456 * 'isPermissionError' \/ 'PermissionDenied'
457 The process has insufficient privileges to perform the operation.
458 @[EROFS, EACCES, EPERM]@
460 * 'ResourceExhausted'
461 Insufficient resources are available to perform the operation.
462 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
464 * 'UnsatisfiedConstraints'
465 Implementation-dependent constraints are not satisfied.
466 @[EBUSY, ENOTEMPTY, EEXIST]@
468 * 'UnsupportedOperation'
469 The implementation does not support renaming in this situation.
472 * 'InappropriateType'
473 Either path refers to an existing non-directory object.
478 renameDirectory :: FilePath -> FilePath -> IO ()
479 renameDirectory opath npath =
480 -- XXX this test isn't performed atomically with the following rename
481 withFileStatus "renameDirectory" opath $ \st -> do
482 is_dir <- isDirectory st
484 then ioException (IOError Nothing InappropriateType "renameDirectory"
485 ("not a directory") (Just opath))
487 #ifdef mingw32_HOST_OS
488 System.Win32.moveFileEx opath npath System.Win32.mOVEFILE_REPLACE_EXISTING
490 System.Posix.rename opath npath
493 {- |@'renameFile' old new@ changes the name of an existing file system
494 object from /old/ to /new/. If the /new/ object already
495 exists, it is atomically replaced by the /old/ object. Neither
496 path may refer to an existing directory. A conformant implementation
497 need not support renaming files in all situations (e.g. renaming
498 across different physical devices), but the constraints must be
501 The operation may fail with:
504 A physical I\/O error has occurred.
508 Either operand is not a valid file name.
509 @[ENAMETOOLONG, ELOOP]@
511 * 'isDoesNotExistError' \/ 'NoSuchThing'
512 The original file does not exist, or there is no path to the target.
515 * 'isPermissionError' \/ 'PermissionDenied'
516 The process has insufficient privileges to perform the operation.
517 @[EROFS, EACCES, EPERM]@
519 * 'ResourceExhausted'
520 Insufficient resources are available to perform the operation.
521 @[EDQUOT, ENOSPC, ENOMEM, EMLINK]@
523 * 'UnsatisfiedConstraints'
524 Implementation-dependent constraints are not satisfied.
527 * 'UnsupportedOperation'
528 The implementation does not support renaming in this situation.
531 * 'InappropriateType'
532 Either path refers to an existing directory.
533 @[ENOTDIR, EISDIR, EINVAL, EEXIST, ENOTEMPTY]@
537 renameFile :: FilePath -> FilePath -> IO ()
538 renameFile opath npath =
539 -- XXX this test isn't performed atomically with the following rename
540 withFileOrSymlinkStatus "renameFile" opath $ \st -> do
541 is_dir <- isDirectory st
543 then ioException (IOError Nothing InappropriateType "renameFile"
544 "is a directory" (Just opath))
546 #ifdef mingw32_HOST_OS
547 System.Win32.moveFileEx opath npath System.Win32.mOVEFILE_REPLACE_EXISTING
549 System.Posix.rename opath npath
552 #endif /* __GLASGOW_HASKELL__ */
554 {- |@'copyFile' old new@ copies the existing file from /old/ to /new/.
555 If the /new/ file already exists, it is atomically replaced by the /old/ file.
556 Neither path may refer to an existing directory. The permissions of /old/ are
557 copied to /new/, if possible.
560 copyFile :: FilePath -> FilePath -> IO ()
562 copyFile fromFPath toFPath =
563 do readFile fromFPath >>= writeFile toFPath
564 Prelude.catch (copyPermissions fromFPath toFPath)
567 copyFile fromFPath toFPath =
568 copy `Prelude.catch` (\exc -> throw $ ioeSetLocation exc "copyFile")
569 where copy = bracket (openBinaryFile fromFPath ReadMode) hClose $ \hFrom ->
570 bracketOnError openTmp cleanTmp $ \(tmpFPath, hTmp) ->
571 do allocaBytes bufferSize $ copyContents hFrom hTmp
573 ignoreIOExceptions $ copyPermissions fromFPath tmpFPath
574 renameFile tmpFPath toFPath
575 openTmp = openBinaryTempFile (takeDirectory toFPath) ".copyFile.tmp"
576 cleanTmp (tmpFPath, hTmp)
577 = do ignoreIOExceptions $ hClose hTmp
578 ignoreIOExceptions $ removeFile tmpFPath
581 copyContents hFrom hTo buffer = do
582 count <- hGetBuf hFrom buffer bufferSize
583 when (count > 0) $ do
584 hPutBuf hTo buffer count
585 copyContents hFrom hTo buffer
587 ignoreIOExceptions io = io `catch` ioExceptionIgnorer
588 ioExceptionIgnorer :: IOException -> IO ()
589 ioExceptionIgnorer _ = return ()
592 -- | Given path referring to a file or directory, returns a
593 -- canonicalized path, with the intent that two paths referring
594 -- to the same file\/directory will map to the same canonicalized
595 -- path. Note that it is impossible to guarantee that the
596 -- implication (same file\/dir \<=\> same canonicalizedPath) holds
597 -- in either direction: this function can make only a best-effort
599 canonicalizePath :: FilePath -> IO FilePath
600 canonicalizePath fpath =
601 withCString fpath $ \pInPath ->
602 allocaBytes long_path_size $ \pOutPath ->
603 #if defined(mingw32_HOST_OS)
604 alloca $ \ppFilePart ->
605 do c_GetFullPathName pInPath (fromIntegral long_path_size) pOutPath ppFilePart
607 do c_realpath pInPath pOutPath
609 path <- peekCString pOutPath
610 return (normalise path)
611 -- normalise does more stuff, like upper-casing the drive letter
613 #if defined(mingw32_HOST_OS)
614 foreign import stdcall unsafe "GetFullPathNameA"
615 c_GetFullPathName :: CString
621 foreign import ccall unsafe "realpath"
622 c_realpath :: CString
627 -- | 'makeRelative' the current directory.
628 makeRelativeToCurrentDirectory :: FilePath -> IO FilePath
629 makeRelativeToCurrentDirectory x = do
630 cur <- getCurrentDirectory
631 return $ makeRelative cur x
633 -- | Given an executable file name, searches for such file
634 -- in the directories listed in system PATH. The returned value
635 -- is the path to the found executable or Nothing if there isn't
636 -- such executable. For example (findExecutable \"ghc\")
637 -- gives you the path to GHC.
638 findExecutable :: String -> IO (Maybe FilePath)
639 findExecutable binary =
640 #if defined(mingw32_HOST_OS)
641 withCString binary $ \c_binary ->
642 withCString ('.':exeExtension) $ \c_ext ->
643 allocaBytes long_path_size $ \pOutPath ->
644 alloca $ \ppFilePart -> do
645 res <- c_SearchPath nullPtr c_binary c_ext (fromIntegral long_path_size) pOutPath ppFilePart
646 if res > 0 && res < fromIntegral long_path_size
647 then do fpath <- peekCString pOutPath
651 foreign import stdcall unsafe "SearchPathA"
652 c_SearchPath :: CString
661 path <- getEnv "PATH"
662 search (splitSearchPath path)
664 fileName = binary <.> exeExtension
666 search :: [FilePath] -> IO (Maybe FilePath)
667 search [] = return Nothing
669 let path = d </> fileName
670 b <- doesFileExist path
671 if b then return (Just path)
676 #ifdef __GLASGOW_HASKELL__
677 {- |@'getDirectoryContents' dir@ returns a list of /all/ entries
680 The operation may fail with:
683 A physical I\/O error has occurred.
687 The operand is not a valid directory name.
688 @[ENAMETOOLONG, ELOOP]@
690 * 'isDoesNotExistError' \/ 'NoSuchThing'
691 The directory does not exist.
694 * 'isPermissionError' \/ 'PermissionDenied'
695 The process has insufficient privileges to perform the operation.
698 * 'ResourceExhausted'
699 Insufficient resources are available to perform the operation.
702 * 'InappropriateType'
703 The path refers to an existing non-directory object.
708 getDirectoryContents :: FilePath -> IO [FilePath]
709 getDirectoryContents path = do
710 modifyIOError (`ioeSetFileName` path) $
711 alloca $ \ ptr_dEnt ->
713 (withCString path $ \s ->
714 throwErrnoIfNullRetry desc (c_opendir s))
715 (\p -> throwErrnoIfMinus1_ desc (c_closedir p))
716 (\p -> loop ptr_dEnt p)
718 desc = "getDirectoryContents"
720 loop :: Ptr (Ptr CDirent) -> Ptr CDir -> IO [String]
721 loop ptr_dEnt dir = do
723 r <- readdir dir ptr_dEnt
726 dEnt <- peek ptr_dEnt
730 entry <- (d_name dEnt >>= peekCString)
732 entries <- loop ptr_dEnt dir
733 return (entry:entries)
734 else do errno <- getErrno
735 if (errno == eINTR) then loop ptr_dEnt dir else do
736 let (Errno eo) = errno
737 if (eo == end_of_dir)
743 {- |If the operating system has a notion of current directories,
744 'getCurrentDirectory' returns an absolute path to the
745 current directory of the calling process.
747 The operation may fail with:
750 A physical I\/O error has occurred.
753 * 'isDoesNotExistError' \/ 'NoSuchThing'
754 There is no path referring to the current directory.
755 @[EPERM, ENOENT, ESTALE...]@
757 * 'isPermissionError' \/ 'PermissionDenied'
758 The process has insufficient privileges to perform the operation.
761 * 'ResourceExhausted'
762 Insufficient resources are available to perform the operation.
764 * 'UnsupportedOperation'
765 The operating system has no notion of current directory.
769 getCurrentDirectory :: IO FilePath
770 getCurrentDirectory = do
771 #ifdef mingw32_HOST_OS
772 -- XXX: should use something from Win32
773 p <- mallocBytes long_path_size
775 where go p bytes = do
776 p' <- c_getcwd p (fromIntegral bytes)
778 then do s <- peekCString p'
781 else do errno <- getErrno
783 then do let bytes' = bytes * 2
784 p'' <- reallocBytes p bytes'
786 else throwErrno "getCurrentDirectory"
788 System.Posix.getWorkingDirectory
791 #ifdef mingw32_HOST_OS
792 foreign import ccall unsafe "getcwd"
793 c_getcwd :: Ptr CChar -> CSize -> IO (Ptr CChar)
796 {- |If the operating system has a notion of current directories,
797 @'setCurrentDirectory' dir@ changes the current
798 directory of the calling process to /dir/.
800 The operation may fail with:
803 A physical I\/O error has occurred.
807 The operand is not a valid directory name.
808 @[ENAMETOOLONG, ELOOP]@
810 * 'isDoesNotExistError' \/ 'NoSuchThing'
811 The directory does not exist.
814 * 'isPermissionError' \/ 'PermissionDenied'
815 The process has insufficient privileges to perform the operation.
818 * 'UnsupportedOperation'
819 The operating system has no notion of current directory, or the
820 current directory cannot be dynamically changed.
822 * 'InappropriateType'
823 The path refers to an existing non-directory object.
828 setCurrentDirectory :: FilePath -> IO ()
829 setCurrentDirectory path =
830 #ifdef mingw32_HOST_OS
831 System.Win32.setCurrentDirectory path
833 System.Posix.changeWorkingDirectory path
836 {- |The operation 'doesDirectoryExist' returns 'True' if the argument file
837 exists and is a directory, and 'False' otherwise.
840 doesDirectoryExist :: FilePath -> IO Bool
841 doesDirectoryExist name =
842 (withFileStatus "doesDirectoryExist" name $ \st -> isDirectory st)
843 `catch` ((\ _ -> return False) :: IOException -> IO Bool)
845 {- |The operation 'doesFileExist' returns 'True'
846 if the argument file exists and is not a directory, and 'False' otherwise.
849 doesFileExist :: FilePath -> IO Bool
851 (withFileStatus "doesFileExist" name $ \st -> do b <- isDirectory st; return (not b))
852 `catch` ((\ _ -> return False) :: IOException -> IO Bool)
854 {- |The 'getModificationTime' operation returns the
855 clock time at which the file or directory was last modified.
857 The operation may fail with:
859 * 'isPermissionError' if the user is not permitted to access
860 the modification time; or
862 * 'isDoesNotExistError' if the file or directory does not exist.
866 getModificationTime :: FilePath -> IO ClockTime
867 getModificationTime name =
868 withFileStatus "getModificationTime" name $ \ st ->
871 withFileStatus :: String -> FilePath -> (Ptr CStat -> IO a) -> IO a
872 withFileStatus loc name f = do
873 modifyIOError (`ioeSetFileName` name) $
874 allocaBytes sizeof_stat $ \p ->
875 withCString (fileNameEndClean name) $ \s -> do
876 throwErrnoIfMinus1Retry_ loc (c_stat s p)
879 withFileOrSymlinkStatus :: String -> FilePath -> (Ptr CStat -> IO a) -> IO a
880 withFileOrSymlinkStatus loc name f = do
881 modifyIOError (`ioeSetFileName` name) $
882 allocaBytes sizeof_stat $ \p ->
883 withCString name $ \s -> do
884 throwErrnoIfMinus1Retry_ loc (lstat s p)
887 modificationTime :: Ptr CStat -> IO ClockTime
888 modificationTime stat = do
889 mtime <- st_mtime stat
890 let realToInteger = round . realToFrac :: Real a => a -> Integer
891 return (TOD (realToInteger (mtime :: CTime)) 0)
893 isDirectory :: Ptr CStat -> IO Bool
894 isDirectory stat = do
896 return (s_isdir mode)
898 fileNameEndClean :: String -> String
899 fileNameEndClean name = if isDrive name then addTrailingPathSeparator name
900 else dropTrailingPathSeparator name
902 foreign import ccall unsafe "__hscore_R_OK" r_OK :: CInt
903 foreign import ccall unsafe "__hscore_W_OK" w_OK :: CInt
904 foreign import ccall unsafe "__hscore_X_OK" x_OK :: CInt
906 foreign import ccall unsafe "__hscore_S_IRUSR" s_IRUSR :: CMode
907 foreign import ccall unsafe "__hscore_S_IWUSR" s_IWUSR :: CMode
908 foreign import ccall unsafe "__hscore_S_IXUSR" s_IXUSR :: CMode
909 #ifdef mingw32_HOST_OS
910 foreign import ccall unsafe "__hscore_S_IFDIR" s_IFDIR :: CMode
913 foreign import ccall unsafe "__hscore_long_path_size"
914 long_path_size :: Int
917 long_path_size :: Int
918 long_path_size = 2048 -- // guess?
920 #endif /* __GLASGOW_HASKELL__ */
922 {- | Returns the current user's home directory.
924 The directory returned is expected to be writable by the current user,
925 but note that it isn't generally considered good practice to store
926 application-specific data here; use 'getAppUserDataDirectory'
929 On Unix, 'getHomeDirectory' returns the value of the @HOME@
930 environment variable. On Windows, the system is queried for a
931 suitable path; a typical path might be
932 @C:/Documents And Settings/user@.
934 The operation may fail with:
936 * 'UnsupportedOperation'
937 The operating system has no notion of home directory.
939 * 'isDoesNotExistError'
940 The home directory for the current user does not exist, or
943 getHomeDirectory :: IO FilePath
945 #if defined(mingw32_HOST_OS)
946 allocaBytes long_path_size $ \pPath -> do
947 r0 <- c_SHGetFolderPath nullPtr csidl_PROFILE nullPtr 0 pPath
950 r1 <- c_SHGetFolderPath nullPtr csidl_WINDOWS nullPtr 0 pPath
951 when (r1 < 0) (raiseUnsupported "System.Directory.getHomeDirectory")
958 {- | Returns the pathname of a directory in which application-specific
959 data for the current user can be stored. The result of
960 'getAppUserDataDirectory' for a given application is specific to
963 The argument should be the name of the application, which will be used
964 to construct the pathname (so avoid using unusual characters that
965 might result in an invalid pathname).
967 Note: the directory may not actually exist, and may need to be created
968 first. It is expected that the parent directory exists and is
971 On Unix, this function returns @$HOME\/.appName@. On Windows, a
972 typical path might be
974 > C:/Documents And Settings/user/Application Data/appName
976 The operation may fail with:
978 * 'UnsupportedOperation'
979 The operating system has no notion of application-specific data directory.
981 * 'isDoesNotExistError'
982 The home directory for the current user does not exist, or
985 getAppUserDataDirectory :: String -> IO FilePath
986 getAppUserDataDirectory appName = do
987 #if defined(mingw32_HOST_OS)
988 allocaBytes long_path_size $ \pPath -> do
989 r <- c_SHGetFolderPath nullPtr csidl_APPDATA nullPtr 0 pPath
990 when (r<0) (raiseUnsupported "System.Directory.getAppUserDataDirectory")
991 s <- peekCString pPath
992 return (s++'\\':appName)
994 path <- getEnv "HOME"
995 return (path++'/':'.':appName)
998 {- | Returns the current user's document directory.
1000 The directory returned is expected to be writable by the current user,
1001 but note that it isn't generally considered good practice to store
1002 application-specific data here; use 'getAppUserDataDirectory'
1005 On Unix, 'getUserDocumentsDirectory' returns the value of the @HOME@
1006 environment variable. On Windows, the system is queried for a
1007 suitable path; a typical path might be
1008 @C:\/Documents and Settings\/user\/My Documents@.
1010 The operation may fail with:
1012 * 'UnsupportedOperation'
1013 The operating system has no notion of document directory.
1015 * 'isDoesNotExistError'
1016 The document directory for the current user does not exist, or
1019 getUserDocumentsDirectory :: IO FilePath
1020 getUserDocumentsDirectory = do
1021 #if defined(mingw32_HOST_OS)
1022 allocaBytes long_path_size $ \pPath -> do
1023 r <- c_SHGetFolderPath nullPtr csidl_PERSONAL nullPtr 0 pPath
1024 when (r<0) (raiseUnsupported "System.Directory.getUserDocumentsDirectory")
1030 {- | Returns the current directory for temporary files.
1032 On Unix, 'getTemporaryDirectory' returns the value of the @TMPDIR@
1033 environment variable or \"\/tmp\" if the variable isn\'t defined.
1034 On Windows, the function checks for the existence of environment variables in
1035 the following order and uses the first path found:
1038 TMP environment variable.
1041 TEMP environment variable.
1044 USERPROFILE environment variable.
1047 The Windows directory
1049 The operation may fail with:
1051 * 'UnsupportedOperation'
1052 The operating system has no notion of temporary directory.
1054 The function doesn\'t verify whether the path exists.
1056 getTemporaryDirectory :: IO FilePath
1057 getTemporaryDirectory = do
1058 #if defined(mingw32_HOST_OS)
1059 allocaBytes long_path_size $ \pPath -> do
1060 _r <- c_GetTempPath (fromIntegral long_path_size) pPath
1065 `Prelude.catch` \e -> if isDoesNotExistError e then return "/tmp"
1068 `Prelude.catch` (\ex -> return "/tmp")
1072 #if defined(mingw32_HOST_OS)
1073 foreign import ccall unsafe "__hscore_getFolderPath"
1074 c_SHGetFolderPath :: Ptr ()
1080 foreign import ccall unsafe "__hscore_CSIDL_PROFILE" csidl_PROFILE :: CInt
1081 foreign import ccall unsafe "__hscore_CSIDL_APPDATA" csidl_APPDATA :: CInt
1082 foreign import ccall unsafe "__hscore_CSIDL_WINDOWS" csidl_WINDOWS :: CInt
1083 foreign import ccall unsafe "__hscore_CSIDL_PERSONAL" csidl_PERSONAL :: CInt
1085 foreign import stdcall unsafe "GetTempPathA" c_GetTempPath :: CInt -> CString -> IO CInt
1087 raiseUnsupported :: String -> IO ()
1088 raiseUnsupported loc =
1089 ioException (IOError Nothing UnsupportedOperation loc "unsupported operation" Nothing)
1093 -- ToDo: This should be determined via autoconf (AC_EXEEXT)
1094 -- | Extension for executable files
1095 -- (typically @\"\"@ on Unix and @\"exe\"@ on Windows or OS\/2)
1096 exeExtension :: String
1097 #ifdef mingw32_HOST_OS
1098 exeExtension = "exe"