Refactor Keter.TempFolder and Keter.App a bit

This commit is contained in:
Michael Snoyman 2013-07-10 14:09:14 +03:00
parent 0faa41cf21
commit 708737019d
4 changed files with 111 additions and 107 deletions

View File

@ -16,30 +16,17 @@ import Keter.TempFolder
import Keter.Process import Keter.Process
import Keter.Types import Keter.Types
import Keter.PortManager hiding (start) import Keter.PortManager hiding (start)
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 qualified Filesystem.Path.CurrentOS as F import qualified Filesystem.Path.CurrentOS as F
import qualified Filesystem as F
import Data.Yaml import Data.Yaml
import Control.Applicative ((<$>), (<*>), (<|>), pure) import Control.Applicative ((<$>), (<*>), (<|>), pure)
import qualified Network import qualified Network
import Data.Maybe (fromMaybe, mapMaybe) import Data.Maybe (fromMaybe, mapMaybe)
import Control.Exception (onException, throwIO, bracket) import Control.Exception (throwIO)
import System.IO (hClose) import System.IO (hClose)
import qualified Data.ByteString.Lazy as L
import Data.Conduit (($$), yield)
import Data.Set (Set) import Data.Set (Set)
import qualified Data.Set as Set import qualified Data.Set as Set
import qualified Data.Conduit.List as CL
import System.Posix.IO (fdWriteBuf, closeFd, FdOption (CloseOnExec), setFdOption, createFile)
import Foreign.Ptr (castPtr)
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import Data.Text.Encoding (encodeUtf8) import Data.Text.Encoding (encodeUtf8)
import System.Posix.Types (UserID, GroupID) import System.Posix.Types (UserID, GroupID)
import System.Posix.Files (setOwnerAndGroup, setFdOwnerAndGroup)
import Control.Monad (unless)
import Data.Conduit.Process.Unix (ProcessTracker, RotatingLog) import Data.Conduit.Process.Unix (ProcessTracker, RotatingLog)
data AppConfig = AppConfig data AppConfig = AppConfig
@ -107,30 +94,20 @@ unpackBundle :: TempFolder
-> Appname -> Appname
-> KIO (Either SomeException (FilePath, Config)) -> KIO (Either SomeException (FilePath, Config))
unpackBundle tf muid bundle appname = do unpackBundle tf muid bundle appname = do
elbs <- readFileLBS bundle log $ UnpackingBundle bundle
case elbs of liftIO $ unpackTempTar muid tf bundle appname $ \dir -> do
Left e -> return $ Left e let configFP = dir F.</> "config" F.</> "keter.yaml"
Right lbs -> do mconfig <- decodeFile $ F.encodeString configFP
edir <- getFolder muid tf appname config <-
case edir of case mconfig of
Left e -> return $ Left e Just config -> return config
Right dir -> do Nothing -> throwIO InvalidConfigFile
log $ UnpackingBundle bundle dir return (dir, config
let rest = do { configStaticHosts = Set.fromList
unpackTar muid dir $ Tar.read $ decompress lbs $ mapMaybe (fixStaticHost dir)
let configFP = dir F.</> "config" F.</> "keter.yaml" $ Set.toList
mconfig <- decodeFile $ F.encodeString configFP $ configStaticHosts config
config <- })
case mconfig of
Just config -> return config
Nothing -> throwIO InvalidConfigFile
return (dir, config
{ configStaticHosts = Set.fromList
$ mapMaybe (fixStaticHost dir)
$ Set.toList
$ configStaticHosts config
})
liftIO $ rest `onException` removeTree dir
-- | Ensures that the given path does not escape the containing folder and sets -- | Ensures that the given path does not escape the containing folder and sets
-- the pathname based on config file location. -- the pathname based on config file location.
@ -143,50 +120,6 @@ fixStaticHost dir sh =
fp0 = shRoot sh fp0 = shRoot sh
fp = F.collapse $ dir F.</> "config" F.</> fp0 fp = F.collapse $ dir F.</> "config" F.</> fp0
-- | Create a directory tree, setting the uid and gid of all newly created
-- folders.
createTreeUID :: UserID -> GroupID -> FilePath -> IO ()
createTreeUID uid gid =
go
where
go fp = do
exists <- F.isDirectory fp
unless exists $ do
go $ F.parent fp
F.createDirectory False fp
setOwnerAndGroup (F.encodeString fp) uid gid
unpackTar :: Maybe (UserID, GroupID)
-> FilePath -> 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
let fp = dir </> decodeString (Tar.entryPath e)
case Tar.entryContent e of
Tar.NormalFile lbs _ -> do
case muid of
Nothing -> createTree $ F.directory fp
Just (uid, gid) -> createTreeUID uid gid $ F.directory fp
let write fd bs = unsafeUseAsCStringLen bs $ \(ptr, len) -> do
_ <- fdWriteBuf fd (castPtr ptr) (fromIntegral len)
return ()
bracket
(do
fd <- createFile (F.encodeString 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_ yield (L.toChunks lbs) $$ CL.mapM_ (write fd))
_ -> return ()
start :: TempFolder start :: TempFolder
-> Maybe (Text, (UserID, GroupID)) -> Maybe (Text, (UserID, GroupID))
-> ProcessTracker -> ProcessTracker

View File

@ -106,7 +106,7 @@ keter (F.decodeString -> input) mkPlugins = do
processTracker <- initProcessTracker processTracker <- initProcessTracker
portman <- runThrow $ PortMan.start configPortMan portman <- runThrow $ PortMan.start configPortMan
tf <- runThrow $ TempFolder.setup $ configDir </> "temp" tf <- runThrow $ liftIO $ TempFolder.setup $ configDir </> "temp"
plugins <- runThrow $ loadPlugins $ map ($ configDir) mkPlugins plugins <- runThrow $ loadPlugins $ map ($ configDir) mkPlugins
mainlog <- runThrow $ liftIO $ LogFile.openRotatingLog mainlog <- runThrow $ liftIO $ LogFile.openRotatingLog
(F.encodeString $ configDir </> "log" </> "keter") (F.encodeString $ configDir </> "log" </> "keter")

View File

@ -157,7 +157,7 @@ data LogMessage
| ProcessDidNotStart F.FilePath | ProcessDidNotStart F.FilePath
| ExceptionThrown T.Text E.SomeException | ExceptionThrown T.Text E.SomeException
| RemovingPort P.Int | RemovingPort P.Int
| UnpackingBundle F.FilePath F.FilePath | UnpackingBundle F.FilePath
| TerminatingApp T.Text | TerminatingApp T.Text
| FinishedReloading T.Text | FinishedReloading T.Text
| TerminatingOldProcess T.Text | TerminatingOldProcess T.Text
@ -183,11 +183,10 @@ instance P.Show LogMessage where
, P.show e , P.show e
] ]
show (RemovingPort p) = "Port in use, removing from port pool: " ++ P.show p show (RemovingPort p) = "Port in use, removing from port pool: " ++ P.show p
show (UnpackingBundle b dir) = P.concat show (UnpackingBundle b) = P.concat
[ "Unpacking bundle '" [ "Unpacking bundle '"
, F.encodeString b , F.encodeString b
, "' into folder: " , "'"
, F.encodeString dir
] ]
show (TerminatingApp t) = "Shutting down app: " ++ T.unpack t show (TerminatingApp t) = "Shutting down app: " ++ T.unpack t
show (FinishedReloading t) = "App finished reloading: " ++ T.unpack t show (FinishedReloading t) = "App finished reloading: " ++ T.unpack t

