LIB: improve symlink handling

This commit is contained in:
Julian Ospald 2015-12-21 18:32:53 +01:00
parent 988350967a
commit 433cb164a6
No known key found for this signature in database
GPG Key ID: 220CD1C5BDEED020
4 changed files with 100 additions and 32 deletions

View File

@ -444,6 +444,7 @@ normalize fp =
-- component if it's a symlink. -- component if it's a symlink.
canonicalizePath' :: FilePath -> IO FilePath canonicalizePath' :: FilePath -> IO FilePath
canonicalizePath' fp = do canonicalizePath' fp = do
-- TODO: throw fileDoesNotExist error earlier
isSymlink <- PF.isSymbolicLink <$> PF.getSymbolicLinkStatus fp isSymlink <- PF.isSymbolicLink <$> PF.getSymbolicLinkStatus fp
if isSymlink if isSymlink
then do then do

View File

@ -115,6 +115,10 @@ open row mygui myview =
del :: Row -> MyGUI -> MyView -> IO () del :: Row -> MyGUI -> MyView -> IO ()
del row mygui myview = del row mygui myview =
case row of case row of
r@(_ :/ Dir { dir = FileInfo { isSymbolicLink = True } }) ->
delSym r
r@(_ :/ RegFile { regFile = FileInfo { isSymbolicLink = True } }) ->
delSym r
r@(_ :/ Dir _ _) -> do r@(_ :/ Dir _ _) -> do
let fp = fullPath r let fp = fullPath r
subADT <- readDirectory fp subADT <- readDirectory fp
@ -134,6 +138,13 @@ del row mygui myview =
withConfirmationDialog cmsg withConfirmationDialog cmsg
$ withErrorDialog (deleteFile fp $ withErrorDialog (deleteFile fp
>> refreshTreeView mygui myview Nothing) >> refreshTreeView mygui myview Nothing)
where
delSym r = do
let fp = fullPath r
cmsg = "Really delete symlink \"" ++ fp ++ "\"?"
withConfirmationDialog cmsg
$ withErrorDialog (deleteSymlink fp
>> refreshTreeView mygui myview Nothing)
-- |Supposed to be used with 'withRow'. Initializes a file copy operation. -- |Supposed to be used with 'withRow'. Initializes a file copy operation.

View File

@ -5,6 +5,10 @@
module IO.Error where module IO.Error where
import Control.Applicative
(
(<$>)
)
import Control.Exception import Control.Exception
import Control.Monad import Control.Monad
( (
@ -30,6 +34,8 @@ import System.FilePath
, takeFileName , takeFileName
) )
import qualified System.Posix.Files as PF
data FmIOException = FileDoesNotExist String data FmIOException = FileDoesNotExist String
| DirDoesNotExist String | DirDoesNotExist String
@ -40,6 +46,7 @@ data FmIOException = FileDoesNotExist String
| NotADir String | NotADir String
| DestinationInSource String String | DestinationInSource String String
| DirDoesExist String | DirDoesExist String
| IsSymlink String
deriving (Show, Typeable) deriving (Show, Typeable)
@ -88,3 +95,9 @@ throwDestinationInSource :: FilePath -- ^ should be canonicalized
-> IO () -> IO ()
throwDestinationInSource source dest = throwDestinationInSource source dest =
when (source `isPrefixOf` dest) (throw $ DestinationInSource dest source) when (source `isPrefixOf` dest) (throw $ DestinationInSource dest source)
throwIsSymlink :: FilePath -> IO ()
throwIsSymlink fp =
whenM (PF.isSymbolicLink <$> PF.getSymbolicLinkStatus fp)
(throw $ IsSymlink fp)

View File

