diff options
-rw-r--r-- | Annex/UUID.hs | 2 | ||||
-rw-r--r-- | Backend/SHA.hs | 1 | ||||
-rw-r--r-- | Command/Map.hs | 13 | ||||
-rw-r--r-- | Config.hs | 2 | ||||
-rw-r--r-- | Git/Command.hs | 13 | ||||
-rw-r--r-- | Git/Config.hs | 15 | ||||
-rw-r--r-- | Git/Queue.hs | 13 | ||||
-rw-r--r-- | Git/UpdateIndex.hs | 13 | ||||
-rw-r--r-- | Remote/Bup.hs | 12 | ||||
-rw-r--r-- | Remote/Git.hs | 13 | ||||
-rw-r--r-- | Seek.hs | 4 | ||||
-rw-r--r-- | Utility/CoProcess.hs | 12 | ||||
-rw-r--r-- | Utility/Gpg.hs | 45 | ||||
-rw-r--r-- | Utility/INotify.hs | 1 | ||||
-rw-r--r-- | Utility/Lsof.hs | 9 | ||||
-rw-r--r-- | Utility/Process.hs | 200 | ||||
-rw-r--r-- | Utility/SafeCommand.hs | 3 |
17 files changed, 248 insertions, 123 deletions
diff --git a/Annex/UUID.hs b/Annex/UUID.hs index 1d2175bcb..13cee865d 100644 --- a/Annex/UUID.hs +++ b/Annex/UUID.hs @@ -20,8 +20,6 @@ module Annex.UUID ( removeRepoUUID, ) where -import System.Process - import Common.Annex import qualified Git import qualified Git.Config diff --git a/Backend/SHA.hs b/Backend/SHA.hs index a1dd1cf64..04b3e362a 100644 --- a/Backend/SHA.hs +++ b/Backend/SHA.hs @@ -12,7 +12,6 @@ import qualified Annex import Types.Backend import Types.Key import Types.KeySource -import System.Process import qualified Build.SysConfig as SysConfig import Data.Digest.Pure.SHA diff --git a/Command/Map.hs b/Command/Map.hs index f69b88a5d..3dbdadbd6 100644 --- a/Command/Map.hs +++ b/Command/Map.hs @@ -9,7 +9,6 @@ module Command.Map where import Control.Exception.Extensible import qualified Data.Map as M -import System.Process import Common.Annex import Command @@ -199,13 +198,11 @@ tryScan r case result of Left _ -> return Nothing Right r' -> return $ Just r' - pipedconfig cmd params = safely $ do - (_, Just h, _, pid) <- - createProcess (proc cmd $ toCommand params) - { std_out = CreatePipe } - r' <- Git.Config.hRead r h - forceSuccessProcess pid cmd $ toCommand params - return r' + pipedconfig cmd params = safely $ + withHandle StdoutHandle createProcessSuccess p $ + Git.Config.hRead r + where + p = proc cmd $ toCommand params configlist = onRemote r (pipedconfig, Nothing) "configlist" [] [] @@ -7,8 +7,6 @@ module Config where -import System.Process - import Common.Annex import qualified Git import qualified Git.Config diff --git a/Git/Command.hs b/Git/Command.hs index 038824f26..d7c983064 100644 --- a/Git/Command.hs +++ b/Git/Command.hs @@ -7,7 +7,6 @@ module Git.Command where -import System.Process import System.Posix.Process (getAnyProcessStatus) import Common @@ -41,12 +40,12 @@ run subcommand params repo = assertLocal repo $ - result unless reap is called. -} pipeRead :: [CommandParam] -> Repo -> IO String -pipeRead params repo = assertLocal repo $ do - (_, Just h, _, _) <- createProcess - (proc "git" $ toCommand $ gitCommandLine params repo) - { std_out = CreatePipe } - fileEncoding h - hGetContents h +pipeRead params repo = assertLocal repo $ + withHandle StdoutHandle createBackgroundProcess p $ \h -> do + fileEncoding h + hGetContents h + where + p = proc "git" $ toCommand $ gitCommandLine params repo {- Runs a git subcommand, feeding it input, and returning its output, - which is expected to be fairly small, since it's all read into memory diff --git a/Git/Config.hs b/Git/Config.hs index 234750113..c82d6bb1b 100644 --- a/Git/Config.hs +++ b/Git/Config.hs @@ -9,7 +9,7 @@ module Git.Config where import qualified Data.Map as M import Data.Char -import System.Process +import System.Process (cwd) import Common import Git @@ -48,14 +48,11 @@ read' repo = go repo go Repo { location = Local { gitdir = d } } = git_config d go Repo { location = LocalUnknown d } = git_config d go _ = assertLocal repo $ error "internal" - git_config d = do - (_, Just h, _, pid) - <- createProcess (proc "git" params) - { std_out = CreatePipe, cwd = Just d } - repo' <- hRead repo h - forceSuccessProcess pid "git" params - return repo' - params = ["config", "--null", "--list"] + git_config d = withHandle StdoutHandle createProcessSuccess p $ + hRead repo + where + params = ["config", "--null", "--list"] + p = (proc "git" params) { cwd = Just d } {- Reads git config from a handle and populates a repo with it. -} hRead :: Repo -> Handle -> IO Repo diff --git a/Git/Queue.hs b/Git/Queue.hs index 4e6f05c2e..f515ad104 100644 --- a/Git/Queue.hs +++ b/Git/Queue.hs @@ -19,7 +19,6 @@ module Git.Queue ( import qualified Data.Map as M import System.IO -import System.Process import Data.String.Utils import Utility.SafeCommand @@ -148,13 +147,11 @@ runAction :: Repo -> Action -> IO () runAction repo (UpdateIndexAction streamers) = -- list is stored in reverse order Git.UpdateIndex.streamUpdateIndex repo $ reverse streamers -runAction repo action@(CommandAction {}) = do - (Just h, _, _, pid) <- createProcess (proc "xargs" params) - { std_in = CreatePipe } - fileEncoding h - hPutStr h $ join "\0" $ getFiles action - hClose h - forceSuccessProcess pid "xargs" params +runAction repo action@(CommandAction {}) = + withHandle StdinHandle createProcessSuccess (proc "xargs" params) $ \h -> do + fileEncoding h + hPutStr h $ join "\0" $ getFiles action + hClose h where params = "-0":"git":baseparams baseparams = toCommand $ gitCommandLine diff --git a/Git/UpdateIndex.hs b/Git/UpdateIndex.hs index 6de0c3ada..929448729 100644 --- a/Git/UpdateIndex.hs +++ b/Git/UpdateIndex.hs @@ -17,8 +17,6 @@ module Git.UpdateIndex ( stageSymlink ) where -import System.Process - import Common import Git import Git.Types @@ -36,12 +34,11 @@ pureStreamer !s = \streamer -> streamer s {- Streams content into update-index from a list of Streamers. -} streamUpdateIndex :: Repo -> [Streamer] -> IO () -streamUpdateIndex repo as = do - (Just h, _, _, p) <- createProcess (proc "git" ps) { std_in = CreatePipe } - fileEncoding h - forM_ as (stream h) - hClose h - forceSuccessProcess p "git" ps +streamUpdateIndex repo as = + withHandle StdinHandle createProcessSuccess (proc "git" ps) $ \h -> do + fileEncoding h + forM_ as (stream h) + hClose h where ps = toCommand $ gitCommandLine params repo params = map Param ["update-index", "-z", "--index-info"] diff --git a/Remote/Bup.hs b/Remote/Bup.hs index 9da374174..8a2c1afef 100644 --- a/Remote/Bup.hs +++ b/Remote/Bup.hs @@ -133,15 +133,13 @@ retrieveCheap :: BupRepo -> Key -> FilePath -> Annex Bool retrieveCheap _ _ _ = return False retrieveEncrypted :: BupRepo -> (Cipher, Key) -> Key -> FilePath -> Annex Bool -retrieveEncrypted buprepo (cipher, enck) _ f = do - let params = bupParams "join" buprepo [Param $ bupRef enck] - liftIO $ catchBoolIO $ do - (_, Just h, _, pid) - <- createProcess (proc "bup" $ toCommand params) - { std_out = CreatePipe } +retrieveEncrypted buprepo (cipher, enck) _ f = liftIO $ catchBoolIO $ + withHandle StdoutHandle createProcessSuccess p $ \h -> do withDecryptedContent cipher (L.hGetContents h) $ L.writeFile f - forceSuccessProcess pid "bup" $ toCommand params return True + where + params = bupParams "join" buprepo [Param $ bupRef enck] + p = proc "bup" $ toCommand params remove :: Key -> Annex Bool remove _ = do diff --git a/Remote/Git.hs b/Remote/Git.hs index a9a6d6004..3412de89b 100644 --- a/Remote/Git.hs +++ b/Remote/Git.hs @@ -9,7 +9,6 @@ module Remote.Git (remote, repoAvail) where import qualified Data.Map as M import Control.Exception.Extensible -import System.Process import Common.Annex import Utility.CopyFile @@ -127,13 +126,11 @@ tryGitConfigRead r safely a = either (const $ return r) return =<< liftIO (try a :: IO (Either SomeException Git.Repo)) - pipedconfig cmd params = safely $ do - (_, Just h, _, pid) <- - createProcess (proc cmd $ toCommand params) - { std_out = CreatePipe } - r' <- Git.Config.hRead r h - forceSuccessProcess pid cmd $ toCommand params - return r' + pipedconfig cmd params = safely $ + withHandle StdoutHandle createProcessSuccess p $ + Git.Config.hRead r + where + p = proc cmd $ toCommand params geturlconfig headers = do s <- Url.get (Git.repoLocation r ++ "/config") headers @@ -108,9 +108,9 @@ withNothing _ _ = error "This command takes no parameters." prepFiltered :: (FilePath -> CommandStart) -> Annex [FilePath] -> Annex [CommandStart] prepFiltered a fs = do matcher <- Limit.getMatcher - map (proc matcher) <$> fs + map (process matcher) <$> fs where - proc matcher f = do + process matcher f = do ok <- matcher f if ok then a f else return Nothing diff --git a/Utility/CoProcess.hs b/Utility/CoProcess.hs index d3b0c46ef..67f861bb3 100644 --- a/Utility/CoProcess.hs +++ b/Utility/CoProcess.hs @@ -13,25 +13,23 @@ module Utility.CoProcess ( query ) where -import System.Process - import Common -type CoProcessHandle = (ProcessHandle, Handle, Handle, FilePath, [String]) +type CoProcessHandle = (ProcessHandle, Handle, Handle, CreateProcess) start :: FilePath -> [String] -> IO CoProcessHandle start command params = do (from, to, _err, pid) <- runInteractiveProcess command params Nothing Nothing - return (pid, to, from, command, params) + return (pid, to, from, proc command params) stop :: CoProcessHandle -> IO () -stop (pid, from, to, command, params) = do +stop (pid, from, to, p) = do hClose to hClose from - forceSuccessProcess pid command params + forceSuccessProcess p pid query :: CoProcessHandle -> (Handle -> IO a) -> (Handle -> IO b) -> IO b -query (_, from, to, _, _) send receive = do +query (_, from, to, _) send receive = do _ <- send to hFlush to receive from diff --git a/Utility/Gpg.hs b/Utility/Gpg.hs index 26ac688e3..eed77805c 100644 --- a/Utility/Gpg.hs +++ b/Utility/Gpg.hs @@ -13,7 +13,6 @@ import Control.Applicative import Control.Concurrent import Control.Exception (bracket) import System.Posix.Env (setEnv, unsetEnv, getEnv) -import System.Process import Common @@ -39,30 +38,21 @@ stdParams params = do readStrict :: [CommandParam] -> IO String readStrict params = do params' <- stdParams params - (_, Just from, _, pid) - <- createProcess (proc "gpg" params') - { std_out = CreatePipe } - hSetBinaryMode from True - r <- hGetContentsStrict from - forceSuccessProcess pid "gpg" params' - return r + withHandle StdoutHandle createProcessSuccess (proc "gpg" params') $ \h -> do + hSetBinaryMode h True + hGetContentsStrict h {- Runs gpg, piping an input value to it, and returning its stdout, - strictly. -} pipeStrict :: [CommandParam] -> String -> IO String pipeStrict params input = do params' <- stdParams params - (Just to, Just from, _, pid) - <- createProcess (proc "gpg" params') - { std_in = CreatePipe - , std_out = CreatePipe } - hSetBinaryMode to True - hSetBinaryMode from True - hPutStr to input - hClose to - r <- hGetContentsStrict from - forceSuccessProcess pid "gpg" params' - return r + withBothHandles createProcessSuccess (proc "gpg" params') $ \(to, from) -> do + hSetBinaryMode to True + hSetBinaryMode from True + hPutStr to input + hClose to + hGetContentsStrict from {- Runs gpg with some parameters, first feeding it a passphrase via - --passphrase-fd, then feeding it an input, and passing a handle @@ -82,16 +72,13 @@ passphraseHandle params passphrase a b = do let passphrasefd = [Param "--passphrase-fd", Param $ show pfd] params' <- stdParams $ passphrasefd ++ params - (Just toh, Just fromh, _, pid) <- createProcess (proc "gpg" params') - { std_in = CreatePipe, std_out = CreatePipe } - L.hPut toh =<< a - hClose toh - ret <- b fromh - - -- cleanup - forceSuccessProcess pid "gpg" params' - closeFd frompipe - return ret + closeFd frompipe `after` + withBothHandles createProcessSuccess (proc "gpg" params') go + where + go (to, from) = do + L.hPut to =<< a + hClose to + b from {- Finds gpg public keys matching some string. (Could be an email address, - a key id, or a name. -} diff --git a/Utility/INotify.hs b/Utility/INotify.hs index 55233ef76..66c0ab23d 100644 --- a/Utility/INotify.hs +++ b/Utility/INotify.hs @@ -10,7 +10,6 @@ module Utility.INotify where import Common hiding (isDirectory) import Utility.ThreadLock import Utility.Types.DirWatcher -import System.Process import System.INotify import qualified System.Posix.Files as Files diff --git a/Utility/Lsof.hs b/Utility/Lsof.hs index ebd273b2e..ce6a16283 100644 --- a/Utility/Lsof.hs +++ b/Utility/Lsof.hs @@ -12,7 +12,6 @@ module Utility.Lsof where import Common import System.Posix.Types -import System.Process data LsofOpenMode = OpenReadWrite | OpenReadOnly | OpenWriteOnly | OpenUnknown deriving (Show, Eq) @@ -34,9 +33,11 @@ queryDir path = query ["+d", path] - Note: If lsof is not available, this always returns [] ! -} query :: [String] -> IO [(FilePath, LsofOpenMode, ProcessInfo)] -query opts = do - (_, s, _) <- readProcessWithExitCode "lsof" ("-F0can" : opts) [] - return $ parse s +query opts = + withHandle StdoutHandle (createProcessChecked checkSuccessProcess) p $ \h -> do + parse <$> hGetContentsStrict h + where + p = proc "lsof" ("-F0can" : opts) {- Parsing null-delimited output like: - diff --git a/Utility/Process.hs b/Utility/Process.hs index 9f79efa81..9b57c3b7a 100644 --- a/Utility/Process.hs +++ b/Utility/Process.hs @@ -1,40 +1,202 @@ -{- System.Process enhancements +{- System.Process enhancements, including additional ways of running + - processes, and logging. - - Copyright 2012 Joey Hess <joey@kitenet.net> - - Licensed under the GNU GPL version 3 or higher. -} -module Utility.Process where +{-# LANGUAGE Rank2Types #-} -import System.Process +module Utility.Process ( + module X, + CreateProcess, + StdHandle(..), + readProcessEnv, + forceSuccessProcess, + checkSuccessProcess, + createProcessSuccess, + createProcessChecked, + createBackgroundProcess, + withHandle, + withBothHandles, + createProcess, + runInteractiveProcess, + readProcess +) where + +import qualified System.Process +import System.Process as X hiding (CreateProcess(..), createProcess, runInteractiveProcess, readProcess, readProcessWithExitCode, system, rawSystem, runInteractiveCommand, runProcess) +import System.Process hiding (createProcess, runInteractiveProcess, readProcess, readProcessWithExitCode) import System.Exit import System.IO +import System.Log.Logger import Utility.Misc +type CreateProcessRunner = forall a. CreateProcess -> ((Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> IO a) -> IO a + +data StdHandle = StdinHandle | StdoutHandle | StderrHandle + deriving (Eq) + +{- Like readProcess, but allows specifying the environment, and does + - not mess with stdin. -} +readProcessEnv :: FilePath -> [String] -> Maybe [(String, String)] -> IO String +readProcessEnv cmd args environ = + withHandle StdoutHandle createProcessSuccess p $ \h -> do + output <- hGetContentsStrict h + hClose h + return output + where + p = (proc cmd args) + { std_out = CreatePipe + , env = environ + } + {- Waits for a ProcessHandle, and throws an exception if the process - did not exit successfully. -} -forceSuccessProcess :: ProcessHandle -> String -> [String] -> IO () -forceSuccessProcess pid cmd args = do +forceSuccessProcess :: CreateProcess -> ProcessHandle -> IO () +forceSuccessProcess p pid = do code <- waitForProcess pid case code of ExitSuccess -> return () - ExitFailure n -> error $ - cmd ++ " " ++ show args ++ " exited " ++ show n + ExitFailure n -> error $ showCmd p ++ " exited " ++ show n -{- Like readProcess, but allows specifying the environment, and does - - not mess with stdin. -} -readProcessEnv :: FilePath -> [String] -> Maybe [(String, String)] -> IO String -readProcessEnv cmd args environ = do - (_, Just h, _, pid) - <- createProcess (proc cmd args) - { std_in = Inherit +{- Waits for a ProcessHandle and returns True if it exited successfully. -} +checkSuccessProcess :: ProcessHandle -> IO Bool +checkSuccessProcess pid = do + code <- waitForProcess pid + return $ code == ExitSuccess + +{- Runs createProcess, then an action on its handles, and then + - forceSuccessProcess. -} +createProcessSuccess :: CreateProcessRunner +createProcessSuccess p a = createProcessChecked (forceSuccessProcess p) p a + +{- Runs createProcess, then an action on its handles, and then + - an action on its exit code. -} +createProcessChecked :: (ProcessHandle -> IO b) -> CreateProcessRunner +createProcessChecked checker p a = do + t@(_, _, _, pid) <- createProcess p + r <- a t + _ <- checker pid + return r + +{- Leaves the process running, suitable for lazy streaming. + - Note: Zombies will result, and must be waited on. -} +createBackgroundProcess :: CreateProcessRunner +createBackgroundProcess p a = a =<< createProcess p + +{- Runs a CreateProcessRunner, on a CreateProcess structure, that + - is adjusted to pipe only from/to a single StdHandle, and passes + - the resulting Handle to an action. -} +withHandle + :: StdHandle + -> CreateProcessRunner + -> CreateProcess + -> (Handle -> IO a) + -> IO a +withHandle h creator p a = creator p' $ a . select + where + base = p + { std_in = Inherit + , std_out = Inherit + , std_err = Inherit + } + (select, p') + | h == StdinHandle = + (stdinHandle, base { std_in = CreatePipe }) + | h == StdoutHandle = + (stdoutHandle, base { std_out = CreatePipe }) + | h == StderrHandle = + (stderrHandle, base { std_err = CreatePipe }) + +{- Like withHandle, but passes (stdin, stdout) handles to the action. -} +withBothHandles + :: CreateProcessRunner + -> CreateProcess + -> ((Handle, Handle) -> IO a) + -> IO a +withBothHandles creator p a = creator p' $ a . bothHandles + where + p' = p + { std_in = CreatePipe , std_out = CreatePipe , std_err = Inherit - , env = environ } - output <- hGetContentsStrict h - hClose h - forceSuccessProcess pid cmd args - return output + +{- Extract a desired handle from createProcess's tuple. + - These partial functions are safe as long as createProcess is run + - with appropriate parameters to set up the desired handle. + - Get it wrong and the runtime crash will always happen, so should be + - easily noticed. -} +type HandleExtractor = (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> Handle +stdinHandle :: HandleExtractor +stdinHandle (Just h, _, _, _) = h +stdinHandle _ = error "expected stdinHandle" +stdoutHandle :: HandleExtractor +stdoutHandle (_, Just h, _, _) = h +stdoutHandle _ = error "expected stdoutHandle" +stderrHandle :: HandleExtractor +stderrHandle (_, _, Just h, _) = h +stderrHandle _ = error "expected stderrHandle" +bothHandles :: (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) -> (Handle, Handle) +bothHandles (Just hin, Just hout, _, _) = (hin, hout) +bothHandles _ = error "expected bothHandles" + +{- Debugging trace for a CreateProcess. -} +debugProcess :: CreateProcess -> IO () +debugProcess p = do + debugM "Utility.Process" $ unwords + [ action ++ ":" + , showCmd p + , maybe "" show (env p) + ] + where + action + | piped (std_in p) && piped (std_out p) = "chat" + | piped (std_in p) = "feed" + | piped (std_out p) = "read" + | otherwise = "call" + piped Inherit = False + piped _ = True + +{- Shows the command that a CreateProcess will run. -} +showCmd :: CreateProcess -> String +showCmd = go . cmdspec + where + go (ShellCommand s) = s + go (RawCommand c ps) = c ++ " " ++ show ps + +{- Wrappers for System.Process functions that do debug logging. + - + - More could be added, but these are the only ones I usually need. + -} + +createProcess :: CreateProcess -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle) +createProcess p = do + debugProcess p + System.Process.createProcess p + +runInteractiveProcess + :: FilePath + -> [String] + -> Maybe FilePath + -> Maybe [(String, String)] + -> IO (Handle, Handle, Handle, ProcessHandle) +runInteractiveProcess f args c e = do + debugProcess $ (proc f args) + { std_in = CreatePipe + , std_out = CreatePipe + , std_err = CreatePipe + } + System.Process.runInteractiveProcess f args c e + +readProcess + :: FilePath + -> [String] + -> String + -> IO String +readProcess f args input = do + debugProcess $ (proc f args) { std_out = CreatePipe } + System.Process.readProcess f args input diff --git a/Utility/SafeCommand.hs b/Utility/SafeCommand.hs index 47280a40b..19dd707b8 100644 --- a/Utility/SafeCommand.hs +++ b/Utility/SafeCommand.hs @@ -8,7 +8,8 @@ module Utility.SafeCommand where import System.Exit -import System.Process +import Utility.Process +import System.Process (env) import Data.String.Utils import Control.Applicative |