View File

@ -1,41 +1,113 @@
{-# LANGUAGE BangPatterns #-} {-# LANGUAGE BangPatterns #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE NoImplicitPrelude #-} {-# LANGUAGE NoImplicitPrelude #-}
{-# LANGUAGE OverloadedStrings #-} {-# 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.
module Keter.TempFolder module Keter.TempFolder
( TempFolder ( TempFolder
, setup , setup
, getFolder , unpackTempTar
) where ) where
import Keter.Prelude import qualified Codec.Archive.Tar as Tar
import Data.Word (Word) import qualified Codec.Archive.Tar.Check as Tar
import Keter.Types (Appname) import qualified Codec.Archive.Tar.Entry as Tar
import qualified Data.IORef as I import Codec.Compression.GZip (decompress)
import System.Posix.Files (setOwnerAndGroup) import Control.Exception (bracket, bracketOnError, throwIO)
import System.Posix.Types (UserID, GroupID) import Control.Monad (unless)
import qualified Data.ByteString.Lazy as L
import Data.ByteString.Unsafe (unsafeUseAsCStringLen)
import qualified Data.IORef as I
import Data.Word (Word)
import qualified Filesystem as F
import qualified Filesystem.Path.CurrentOS as F import qualified Filesystem.Path.CurrentOS as F
import Foreign.Ptr (castPtr)
import Keter.Prelude
import Keter.Types (Appname)
import Prelude (IO)
import System.Posix.Files (setFdOwnerAndGroup,
setOwnerAndGroup)
import System.Posix.IO (FdOption (CloseOnExec), closeFd,
createFile, fdWriteBuf, setFdOption)
import System.Posix.Types (GroupID, UserID)
data TempFolder = TempFolder data TempFolder = TempFolder
{ tfRoot :: FilePath { tfRoot :: FilePath
, tfCounter :: IORef Word , tfCounter :: IORef Word
} }
setup :: FilePath -> KIO (Either SomeException TempFolder) setup :: FilePath -> IO TempFolder
setup fp = liftIO $ do setup fp = do
e <- isDirectory fp e <- isDirectory fp
when e $ removeTree fp when e $ removeTree fp
createTree fp createTree fp
c <- I.newIORef minBound c <- I.newIORef minBound
return $ TempFolder fp c return $ TempFolder fp c
getFolder :: Maybe (UserID, GroupID) -> TempFolder -> Appname -> KIO (Either SomeException FilePath) getFolder :: Maybe (UserID, GroupID) -> TempFolder -> Appname -> IO FilePath
getFolder muid TempFolder {..} appname = do getFolder muid TempFolder {..} appname = do
!i <- atomicModifyIORef tfCounter $ \i -> (succ i, i) !i <- I.atomicModifyIORef tfCounter $ \i -> (succ i, i)
let fp = tfRoot </> fromText (appname ++ "-" ++ show i) let fp = tfRoot </> fromText (appname ++ "-" ++ show i)
liftIO $ do createTree fp
createTree fp case muid of
case muid of Nothing -> return ()
Nothing -> return () Just (uid, gid) -> setOwnerAndGroup (F.encodeString fp) uid gid
Just (uid, gid) -> setOwnerAndGroup (F.encodeString fp) uid gid return fp
return fp
unpackTempTar :: Maybe (UserID, GroupID)
-> TempFolder
-> FilePath -- ^ bundle
-> Appname
-> (F.FilePath -> IO a)
-> IO a
unpackTempTar muid tf bundle appname withDir = do
lbs <- L.readFile $ F.encodeString bundle
bracketOnError (getFolder muid tf appname) removeTree $ \dir -> do
unpackTar muid dir $ Tar.read $ decompress lbs
withDir dir
unpackTar :: Maybe (UserID, GroupID)
-> FilePath -> 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
let fp = dir </> decodeString (Tar.entryPath e)
case Tar.entryContent e of
Tar.NormalFile lbs _ -> do
case muid of
Nothing -> createTree $ F.directory fp
Just (uid, gid) -> createTreeUID uid gid $ F.directory fp
let write fd bs = unsafeUseAsCStringLen bs $ \(ptr, len) -> do
_ <- fdWriteBuf fd (castPtr ptr) (fromIntegral len)
return ()
bracket
(do
fd <- createFile (F.encodeString 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.
createTreeUID :: UserID -> GroupID -> FilePath -> IO ()
createTreeUID uid gid =
go
where
go fp = do
exists <- F.isDirectory fp
unless exists $ do
go $ F.parent fp
F.createDirectory False fp
setOwnerAndGroup (F.encodeString fp) uid gid