2013-07-10 15:09:14 +04:00
|
|
|
{-# LANGUAGE BangPatterns #-}
|
2012-05-14 12:18:09 +04:00
|
|
|
{-# LANGUAGE OverloadedStrings #-}
|
2013-07-10 15:09:14 +04:00
|
|
|
{-# 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
|
2013-07-10 15:09:14 +04:00
|
|
|
, unpackTempTar
|
2012-05-08 16:18:06 +04:00
|
|
|
) where
|
|
|
|
|
2013-07-10 15:09:14 +04:00
|
|
|
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)
|
2013-07-10 15:09:14 +04:00
|
|
|
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)
|
2013-07-10 15:09:14 +04:00
|
|
|
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
|
2013-07-10 15:09:14 +04:00
|
|
|
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
|
2013-07-10 15:09:14 +04:00
|
|
|
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
|
2012-12-27 13:52:55 +04:00
|
|
|
getFolder muid TempFolder {..} appname = do
|
2013-07-10 15:09:14 +04:00
|
|
|
!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
|
2013-07-10 15:09:14 +04:00
|
|
|
case muid of
|
|
|
|
Nothing -> return ()
|
2015-05-12 13:01:58 +03:00
|
|
|
Just (uid, gid) -> setOwnerAndGroup fp uid gid
|
2013-07-10 15:09:14 +04:00
|
|
|
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)
|
2013-07-10 15:09:14 +04:00
|
|
|
-> 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
|
2013-07-10 15:09:14 +04:00
|
|
|
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 ()
|
2013-07-10 15:09:14 +04:00
|
|
|
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
|
2013-07-10 15:09:14 +04:00
|
|
|
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
|
2013-07-10 15:09:14 +04:00
|
|
|
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
|
2013-07-10 15:09:14 +04:00
|
|
|
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 ()
|
2013-07-10 15:09:14 +04:00
|
|
|
createTreeUID uid gid =
|
|
|
|
go
|
|
|
|
where
|
|
|
|
go fp = do
|
2015-05-12 13:01:58 +03:00
|
|
|
exists <- D.doesDirectoryExist fp
|
2013-07-10 15:09:14 +04:00
|
|
|
unless exists $ do
|
2015-05-12 13:01:58 +03:00
|
|
|
go $ F.takeDirectory fp
|
|
|
|
D.createDirectoryIfMissing False fp
|
|
|
|
setOwnerAndGroup fp uid gid
|