keter/Codec/Archive/TempTarball.hs

118 lines
4.4 KiB
Haskell
Raw Normal View History

{-# LANGUAGE BangPatterns #-}
2012-05-14 12:18:09 +04:00
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | Handles allocation of temporary directories and unpacking of bundles into
-- them. Sets owner and group of all created files and directories as
-- necessary.
2013-07-10 15:15:18 +04:00
module Codec.Archive.TempTarball
2012-05-08 16:18:06 +04:00
( TempFolder
, setup
, unpackTempTar
2012-05-08 16:18:06 +04:00
) where
import qualified Codec.Archive.Tar as Tar
import qualified Codec.Archive.Tar.Check as Tar
import qualified Codec.Archive.Tar.Entry as Tar
import Codec.Compression.GZip (decompress)
import Control.Exception (bracket, bracketOnError, throwIO)
2013-07-10 15:15:18 +04:00
import Control.Monad (unless, when)
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import qualified Data.IORef as I
2013-07-10 15:15:18 +04:00
import Data.Monoid ((<>))
2015-05-12 13:01:58 +03:00
import Data.Text (Text, pack, unpack)
import Data.Word (Word)
2015-05-12 13:01:58 +03:00
import System.FilePath ((</>))
import qualified System.FilePath as F
import qualified System.Directory as D
import Foreign.Ptr (castPtr)
import System.Posix.Files (setFdOwnerAndGroup,
setOwnerAndGroup)
import System.Posix.IO (FdOption (CloseOnExec), closeFd,
createFile, fdWriteBuf, setFdOption)
import System.Posix.Types (GroupID, UserID)
2012-05-08 16:18:06 +04:00
data TempFolder = TempFolder
2015-05-12 13:01:58 +03:00
{ tfRoot :: FilePath
2013-07-10 15:15:18 +04:00
, tfCounter :: I.IORef Word
2012-05-08 16:18:06 +04:00
}
2015-05-12 13:01:58 +03:00
setup :: FilePath -> IO TempFolder
setup fp = do
2015-05-12 13:01:58 +03:00
e <- D.doesDirectoryExist fp
when e $ D.removeDirectoryRecursive fp
D.createDirectoryIfMissing True fp
2012-05-08 16:18:06 +04:00
c <- I.newIORef minBound
return $ TempFolder fp c
2013-07-10 15:15:18 +04:00
getFolder :: Maybe (UserID, GroupID)
-> TempFolder
-> Text -- ^ prefix for folder name
2015-05-12 13:01:58 +03:00
-> IO FilePath
getFolder muid TempFolder {..} appname = do
!i <- I.atomicModifyIORef tfCounter $ \i -> (succ i, i)
2015-05-12 13:01:58 +03:00
let fp = tfRoot </> unpack (appname <> "-" <> pack (show i))
D.createDirectoryIfMissing True fp
case muid of
Nothing -> return ()
2015-05-12 13:01:58 +03:00
Just (uid, gid) -> setOwnerAndGroup fp uid gid
return fp
unpackTempTar :: Maybe (UserID, GroupID)
-> TempFolder
2015-05-12 13:01:58 +03:00
-> FilePath -- ^ bundle
2013-07-10 15:15:18 +04:00
-> Text -- ^ prefix for folder name
2015-05-12 13:01:58 +03:00
-> (FilePath -> IO a)
-> IO a
unpackTempTar muid tf bundle appname withDir = do
2015-05-12 13:01:58 +03:00
lbs <- L.readFile bundle
bracketOnError (getFolder muid tf appname) D.removeDirectoryRecursive $ \dir -> do
unpackTar muid dir $ Tar.read $ decompress lbs
withDir dir
unpackTar :: Maybe (UserID, GroupID)
2015-05-12 13:01:58 +03:00
-> FilePath
2013-07-10 15:15:18 +04:00
-> Tar.Entries Tar.FormatError
-> IO ()
unpackTar muid dir =
loop . Tar.checkSecurity
where
loop Tar.Done = return ()
loop (Tar.Fail e) = either throwIO throwIO e
loop (Tar.Next e es) = go e >> loop es
go e = do
2015-05-12 13:01:58 +03:00
let fp = dir </> Tar.entryPath e
case Tar.entryContent e of
Tar.NormalFile lbs _ -> do
case muid of
2015-05-12 13:01:58 +03:00
Nothing -> D.createDirectoryIfMissing True $ F.takeDirectory fp
Just (uid, gid) -> createTreeUID uid gid $ F.takeDirectory fp
let write fd bs = unsafeUseAsCStringLen bs $ \(ptr, len) -> do
_ <- fdWriteBuf fd (castPtr ptr) (fromIntegral len)
return ()
bracket
(do
2015-05-12 13:01:58 +03:00
fd <- createFile fp $ Tar.entryPermissions e
setFdOption fd CloseOnExec True
case muid of
Nothing -> return ()
Just (uid, gid) -> setFdOwnerAndGroup fd uid gid
return fd)
closeFd
(\fd -> mapM_ (write fd) (L.toChunks lbs))
_ -> return ()
-- | Create a directory tree, setting the uid and gid of all newly created
-- folders.
2015-05-12 13:01:58 +03:00
createTreeUID :: UserID -> GroupID -> FilePath -> IO ()
createTreeUID uid gid =
go
where
go fp = do
2015-05-12 13:01:58 +03:00
exists <- D.doesDirectoryExist fp
unless exists $ do
2015-05-12 13:01:58 +03:00
go $ F.takeDirectory fp
D.createDirectoryIfMissing False fp
setOwnerAndGroup fp uid gid