Use Control.Exception exception handlers, and make copyFile meet its spec
[haskell-directory.git] / System / Directory.hs
index 5e87c21..fcdb937 100644 (file)
@@ -39,6 +39,7 @@ module System.Directory
     , copyFile                  -- :: FilePath -> FilePath -> IO ()
     
     , canonicalizePath
+    , makeRelativeToCurrentDirectory
     , findExecutable
 
     -- * Existence tests
@@ -65,29 +66,28 @@ module System.Directory
     , getModificationTime       -- :: FilePath -> IO ClockTime
    ) where
 
-import System.Directory.Internals
+import Prelude hiding ( catch )
+
 import System.Environment      ( getEnv )
-import System.IO.Error
+import System.FilePath
+import System.IO.Error hiding ( catch, try )
 import Control.Monad           ( when, unless )
+import Control.Exception
 
 #ifdef __NHC__
 import Directory
-import NHC.FFI
 #endif /* __NHC__ */
 
 #ifdef __HUGS__
 import Hugs.Directory
 #endif /* __HUGS__ */
 
-#if defined(__GLASGOW_HASKELL__) || defined(mingw32_HOST_OS)
 import Foreign
 import Foreign.C
-#endif
 
-#ifdef __GLASGOW_HASKELL__
-import Prelude
+{-# CFILES cbits/directory.c #-}
 
-import Control.Exception       ( bracket )
+#ifdef __GLASGOW_HASKELL__
 import System.Posix.Types
 import System.Posix.Internals
 import System.Time             ( ClockTime(..) )
@@ -270,11 +270,12 @@ createDirectoryIfMissing :: Bool     -- ^ Create its parents too?
                         -> IO ()
 createDirectoryIfMissing parents file = do
   b <- doesDirectoryExist file
-  case (b,parents, file) of 
+  case (b,parents, file) of
     (_,     _, "") -> return ()
     (True,  _,  _) -> return ()
-    (_,  True,  _) -> mapM_ (createDirectoryIfMissing False) (tail (pathParents file))
+    (_,  True,  _) -> mapM_ (createDirectoryIfMissing False) $ mkParents file
     (_, False,  _) -> createDirectory file
+ where mkParents = scanl1 (</>) . splitDirectories . normalise
 
 #if __GLASGOW_HASKELL__
 {- | @'removeDirectory' dir@ removes an existing directory /dir/.  The
@@ -331,7 +332,7 @@ removeDirectory path = do
 removeDirectoryRecursive :: FilePath -> IO ()
 removeDirectoryRecursive startLoc = do
   cont <- getDirectoryContents startLoc
-  sequence_ [rm (startLoc `joinFileName` x) | x <- cont, x /= "." && x /= ".."]
+  sequence_ [rm (startLoc </> x) | x <- cont, x /= "." && x /= ".."]
   removeDirectory startLoc
   where
     rm :: FilePath -> IO ()
@@ -339,7 +340,7 @@ removeDirectoryRecursive startLoc = do
               case temp of
                 Left e  -> do isDir <- doesDirectoryExist f
                               -- If f is not a directory, re-throw the error
-                              unless isDir $ ioError e
+                              unless isDir $ throw e
                               removeDirectoryRecursive f
                 Right _ -> return ()
 
@@ -507,43 +508,33 @@ renameFile opath npath =
 
 {- |@'copyFile' old new@ copies the existing file from /old/ to /new/.
 If the /new/ file already exists, it is atomically replaced by the /old/ file.
-Neither path may refer to an existing directory.
+Neither path may refer to an existing directory.  The permissions of /old/ are
+copied to /new/, if possible.
 -}
+
 copyFile :: FilePath -> FilePath -> IO ()
-copyFile fromFPath toFPath = do
-       -- We try removing the target file before opening it for
-       -- writing.  In the event that the target file is locked or in
-       -- use, this allows us to replace it safely.  However, it
-       -- leaves a race condition: someone else might create the file
-       -- after we delete it, but there isn't much we can do about
-       -- that.
-#if (!(defined(__GLASGOW_HASKELL__) && __GLASGOW_HASKELL__ > 600))
-       contents <- readFile fromFPath
-       try (removeFile toFPath)
-       writeFile toFPath contents
-       try (copyPermissions fromFPath toFPath)
-       return ()
-#else
-       (bracket (openBinaryFile fromFPath ReadMode) hClose $ \hFrom -> do
-        try (removeFile toFPath)
-        bracket (openBinaryFile toFPath WriteMode) hClose $ \hTo -> do
-        allocaBytes bufferSize $ \buffer -> do
-               copyContents hFrom hTo buffer
-               try (copyPermissions fromFPath toFPath)
-               return ()) `catch` (ioError . changeFunName)
-       where
-               bufferSize = 1024
-               
-               changeFunName (IOError h iot fun str mb_fp) = IOError h iot "copyFile" str mb_fp
-               
-               copyContents hFrom hTo buffer = do
-                       count <- hGetBuf hFrom buffer bufferSize
-                       when (count > 0) $ do
-                               hPutBuf hTo buffer count
-                               copyContents hFrom hTo buffer
-#endif
+copyFile fromFPath toFPath =
+    copy `catch` (\e -> case e of
+                        IOException e ->
+                            throw $ IOException $ ioeSetLocation e "copyFile"
+                        _ -> throw e)
+    where copy = bracket (openBinaryFile fromFPath ReadMode) hClose $ \hFrom ->
+                 bracketOnError openTmp cleanTmp $ \(tmpFPath, hTmp) ->
+                 do allocaBytes bufferSize $ copyContents hFrom hTmp
+                    hClose hTmp
+                    try (copyPermissions fromFPath toFPath)
+                    renameFile tmpFPath toFPath
+          openTmp = openBinaryTempFile (takeDirectory toFPath) ".copyFile.tmp"
+          cleanTmp (tmpFPath, hTmp) = do try $ hClose hTmp
+                                         try $ removeFile tmpFPath
+          bufferSize = 1024
+
+          copyContents hFrom hTo buffer = do
+                  count <- hGetBuf hFrom buffer bufferSize
+                  when (count > 0) $ do
+                          hPutBuf hTo buffer count
+                          copyContents hFrom hTo buffer
 
-#ifdef __GLASGOW_HASKELL__
 -- | Given path referring to a file or directory, returns a
 -- canonicalized path, with the intent that two paths referring
 -- to the same file\/directory will map to the same canonicalized
@@ -576,11 +567,12 @@ foreign import ccall unsafe "realpath"
                               -> CString
                               -> IO CString
 #endif
-#else /* !__GLASGOW_HASKELL__ */
--- dummy implementation
-canonicalizePath :: FilePath -> IO FilePath
-canonicalizePath fpath = return fpath
-#endif /* !__GLASGOW_HASKELL__ */
+
+-- | 'makeRelative' the current directory.
+makeRelativeToCurrentDirectory :: FilePath -> IO FilePath
+makeRelativeToCurrentDirectory x = do
+    cur <- getCurrentDirectory
+    return $ makeRelative cur x
 
 -- | Given an executable file name, searches for such file
 -- in the directories listed in system PATH. The returned value 
@@ -608,23 +600,19 @@ foreign import stdcall unsafe "SearchPathA"
                          -> CString
                          -> Ptr CString
                          -> IO CInt
-# if !defined(__GLASGOW_HASKELL__)
-long_path_size :: Int
-long_path_size = 4096
-# endif
 #else
  do
   path <- getEnv "PATH"
-  search (parseSearchPath path)
+  search (splitSearchPath path)
   where
-    fileName = binary `joinFileExt` exeExtension
+    fileName = binary <.> exeExtension
 
     search :: [FilePath] -> IO (Maybe FilePath)
     search [] = return Nothing
     search (d:ds) = do
-       let path = d `joinFileName` fileName
-       b <- doesFileExist path
-       if b then return (Just path)
+        let path = d </> fileName
+        b <- doesFileExist path
+        if b then return (Just path)
              else search ds
 #endif
 
@@ -843,26 +831,24 @@ isDirectory stat = do
   return (s_isdir mode)
 
 fileNameEndClean :: String -> String
-fileNameEndClean name = 
-  if i > 0 && (ec == '\\' || ec == '/') then 
-     fileNameEndClean (take i name)
-   else
-     name
-  where
-      i  = (length name) - 1
-      ec = name !! i
-
-foreign import ccall unsafe "__hscore_long_path_size"
-  long_path_size :: Int
+fileNameEndClean name = if isDrive name then addTrailingPathSeparator name
+                                        else dropTrailingPathSeparator name
 
-foreign import ccall unsafe "__hscore_R_OK" r_OK :: CMode
-foreign import ccall unsafe "__hscore_W_OK" w_OK :: CMode
-foreign import ccall unsafe "__hscore_X_OK" x_OK :: CMode
+foreign import ccall unsafe "__hscore_R_OK" r_OK :: CInt
+foreign import ccall unsafe "__hscore_W_OK" w_OK :: CInt
+foreign import ccall unsafe "__hscore_X_OK" x_OK :: CInt
 
 foreign import ccall unsafe "__hscore_S_IRUSR" s_IRUSR :: CMode
 foreign import ccall unsafe "__hscore_S_IWUSR" s_IWUSR :: CMode
 foreign import ccall unsafe "__hscore_S_IXUSR" s_IXUSR :: CMode
 
+foreign import ccall unsafe "__hscore_long_path_size"
+  long_path_size :: Int
+
+#else
+long_path_size :: Int
+long_path_size = 2048  --  // guess?
+
 #endif /* __GLASGOW_HASKELL__ */
 
 {- | Returns the current user's home directory.
@@ -888,7 +874,7 @@ cannot be found.
 -}
 getHomeDirectory :: IO FilePath
 getHomeDirectory =
-#if __GLASGOW_HASKELL__ && defined(mingw32_HOST_OS)
+#if defined(mingw32_HOST_OS)
   allocaBytes long_path_size $ \pPath -> do
      r <- c_SHGetFolderPath nullPtr csidl_PROFILE nullPtr 0 pPath
      if (r < 0)
@@ -930,7 +916,7 @@ cannot be found.
 -}
 getAppUserDataDirectory :: String -> IO FilePath
 getAppUserDataDirectory appName = do
-#if __GLASGOW_HASKELL__ && defined(mingw32_HOST_OS)
+#if defined(mingw32_HOST_OS)
   allocaBytes long_path_size $ \pPath -> do
      r <- c_SHGetFolderPath nullPtr csidl_APPDATA nullPtr 0 pPath
      when (r<0) (raiseUnsupported "System.Directory.getAppUserDataDirectory")
@@ -964,7 +950,7 @@ cannot be found.
 -}
 getUserDocumentsDirectory :: IO FilePath
 getUserDocumentsDirectory = do
