{-# LANGUAGE CPP #-}
{- arch-tag: HVFS Combinators
Copyright (c) 2004-2011 John Goerzen <jgoerzen@complete.org>

All rights reserved.

For license and copyright information, see the file LICENSE
-}
{- |
   Module     : System.IO.HVFS.Combinators
   Copyright  : Copyright (C) 2004-2011 John Goerzen
   SPDX-License-Identifier: BSD-3-Clause

   Stability  : stable
   Portability: portable

Support for combining different HVFS modules together

Copyright (c) 2004-2005 John Goerzen, jgoerzen\@complete.org

-}

module System.IO.HVFS.Combinators ( -- * Restrictions
                                    HVFSReadOnly(..),
                                    HVFSChroot, newHVFSChroot)
    where

import System.IO ( IOMode(ReadMode) )
import System.IO.Error
    ( doesNotExistErrorType, permissionErrorType )
import System.IO.HVFS
    ( HVFSOpenable(vOpen),
      HVFS(vRemoveFile, vCreateLink, vCreateSymbolicLink, vRenameFile,
           vRenameDirectory, vRemoveDirectory, vCreateDirectory,
           vGetCurrentDirectory, vGetDirectoryContents, vDoesFileExist,
           vGetFileStatus, vGetSymbolicLinkStatus, vGetModificationTime,
           vReadSymbolicLink, vRaiseError, vDoesDirectoryExist,
           vSetCurrentDirectory) )
import           System.IO.HVFS.InstanceHelpers (getFullPath)
import           System.FilePath                (isPathSeparator, pathSeparator,
                                                 (</>))
import           System.Path                    (secureAbsNormPath)
import           System.Path.NameManip          (normalise_path)

----------------------------------------------------------------------
-- Providing read-only access
----------------------------------------------------------------------
{- | Restrict access to the underlying filesystem to be strictly
read-only.  Any write-type operations will cause an error.

No constructor is required; just say @HVFSReadOnly fs@ to make a
new read-only wrapper around the 'HVFS' instance @fs@.
-}
data HVFS a => HVFSReadOnly a = HVFSReadOnly a
                              deriving (HVFSReadOnly a -> HVFSReadOnly a -> Bool
forall a.
(HVFS a, Eq a) =>
HVFSReadOnly a -> HVFSReadOnly a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: HVFSReadOnly a -> HVFSReadOnly a -> Bool
$c/= :: forall a.
(HVFS a, Eq a) =>
HVFSReadOnly a -> HVFSReadOnly a -> Bool
== :: HVFSReadOnly a -> HVFSReadOnly a -> Bool
$c== :: forall a.
(HVFS a, Eq a) =>
HVFSReadOnly a -> HVFSReadOnly a -> Bool
Eq, Int -> HVFSReadOnly a -> ShowS
forall a. HVFS a => Int -> HVFSReadOnly a -> ShowS
forall a. HVFS a => [HVFSReadOnly a] -> ShowS
forall a. HVFS a => HVFSReadOnly a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [HVFSReadOnly a] -> ShowS
$cshowList :: forall a. HVFS a => [HVFSReadOnly a] -> ShowS
show :: HVFSReadOnly a -> String
$cshow :: forall a. HVFS a => HVFSReadOnly a -> String
showsPrec :: Int -> HVFSReadOnly a -> ShowS
$cshowsPrec :: forall a. HVFS a => Int -> HVFSReadOnly a -> ShowS
Show)
withro :: HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro :: forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro a -> b
f (HVFSReadOnly a
x) = a -> b
f a
x

roerror :: (HVFS a) => HVFSReadOnly a -> IO c
roerror :: forall a c. HVFS a => HVFSReadOnly a -> IO c
roerror HVFSReadOnly a
h =
    let err :: a -> IO c
err a
x = forall a c.
HVFS a =>
a -> IOErrorType -> String -> Maybe String -> IO c
vRaiseError a
x IOErrorType
permissionErrorType String
"Read-only virtual filesystem"
                  forall a. Maybe a
Nothing
        in forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall {a} {c}. HVFS a => a -> IO c
err HVFSReadOnly a
h

instance HVFS a => HVFS (HVFSReadOnly a) where
    vGetCurrentDirectory :: HVFSReadOnly a -> IO String
vGetCurrentDirectory = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> IO String
vGetCurrentDirectory
    vSetCurrentDirectory :: HVFSReadOnly a -> String -> IO ()
vSetCurrentDirectory = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> String -> IO ()
vSetCurrentDirectory
    vGetDirectoryContents :: HVFSReadOnly a -> String -> IO [String]
vGetDirectoryContents = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> String -> IO [String]
vGetDirectoryContents
    vDoesFileExist :: HVFSReadOnly a -> String -> IO Bool
vDoesFileExist = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> String -> IO Bool
vDoesFileExist
    vDoesDirectoryExist :: HVFSReadOnly a -> String -> IO Bool
vDoesDirectoryExist = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> String -> IO Bool
vDoesDirectoryExist
    vCreateDirectory :: HVFSReadOnly a -> String -> IO ()
vCreateDirectory HVFSReadOnly a
h String
_ = forall a c. HVFS a => HVFSReadOnly a -> IO c
roerror HVFSReadOnly a
h
    vRemoveDirectory :: HVFSReadOnly a -> String -> IO ()
vRemoveDirectory HVFSReadOnly a
h String
_ = forall a c. HVFS a => HVFSReadOnly a -> IO c
roerror HVFSReadOnly a
h
    vRenameDirectory :: HVFSReadOnly a -> String -> String -> IO ()
vRenameDirectory HVFSReadOnly a
h String
_ String
_ = forall a c. HVFS a => HVFSReadOnly a -> IO c
roerror HVFSReadOnly a
h
    vRenameFile :: HVFSReadOnly a -> String -> String -> IO ()
vRenameFile HVFSReadOnly a
h String
_ String
_ = forall a c. HVFS a => HVFSReadOnly a -> IO c
roerror HVFSReadOnly a
h
    vGetFileStatus :: HVFSReadOnly a -> String -> IO HVFSStatEncap
vGetFileStatus = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> String -> IO HVFSStatEncap
vGetFileStatus
    vGetSymbolicLinkStatus :: HVFSReadOnly a -> String -> IO HVFSStatEncap
vGetSymbolicLinkStatus = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> String -> IO HVFSStatEncap
vGetSymbolicLinkStatus
    vGetModificationTime :: HVFSReadOnly a -> String -> IO ClockTime
vGetModificationTime = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> String -> IO ClockTime
vGetModificationTime
    vRaiseError :: forall c.
HVFSReadOnly a -> IOErrorType -> String -> Maybe String -> IO c
vRaiseError = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a c.
HVFS a =>
a -> IOErrorType -> String -> Maybe String -> IO c
vRaiseError
    vCreateSymbolicLink :: HVFSReadOnly a -> String -> String -> IO ()
vCreateSymbolicLink HVFSReadOnly a
h String
_ String
_ = forall a c. HVFS a => HVFSReadOnly a -> IO c
roerror HVFSReadOnly a
h
    vReadSymbolicLink :: HVFSReadOnly a -> String -> IO String
vReadSymbolicLink = forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro forall a. HVFS a => a -> String -> IO String
vReadSymbolicLink
    vCreateLink :: HVFSReadOnly a -> String -> String -> IO ()
vCreateLink HVFSReadOnly a
h String
_ String
_ = forall a c. HVFS a => HVFSReadOnly a -> IO c
roerror HVFSReadOnly a
h

instance HVFSOpenable a => HVFSOpenable (HVFSReadOnly a) where
    vOpen :: HVFSReadOnly a -> String -> IOMode -> IO HVFSOpenEncap
vOpen HVFSReadOnly a
fh String
fp IOMode
mode =
        case IOMode
mode of IOMode
ReadMode -> forall a b. HVFS a => (a -> b) -> HVFSReadOnly a -> b
withro (\a
h -> forall a.
HVFSOpenable a =>
a -> String -> IOMode -> IO HVFSOpenEncap
vOpen a
h String
fp IOMode
mode) HVFSReadOnly a
fh
                     IOMode
_        -> forall a c. HVFS a => HVFSReadOnly a -> IO c
roerror HVFSReadOnly a
fh

----------------------------------------------------------------------
-- Restricting to a subdirectory
----------------------------------------------------------------------
{- | Access a subdirectory of a real filesystem as if it was the root
of that filesystem. -}
data HVFS a => HVFSChroot a = HVFSChroot String a
                            deriving (HVFSChroot a -> HVFSChroot a -> Bool
forall a. (HVFS a, Eq a) => HVFSChroot a -> HVFSChroot a -> Bool
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: HVFSChroot a -> HVFSChroot a -> Bool
$c/= :: forall a. (HVFS a, Eq a) => HVFSChroot a -> HVFSChroot a -> Bool
== :: HVFSChroot a -> HVFSChroot a -> Bool
$c== :: forall a. (HVFS a, Eq a) => HVFSChroot a -> HVFSChroot a -> Bool
Eq, Int -> HVFSChroot a -> ShowS
forall a. HVFS a => Int -> HVFSChroot a -> ShowS
forall a. HVFS a => [HVFSChroot a] -> ShowS
forall a. HVFS a => HVFSChroot a -> String
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
showList :: [HVFSChroot a] -> ShowS
$cshowList :: forall a. HVFS a => [HVFSChroot a] -> ShowS
show :: HVFSChroot a -> String
$cshow :: forall a. HVFS a => HVFSChroot a -> String
showsPrec :: Int -> HVFSChroot a -> ShowS
$cshowsPrec :: forall a. HVFS a => Int -> HVFSChroot a -> ShowS
Show)

{- | Create a new 'HVFSChroot' object. -}
newHVFSChroot :: HVFS a => a            -- ^ The object to pass requests on to
              -> FilePath               -- ^ The path of the directory to make root
              -> IO (HVFSChroot a)      -- ^ The resulting new object
newHVFSChroot :: forall a. HVFS a => a -> String -> IO (HVFSChroot a)
newHVFSChroot a
fh String
fp =
    do String
full <- forall a. HVFS a => a -> String -> IO String
getFullPath a
fh String
fp
       Bool
isdir <- forall a. HVFS a => a -> String -> IO Bool
vDoesDirectoryExist a
fh String
full
       if Bool
isdir
          then do let newobj :: HVFSChroot a
newobj = (forall a. String -> a -> HVFSChroot a
HVFSChroot String
full a
fh)
                  forall a. HVFS a => a -> String -> IO ()
vSetCurrentDirectory HVFSChroot a
newobj [Char
pathSeparator]
                  forall (m :: * -> *) a. Monad m => a -> m a
return HVFSChroot a
newobj
          else forall a c.
HVFS a =>
a -> IOErrorType -> String -> Maybe String -> IO c
vRaiseError a
fh IOErrorType
doesNotExistErrorType
                 (String
"Attempt to instantiate HVFSChroot over non-directory " forall a. [a] -> [a] -> [a]
++ String
full)
                 (forall a. a -> Maybe a
Just String
full)

{- | Get the embedded object -}
dch :: (HVFS t) => HVFSChroot t -> t
dch :: forall t. HVFS t => HVFSChroot t -> t
dch (HVFSChroot String
_ t
a) = t
a

{- | Convert a local (chroot) path to a full path. -}
dch2fp, fp2dch :: (HVFS t) => HVFSChroot t -> String -> IO String
dch2fp :: forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp mainh :: HVFSChroot t
mainh@(HVFSChroot String
fp t
h) String
locfp =
    do String
full <- (String
fp forall a. [a] -> [a] -> [a]
++) forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
`fmap` if Char -> Bool
isPathSeparator (forall a. [a] -> a
head String
locfp)
                                then forall (m :: * -> *) a. Monad m => a -> m a
return String
locfp
                                else forall a. HVFS a => a -> String -> IO String
getFullPath HVFSChroot t
mainh String
locfp
       case String -> String -> Maybe String
secureAbsNormPath String
fp String
full of
           Maybe String
Nothing -> forall a c.
HVFS a =>
a -> IOErrorType -> String -> Maybe String -> IO c
vRaiseError t
h IOErrorType
doesNotExistErrorType
                        (String
"Trouble normalizing path in chroot")
                        (forall a. a -> Maybe a
Just (String
fp forall a. [a] -> [a] -> [a]
++ String
"," forall a. [a] -> [a] -> [a]
++ String
full))
           Just String
x -> forall (m :: * -> *) a. Monad m => a -> m a
return String
x

{- | Convert a full path to a local (chroot) path. -}
fp2dch :: forall t. HVFS t => HVFSChroot t -> String -> IO String
fp2dch (HVFSChroot String
fp t
h) String
locfp =
    do String
newpath <- case String -> String -> Maybe String
secureAbsNormPath String
fp String
locfp of
                     Maybe String
Nothing -> forall a c.
HVFS a =>
a -> IOErrorType -> String -> Maybe String -> IO c
vRaiseError t
h IOErrorType
doesNotExistErrorType
                                  (String
"Unable to securely normalize path")
                                  (forall a. a -> Maybe a
Just (String
fp String -> ShowS
</> String
locfp))
                     Just String
x -> forall (m :: * -> *) a. Monad m => a -> m a
return String
x
       if (forall a. Int -> [a] -> [a]
take (forall (t :: * -> *) a. Foldable t => t a -> Int
length String
fp) String
newpath forall a. Eq a => a -> a -> Bool
/= String
fp)
               then forall a c.
HVFS a =>
a -> IOErrorType -> String -> Maybe String -> IO c
vRaiseError t
h IOErrorType
doesNotExistErrorType
                        (String
"Local path is not subdirectory of parent path")
                        (forall a. a -> Maybe a
Just String
newpath)
               else let newpath2 :: String
newpath2 = forall a. Int -> [a] -> [a]
drop (forall (t :: * -> *) a. Foldable t => t a -> Int
length String
fp) String
newpath
                        in forall (m :: * -> *) a. Monad m => a -> m a
return forall a b. (a -> b) -> a -> b
$ ShowS
normalise_path ([Char
pathSeparator] forall a. [a] -> [a] -> [a]
++ String
newpath2)

dch2fph :: (HVFS t) => (t -> String -> IO t1) -> HVFSChroot t -> [Char] -> IO t1
dch2fph :: forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph t -> String -> IO t1
func fh :: HVFSChroot t
fh@(HVFSChroot String
_ t
h) String
locfp =
    do String
newfp <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot t
fh String
locfp
       t -> String -> IO t1
func t
h String
newfp

instance HVFS a => HVFS (HVFSChroot a) where
    vGetCurrentDirectory :: HVFSChroot a -> IO String
vGetCurrentDirectory HVFSChroot a
x = do String
fp <- forall a. HVFS a => a -> IO String
vGetCurrentDirectory (forall t. HVFS t => HVFSChroot t -> t
dch HVFSChroot a
x)
                                forall t. HVFS t => HVFSChroot t -> String -> IO String
fp2dch HVFSChroot a
x String
fp
    vSetCurrentDirectory :: HVFSChroot a -> String -> IO ()
vSetCurrentDirectory = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO ()
vSetCurrentDirectory
    vGetDirectoryContents :: HVFSChroot a -> String -> IO [String]
vGetDirectoryContents = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO [String]
vGetDirectoryContents
    vDoesFileExist :: HVFSChroot a -> String -> IO Bool
vDoesFileExist = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO Bool
vDoesFileExist
    vDoesDirectoryExist :: HVFSChroot a -> String -> IO Bool
vDoesDirectoryExist = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO Bool
vDoesDirectoryExist
    vCreateDirectory :: HVFSChroot a -> String -> IO ()
vCreateDirectory = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO ()
vCreateDirectory
    vRemoveDirectory :: HVFSChroot a -> String -> IO ()
vRemoveDirectory = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO ()
vRemoveDirectory
    vRenameDirectory :: HVFSChroot a -> String -> String -> IO ()
vRenameDirectory HVFSChroot a
fh String
old String
new = do String
old' <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
old
                                     String
new' <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
new
                                     forall a. HVFS a => a -> String -> String -> IO ()
vRenameDirectory (forall t. HVFS t => HVFSChroot t -> t
dch HVFSChroot a
fh) String
old' String
new'
    vRemoveFile :: HVFSChroot a -> String -> IO ()
vRemoveFile = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO ()
vRemoveFile
    vRenameFile :: HVFSChroot a -> String -> String -> IO ()
vRenameFile HVFSChroot a
fh String
old String
new = do String
old' <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
old
                                String
new' <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
new
                                forall a. HVFS a => a -> String -> String -> IO ()
vRenameFile (forall t. HVFS t => HVFSChroot t -> t
dch HVFSChroot a
fh) String
old' String
new'
    vGetFileStatus :: HVFSChroot a -> String -> IO HVFSStatEncap
vGetFileStatus = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO HVFSStatEncap
vGetFileStatus
    vGetSymbolicLinkStatus :: HVFSChroot a -> String -> IO HVFSStatEncap
vGetSymbolicLinkStatus = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO HVFSStatEncap
vGetSymbolicLinkStatus
    vGetModificationTime :: HVFSChroot a -> String -> IO ClockTime
vGetModificationTime = forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO ClockTime
vGetModificationTime
    -- vRaiseError
    vCreateSymbolicLink :: HVFSChroot a -> String -> String -> IO ()
vCreateSymbolicLink HVFSChroot a
fh String
old String
new = do String
old' <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
old
                                        String
new' <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
new
                                        forall a. HVFS a => a -> String -> String -> IO ()
vCreateSymbolicLink (forall t. HVFS t => HVFSChroot t -> t
dch HVFSChroot a
fh) String
old' String
new'
    vReadSymbolicLink :: HVFSChroot a -> String -> IO String
vReadSymbolicLink HVFSChroot a
fh String
fp = do String
result <- forall t t1.
HVFS t =>
(t -> String -> IO t1) -> HVFSChroot t -> String -> IO t1
dch2fph forall a. HVFS a => a -> String -> IO String
vReadSymbolicLink HVFSChroot a
fh String
fp
                                 forall t. HVFS t => HVFSChroot t -> String -> IO String
fp2dch HVFSChroot a
fh String
result
    vCreateLink :: HVFSChroot a -> String -> String -> IO ()
vCreateLink HVFSChroot a
fh String
old String
new = do String
old' <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
old
                                String
new' <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
new
                                forall a. HVFS a => a -> String -> String -> IO ()
vCreateLink (forall t. HVFS t => HVFSChroot t -> t
dch HVFSChroot a
fh) String
old' String
new'

instance HVFSOpenable a => HVFSOpenable (HVFSChroot a) where
    vOpen :: HVFSChroot a -> String -> IOMode -> IO HVFSOpenEncap
vOpen HVFSChroot a
fh String
fp IOMode
mode = do String
newfile <- forall t. HVFS t => HVFSChroot t -> String -> IO String
dch2fp HVFSChroot a
fh String
fp
                          forall a.
HVFSOpenable a =>
a -> String -> IOMode -> IO HVFSOpenEncap
vOpen (forall t. HVFS t => HVFSChroot t -> t
dch HVFSChroot a
fh) String
newfile IOMode
mode