@ -11,6 +11,10 @@
module IO.File where module IO.File where
import Control.Applicative
(
(<$>)
)
import Control.Exception import Control.Exception
( (
throw throw
@ -65,9 +69,7 @@ import qualified System.Directory as SD
import qualified System.Posix.Files as PF import qualified System.Posix.Files as PF
-- TODO: modify the DTZipper directly after file operations!?
-- TODO: file operations should be threaded and not block the UI -- TODO: file operations should be threaded and not block the UI
-- TODO: canonicalize paths?
-- |Data type describing an actual file operation that can be -- |Data type describing an actual file operation that can be
@ -124,7 +126,8 @@ runFileOp _ = return Nothing
-- TODO: allow renaming -- TODO: allow renaming
-- |Copies a directory to the given destination with the specified -- |Copies a directory to the given destination with the specified
-- `DirCopyMode`. -- `DirCopyMode`. This is safe to call if the source directory is a symlink
-- in which case it will just be recreated.
-- --
-- The operation may fail with: -- The operation may fail with:
-- --
@ -145,7 +148,7 @@ copyDir :: DirCopyMode
copyDir cm from' to' = do copyDir cm from' to' = do
from <- canonicalizePath' from' from <- canonicalizePath' from'
to <- canonicalizePath' to' to <- canonicalizePath' to'
go from to onSymlinkOr from (copyFileToDir from to) (go from to)
where where
go from to = do go from to = do
let fn = takeFileName from let fn = takeFileName from
@ -164,7 +167,7 @@ copyDir cm from' to' = do
let ffn = from </> f let ffn = from </> f
fs <- PF.getSymbolicLinkStatus ffn fs <- PF.getSymbolicLinkStatus ffn
case (PF.isSymbolicLink fs, PF.isDirectory fs) of case (PF.isSymbolicLink fs, PF.isDirectory fs) of
(True, _) -> recreateSymlink destdir f ffn (True, _) -> recreateSymlink' destdir f ffn
(_, True) -> copyDir cm ffn destdir (_, True) -> copyDir cm ffn destdir
(_, _) -> copyFileToDir ffn destdir (_, _) -> copyFileToDir ffn destdir
createDestdir destdir = createDestdir destdir =
@ -177,7 +180,7 @@ copyDir cm from' to' = do
Replace -> do Replace -> do
whenM (doesDirectoryExist destdir) (removeDirectoryRecursive destdir) whenM (doesDirectoryExist destdir) (removeDirectoryRecursive destdir)
createDirectory destdir createDirectory destdir
recreateSymlink destdir n f = do recreateSymlink' destdir n f = do
let sympoint = destdir </> n let sympoint = destdir </> n
case cm of case cm of
@ -185,11 +188,22 @@ copyDir cm from' to' = do
Merge -> easyDelete sympoint Merge -> easyDelete sympoint
_ -> return () _ -> return ()
symname <- readSymbolicLink f recreateSymlink f sympoint
createSymbolicLink symname sympoint
-- |Copies the given file.
-- |Recreate a symlink. Don't call `canonicalizePath` on the symlink
-- destination since that will mess with the symlink.
recreateSymlink :: FilePath -- ^ the old symlink file
-> FilePath -- ^ destination of the new symlink file
-> IO ()
recreateSymlink symf' symdest = do
symf <- canonicalizePath' symf'
symname <- readSymbolicLink symf
createSymbolicLink symname symdest
-- |Copies the given file. This can also be called on symlinks.
-- --
-- The operation may fail with: -- The operation may fail with:
-- --
@ -207,17 +221,19 @@ copyFile from' to' = do
from <- canonicalizePath' from' from <- canonicalizePath' from'
tod <- canonicalizePath' (baseDir to') tod <- canonicalizePath' (baseDir to')
let to = tod </> takeFileName to' let to = tod </> takeFileName to'
fileSanityThrow from onSymlinkOr from (recreateSymlink from to) $ do
throwNotAbsolute to fileSanityThrow from
throwDirDoesExist to throwNotAbsolute to
toC <- canonicalizePath' (takeDirectory to) throwDirDoesExist to
let to' = toC </> takeFileName to toC <- canonicalizePath' (takeDirectory to)
throwSameFile from to' let to' = toC </> takeFileName to
SD.copyFile from to' throwSameFile from to'
SD.copyFile from to'
-- |Copies the given file to the given dir with the same filename. -- |Copies the given file to the given dir with the same filename.
-- This can also be called on symlinks.
-- --
-- The operation may fail with: -- The operation may fail with:
-- --
@ -234,7 +250,7 @@ copyFileToDir from' to' = do
easyCopy :: DirCopyMode -> FilePath -> FilePath -> IO () easyCopy :: DirCopyMode -> FilePath -> FilePath -> IO ()
easyCopy cm from to = doFileOrDir from (copyDir cm from to) easyCopy cm from to = onDirOrFile from (copyDir cm from to)
(copyFileToDir from to) (copyFileToDir from to)
@ -244,8 +260,20 @@ easyCopy cm from to = doFileOrDir from (copyDir cm from to)
--------------------- ---------------------
-- TODO: misbehaves on symlinks -- |Deletes a symlink, which can either point to a file or directory.
-- |Deletes the given file or symlink. --
-- The operation may fail with:
--
-- * `dirSanityThrow`
-- * `fileSanityThrow`
deleteSymlink :: FilePath -> IO ()
deleteSymlink fp' = do
fp <- canonicalizePath' fp'
onDirOrFile fp (dirSanityThrow fp >> removeFile fp)
(fileSanityThrow fp >> removeFile fp)
-- |Deletes the given file.
-- --
-- The operation may fail with: -- The operation may fail with:
-- --
@ -256,10 +284,10 @@ deleteFile :: FilePath -> IO ()
deleteFile fp' = do deleteFile fp' = do
fp <- canonicalizePath' fp' fp <- canonicalizePath' fp'
fileSanityThrow fp fileSanityThrow fp
throwIsSymlink fp
removeFile fp removeFile fp
-- TODO: misbehaves on symlinks
-- |Deletes the given directory. -- |Deletes the given directory.
-- --
-- The operation may fail with: -- The operation may fail with:
@ -268,10 +296,12 @@ deleteFile fp' = do
-- * `PathNotAbsolute` if the dir is not absolute -- * `PathNotAbsolute` if the dir is not absolute
-- * anything that `removeDirectory` throws -- * anything that `removeDirectory` throws
deleteDir :: FilePath -> IO () deleteDir :: FilePath -> IO ()
deleteDir fp' = do deleteDir fp' =
fp <- canonicalizePath' fp' onSymlinkOr fp' (deleteFile fp') $ do
dirSanityThrow fp fp <- canonicalizePath' fp'
removeDirectory fp dirSanityThrow fp
throwIsSymlink fp
removeDirectory fp
-- |Deletes the given directory recursively. -- |Deletes the given directory recursively.
@ -282,13 +312,15 @@ deleteDir fp' = do
-- * `PathNotAbsolute` if the dir is not absolute -- * `PathNotAbsolute` if the dir is not absolute
-- * anything that `removeDirectoryRecursive` throws -- * anything that `removeDirectoryRecursive` throws
deleteDirRecursive :: FilePath -> IO () deleteDirRecursive :: FilePath -> IO ()
deleteDirRecursive fp' = do deleteDirRecursive fp' =
fp <- canonicalizePath' fp' onSymlinkOr fp' (deleteFile fp') $ do
dirSanityThrow fp fp <- canonicalizePath' fp'
removeDirectoryRecursive fp dirSanityThrow fp
throwIsSymlink fp
removeDirectoryRecursive fp
-- |Deletes a file or directory, whatever it may be. -- |Deletes a file, directory or symlink, whatever it may be.
-- --
-- The operation may fail with: -- The operation may fail with:
-- --
@ -298,7 +330,8 @@ deleteDirRecursive fp' = do
easyDelete :: FilePath -> IO () easyDelete :: FilePath -> IO ()
easyDelete fp' = do easyDelete fp' = do
fp <- canonicalizePath' fp' fp <- canonicalizePath' fp'
doFileOrDir fp (deleteDir fp) (deleteFile fp) onSymlinkOr fp (deleteSymlink fp) $
onDirOrFile fp (deleteDir fp) (deleteFile fp)
@ -345,14 +378,24 @@ executeFile prog' args = do
-------------------- --------------------
-- |Carry out action 1 if the filepath is a symlink, otherwise action2.
onSymlinkOr :: FilePath
-> IO () -- ^ action1
-> IO () -- ^ action2
-> IO ()
onSymlinkOr fp a1 a2 = do
isSymlink <- PF.isSymbolicLink <$> PF.getSymbolicLinkStatus fp
if isSymlink then a1 else a2
-- |Executes either a directory or file related IO action, depending on -- |Executes either a directory or file related IO action, depending on
-- the input filepath. -- the input filepath.
-- --
-- The operation may fail with: -- The operation may fail with:
-- --
-- * `throwFileDoesNotExist` if the filepath is neither a file or directory -- * `throwFileDoesNotExist` if the filepath is neither a file or directory
doFileOrDir :: FilePath -> IO () -> IO () -> IO () onDirOrFile :: FilePath -> IO () -> IO () -> IO ()
doFileOrDir fp' iod iof = do onDirOrFile fp' iod iof = do
fp <- canonicalizePath' fp' fp <- canonicalizePath' fp'
isD <- doesDirectoryExist fp isD <- doesDirectoryExist fp
isF <- doesFileExist fp isF <- doesFileExist fp