-#if __GLASGOW_HASKELL__ && defined(mingw32_HOST_OS)
+#if defined(mingw32_HOST_OS)
   allocaBytes long_path_size $ \pPath -> do
      r <- c_SHGetFolderPath nullPtr csidl_PERSONAL nullPtr 0 pPath
      when (r<0) (raiseUnsupported "System.Directory.getUserDocumentsDirectory")
@@ -1001,7 +987,7 @@ The function doesn\'t verify whether the path exists.
 -}
 getTemporaryDirectory :: IO FilePath
 getTemporaryDirectory = do
-#if __GLASGOW_HASKELL__ && defined(mingw32_HOST_OS)
+#if defined(mingw32_HOST_OS)
   allocaBytes long_path_size $ \pPath -> do
      r <- c_GetTempPath (fromIntegral long_path_size) pPath
      peekCString pPath
@@ -1009,7 +995,7 @@ getTemporaryDirectory = do
   catch (getEnv "TMPDIR") (\ex -> return "/tmp")
 #endif
 
-#if __GLASGOW_HASKELL__ && defined(mingw32_HOST_OS)
+#if defined(mingw32_HOST_OS)
 foreign import ccall unsafe "__hscore_getFolderPath"
             c_SHGetFolderPath :: Ptr () 
                               -> CInt 
@@ -1028,3 +1014,14 @@ raiseUnsupported loc =
    ioException (IOError Nothing UnsupportedOperation loc "unsupported operation" Nothing)
 
 #endif
+
+-- ToDo: This should be determined via autoconf (AC_EXEEXT)
+-- | Extension for executable files
+-- (typically @\"\"@ on Unix and @\"exe\"@ on Windows or OS\/2)
+exeExtension :: String
+#ifdef mingw32_HOST_OS
+exeExtension = "exe"
+#else
+exeExtension = ""
+#endif
+