Files
hsfm/src/HSFM/FileSystem/Errors.hs

192 lines
5.6 KiB
Haskell
Raw Normal View History

2015-12-24 18:25:05 +01:00
{--
HSFM, a filemanager written in Haskell.
2016-03-31 00:28:23 +02:00
Copyright (C) 2016 Julian Ospald
2015-12-24 18:25:05 +01:00
This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
version 2 as published by the Free Software Foundation.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
--}
2015-12-17 04:42:22 +01:00
{-# OPTIONS_HADDOCK ignore-exports #-}
{-# LANGUAGE DeriveDataTypeable #-}
-- |Provides error handling.
2016-03-30 20:16:34 +02:00
module HSFM.FileSystem.Errors where
2015-12-17 04:42:22 +01:00
import Control.Exception
import Control.Monad
(
when
, forM
)
2015-12-17 04:42:22 +01:00
import Data.Typeable
import Foreign.C.Error
(
getErrno
, Errno
)
import qualified HPath as P
import HPath
(
Abs
, Path
)
2016-03-30 20:16:34 +02:00
import HSFM.Utils.IO
import System.FilePath
(
equalFilePath
)
import System.IO.Error
(
catchIOError
)
2015-12-17 04:42:22 +01:00
2015-12-21 18:32:53 +01:00
import qualified System.Posix.Files as PF
import qualified System.Posix.Directory as PFD
2015-12-21 18:32:53 +01:00
2015-12-17 04:42:22 +01:00
data FmIOException = FileDoesNotExist String
| DirDoesNotExist String
2015-12-17 04:42:22 +01:00
| PathNotAbsolute String
| FileNotExecutable String
| SameFile String String
| NotAFile String
| NotADir String
| DestinationInSource String String
| FileDoesExist String
| DirDoesExist String
2015-12-21 18:32:53 +01:00
| IsSymlink String
| InvalidOperation String
| InvalidFileName
| Can'tOpenDirectory String
2015-12-17 04:42:22 +01:00
deriving (Show, Typeable)
instance Exception FmIOException
----------------------------
--[ Path based functions ]--
----------------------------
throwFileDoesExist :: Path Abs -> IO ()
throwFileDoesExist fp =
whenM (doesFileExist fp) (throw $ FileDoesExist $ P.fromAbs fp)
throwDirDoesExist :: Path Abs -> IO ()
2015-12-18 15:37:14 +01:00
throwDirDoesExist fp =
whenM (doesDirectoryExist fp) (throw $ DirDoesExist $ P.fromAbs fp)
throwFileDoesNotExist :: Path Abs -> IO ()
throwFileDoesNotExist fp =
whenM (doesFileExist fp) (throw $ FileDoesExist $ P.fromAbs fp)
throwDirDoesNotExist :: Path Abs -> IO ()
throwDirDoesNotExist fp =
whenM (doesDirectoryExist fp) (throw $ DirDoesExist $ P.fromAbs fp)
throwSameFile :: Path Abs -- ^ will be canonicalized
-> Path Abs -- ^ will be canonicalized
-> IO ()
throwSameFile fp1 fp2 = do
fp1' <- fmap P.fromAbs $ P.canonicalizePath fp1
fp2' <- fmap P.fromAbs $ P.canonicalizePath fp2
when (equalFilePath fp1' fp2') (throw $ SameFile fp1' fp2')
-- |Checks whether the destination directory is contained
-- within the source directory by comparing the device+file ID of the
-- source directory with all device+file IDs of the parent directories
-- of the destination.
throwDestinationInSource :: Path Abs -- ^ source dir
-> Path Abs -- ^ full destination, `dirname dest`
-- must exist
-> IO ()
throwDestinationInSource source dest = do
source' <- P.canonicalizePath source
dest' <- (P.</> P.basename dest) <$> (P.canonicalizePath $ P.dirname dest)
dids <- forM (P.getAllParents dest') $ \p -> do
fs <- PF.getSymbolicLinkStatus (P.fromAbs p)
return (PF.deviceID fs, PF.fileID fs)
sid <- fmap (\x -> (PF.deviceID x, PF.fileID x))
$ PF.getSymbolicLinkStatus (P.fromAbs source')
when (elem sid dids)
(throw $ DestinationInSource (P.fromAbs dest) (P.fromAbs source))
2015-12-21 18:32:53 +01:00
-- |Checks if the given file exists and is not a directory. This follows
-- symlinks, but will return True if the symlink is broken.
doesFileExist :: Path Abs -> IO Bool
doesFileExist fp =
handleIOError (\_ -> return False) $ do
fp' <- fmap P.fromAbs $ P.canonicalizePath fp
fs <- PF.getFileStatus fp'
return $ not . PF.isDirectory $ fs
-- |Checks if the given file exists and is a directory. This follows
-- symlinks, but will return False if the symlink is broken.
doesDirectoryExist :: Path Abs -> IO Bool
doesDirectoryExist fp =
handleIOError (\_ -> return False) $ do
fp' <- fmap P.fromAbs $ P.canonicalizePath fp
fs <- PF.getFileStatus fp'
return $ PF.isDirectory fs
canOpenDirectory :: Path Abs -> IO Bool
canOpenDirectory fp =
handleIOError (\_ -> return False) $ do
dirstream <- PFD.openDirStream . P.fromAbs $ fp
PFD.closeDirStream dirstream
return True
throwCantOpenDirectory :: Path Abs -> IO ()
throwCantOpenDirectory fp =
unlessM (canOpenDirectory fp) (throw $ Can'tOpenDirectory $ P.fromAbs fp)
--------------------------------
--[ Error handling functions ]--
--------------------------------
-- |Carries out an action, then checks if there is an IOException and
-- a specific errno. If so, then it carries out another action, otherwise
-- it rethrows the error.
catchErrno :: Errno -- ^ errno to catch
-> IO a -- ^ action to try, which can raise an IOException
-> IO a -- ^ action to carry out in case of an IOException and
-- if errno matches
-> IO a
catchErrno en a1 a2 =
catchIOError a1 $ \e -> do
errno <- getErrno
if errno == en
then a2
else ioError e
handleIOError :: (IOError -> IO a) -> IO a -> IO a
handleIOError a1 a2 = catchIOError a2 a1