{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE RecordWildCards #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE RankNTypes #-}
{-# LANGUAGE ScopedTypeVariables #-}
module System.Process.Typed
(
ProcessConfig
, StreamSpec
, StreamType (..)
, Process
, proc
, shell
, setStdin
, setStdout
, setStderr
, setWorkingDir
, setWorkingDirInherit
, setEnv
, setEnvInherit
, setCloseFds
, setCreateGroup
, setDelegateCtlc
#if MIN_VERSION_process(1, 3, 0)
, setDetachConsole
, setCreateNewConsole
, setNewSession
#endif
#if MIN_VERSION_process(1, 4, 0) && !WINDOWS
, setChildGroup
, setChildGroupInherit
, setChildUser
, setChildUserInherit
#endif
, mkStreamSpec
, inherit
, nullStream
, closed
, byteStringInput
, byteStringOutput
, createPipe
, useHandleOpen
, useHandleClose
, startProcess
, stopProcess
, withProcessWait
, withProcessWait_
, withProcessTerm
, withProcessTerm_
, withProcess
, withProcess_
, readProcess
, readProcess_
, runProcess
, runProcess_
, readProcessStdout
, readProcessStdout_
, readProcessStderr
, readProcessStderr_
, readProcessInterleaved
, readProcessInterleaved_
, waitExitCode
, waitExitCodeSTM
, getExitCode
, getExitCodeSTM
, checkExitCode
, checkExitCodeSTM
, getStdin
, getStdout
, getStderr
, ExitCodeException (..)
, ByteStringOutputException (..)
, unsafeProcessHandle
) where
import qualified Data.ByteString as S
import Data.ByteString.Lazy.Internal (defaultChunkSize)
import qualified Control.Exception as E
import Control.Exception hiding (bracket, finally)
import Control.Monad (void)
import Control.Monad.IO.Class
import qualified System.Process as P
import Data.Typeable (Typeable)
import System.IO (Handle, hClose, IOMode(ReadWriteMode), withBinaryFile)
import System.IO.Error (isPermissionError)
import Control.Concurrent (threadDelay)
import Control.Concurrent.Async (async, asyncWithUnmask, cancel, waitCatch)
import Control.Concurrent.STM (newEmptyTMVarIO, atomically, putTMVar, TMVar, readTMVar, tryReadTMVar, STM, tryPutTMVar, throwSTM, catchSTM)
import System.Exit (ExitCode (ExitSuccess))
import System.Process.Typed.Internal
import qualified Data.ByteString.Lazy as L
import qualified Data.ByteString.Lazy.Char8 as L8
import Data.String (IsString (fromString))
import GHC.RTS.Flags (getConcFlags, ctxtSwitchTime)
import Control.Monad.IO.Unlift
#if MIN_VERSION_process(1, 4, 0) && !WINDOWS
import System.Posix.Types (GroupID, UserID)
#endif
#if !MIN_VERSION_base(4, 8, 0)
import Control.Applicative (Applicative (..), (<$>), (<$))
#endif
#if !MIN_VERSION_process(1, 3, 0)
import qualified System.Process.Internals as P (createProcess_)
#endif
data ProcessConfig stdin stdout stderr = ProcessConfig
{ pcCmdSpec :: !P.CmdSpec
, pcStdin :: !(StreamSpec 'STInput stdin)
, pcStdout :: !(StreamSpec 'STOutput stdout)
, pcStderr :: !(StreamSpec 'STOutput stderr)
, pcWorkingDir :: !(Maybe FilePath)
, pcEnv :: !(Maybe [(String, String)])
, pcCloseFds :: !Bool
, pcCreateGroup :: !Bool
, pcDelegateCtlc :: !Bool
#if MIN_VERSION_process(1, 3, 0)
, pcDetachConsole :: !Bool
, pcCreateNewConsole :: !Bool
, pcNewSession :: !Bool
#endif
#if MIN_VERSION_process(1, 4, 0) && !WINDOWS
, pcChildGroup :: !(Maybe GroupID)
, pcChildUser :: !(Maybe UserID)
#endif
}
instance Show (ProcessConfig stdin stdout stderr) where
show pc = concat
[ case pcCmdSpec pc of
P.ShellCommand s -> "Shell command: " ++ s
P.RawCommand x xs -> "Raw command: " ++ unwords (map escape (x:xs))
, "\n"
, case pcWorkingDir pc of
Nothing -> ""
Just wd -> concat
[ "Run from: "
, wd
, "\n"
]
, case pcEnv pc of
Nothing -> ""
Just e -> unlines
$ "Modified environment:"
: map (\(k, v) -> concat [k, "=", v]) e
]
where
escape x
| any (`elem` " \\\"'") x = show x
| otherwise = x
instance (stdin ~ (), stdout ~ (), stderr ~ ())
=> IsString (ProcessConfig stdin stdout stderr) where
fromString s
| any (== ' ') s = shell s
| otherwise = proc s []
-- | Whether a stream is an input stream or output stream. Note that
-- this is from the perspective of the /child process/, so that a
-- child's standard input stream is an @STInput@, even though the
-- parent process will be writing to it.
--
-- @since 0.1.0.0
data StreamType = STInput | STOutput
-- | A specification for how to create one of the three standard child
-- streams. See examples below.
--
-- @since 0.1.0.0
data StreamSpec (streamType :: StreamType) a = StreamSpec
{ ssStream :: !(forall b. (P.StdStream -> IO b) -> IO b)
, ssCreate :: !(ProcessConfig () () () -> Maybe Handle -> Cleanup a)
}
deriving Functor
-- | This instance uses 'byteStringInput' to convert a raw string into
-- a stream of input for a child process.
--
-- @since 0.1.0.0
instance (streamType ~ 'STInput, res ~ ())
=> IsString (StreamSpec streamType res) where
fromString = byteStringInput . fromString
-- | Internal type, to make for easier composition of cleanup actions.
--
-- @since 0.1.0.0
newtype Cleanup a = Cleanup { runCleanup :: IO (a, IO ()) }
deriving Functor
instance Applicative Cleanup where
pure x = Cleanup (return (x, return ()))
Cleanup f <*> Cleanup x = Cleanup $ do
(f', c1) <- f
(`onException` c1) $ do
(x', c2) <- x
return (f' x', c1 `finally` c2)
-- | A running process. The three type parameters provide the type of
-- the standard input, standard output, and standard error streams.
--
-- @since 0.1.0.0
data Process stdin stdout stderr = Process
{ pConfig :: !(ProcessConfig () () ())
, pCleanup :: !(IO ())
, pStdin :: !stdin
, pStdout :: !stdout
, pStderr :: !stderr
, pHandle :: !P.ProcessHandle
, pExitCode :: !(TMVar ExitCode)
}
instance Show (Process stdin stdout stderr) where
show p = "Running process: " ++ show (pConfig p)
-- | Internal helper
defaultProcessConfig :: ProcessConfig () () ()
defaultProcessConfig = ProcessConfig
{ pcCmdSpec = P.ShellCommand ""
, pcStdin = inherit
, pcStdout = inherit
, pcStderr = inherit
, pcWorkingDir = Nothing
, pcEnv = Nothing
, pcCloseFds = False
, pcCreateGroup = False
, pcDelegateCtlc = False
#if MIN_VERSION_process(1, 3, 0)
, pcDetachConsole = False
, pcCreateNewConsole = False
, pcNewSession = False
#endif
#if MIN_VERSION_process(1, 4, 0) && !WINDOWS
, pcChildGroup = Nothing
, pcChildUser = Nothing
#endif
}
-- | Create a 'ProcessConfig' from the given command and arguments.
--
-- @since 0.1.0.0
proc :: FilePath -> [String] -> ProcessConfig () () ()
proc cmd args = setProc cmd args defaultProcessConfig
-- | Internal helper
setProc :: FilePath -> [String]
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setProc cmd args p = p { pcCmdSpec = P.RawCommand cmd args }
-- | Create a 'ProcessConfig' from the given shell command.
--
-- @since 0.1.0.0
shell :: String -> ProcessConfig () () ()
shell cmd = setShell cmd defaultProcessConfig
-- | Internal helper
setShell :: String
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setShell cmd p = p { pcCmdSpec = P.ShellCommand cmd }
-- | Set the child's standard input stream to the given 'StreamSpec'.
--
-- Default: 'inherit'
--
-- @since 0.1.0.0
setStdin :: StreamSpec 'STInput stdin
-> ProcessConfig stdin0 stdout stderr
-> ProcessConfig stdin stdout stderr
setStdin spec pc = pc { pcStdin = spec }
-- | Set the child's standard output stream to the given 'StreamSpec'.
--
-- Default: 'inherit'
--
-- @since 0.1.0.0
setStdout :: StreamSpec 'STOutput stdout
-> ProcessConfig stdin stdout0 stderr
-> ProcessConfig stdin stdout stderr
setStdout spec pc = pc { pcStdout = spec }
-- | Set the child's standard error stream to the given 'StreamSpec'.
--
-- Default: 'inherit'
--
-- @since 0.1.0.0
setStderr :: StreamSpec 'STOutput stderr
-> ProcessConfig stdin stdout stderr0
-> ProcessConfig stdin stdout stderr
setStderr spec pc = pc { pcStderr = spec }
-- | Set the working directory of the child process.
--
-- Default: current process's working directory.
--
-- @since 0.1.0.0
setWorkingDir :: FilePath
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setWorkingDir dir pc = pc { pcWorkingDir = Just dir }
-- | Inherit the working directory from the parent process.
--
-- @since 0.2.2.0
setWorkingDirInherit
:: ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setWorkingDirInherit pc = pc { pcWorkingDir = Nothing }
-- | Set the environment variables of the child process.
--
-- Default: current process's environment.
--
-- @since 0.1.0.0
setEnv :: [(String, String)]
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setEnv env pc = pc { pcEnv = Just env }
-- | Inherit the environment variables from the parent process.
--
-- @since 0.2.2.0
setEnvInherit
:: ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setEnvInherit pc = pc { pcEnv = Nothing }
-- | Should we close all file descriptors besides stdin, stdout, and
-- stderr? See 'P.close_fds' for more information.
--
-- Default: False
--
-- @since 0.1.0.0
setCloseFds
:: Bool
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setCloseFds x pc = pc { pcCloseFds = x }
-- | Should we create a new process group?
--
-- Default: False
--
-- @since 0.1.0.0
setCreateGroup
:: Bool
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setCreateGroup x pc = pc { pcCreateGroup = x }
-- | Delegate handling of Ctrl-C to the child. For more information,
-- see 'P.delegate_ctlc'.
--
-- Default: False
--
-- @since 0.1.0.0
setDelegateCtlc
:: Bool
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setDelegateCtlc x pc = pc { pcDelegateCtlc = x }
#if MIN_VERSION_process(1, 3, 0)
setDetachConsole
:: Bool
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setDetachConsole x pc = pc { pcDetachConsole = x }
setCreateNewConsole
:: Bool
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setCreateNewConsole x pc = pc { pcCreateNewConsole = x }
setNewSession
:: Bool
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setNewSession x pc = pc { pcNewSession = x }
#endif
#if MIN_VERSION_process(1, 4, 0) && !WINDOWS
setChildGroup
:: GroupID
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setChildGroup x pc = pc { pcChildGroup = Just x }
setChildGroupInherit
:: ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setChildGroupInherit pc = pc { pcChildGroup = Nothing }
setChildUser
:: UserID
-> ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setChildUser x pc = pc { pcChildUser = Just x }
setChildUserInherit
:: ProcessConfig stdin stdout stderr
-> ProcessConfig stdin stdout stderr
setChildUserInherit pc = pc { pcChildUser = Nothing }
#endif
mkStreamSpec :: P.StdStream
-> (ProcessConfig () () () -> Maybe Handle -> IO (a, IO ()))
-> StreamSpec streamType a
mkStreamSpec ss f = mkManagedStreamSpec ($ ss) f
mkManagedStreamSpec :: (forall b. (P.StdStream -> IO b) -> IO b)
-> (ProcessConfig () () () -> Maybe Handle -> IO (a, IO ()))
-> StreamSpec streamType a
mkManagedStreamSpec ss f = StreamSpec ss (\pc mh -> Cleanup (f pc mh))
inherit :: StreamSpec anyStreamType ()
inherit = mkStreamSpec P.Inherit (\_ Nothing -> pure ((), return ()))
nullStream :: StreamSpec anyStreamType ()
nullStream = mkManagedStreamSpec opener cleanup
where
opener f =
withBinaryFile nullDevice ReadWriteMode $ \handle ->
f (P.UseHandle handle)
cleanup _ _ =
pure ((), return ())
closed :: StreamSpec anyStreamType ()
#if MIN_VERSION_process(1, 4, 0)
closed = mkStreamSpec P.NoStream (\_ Nothing -> pure ((), return ()))
#else
closed = mkStreamSpec P.CreatePipe (\_ (Just h) -> ((), return ()) <$ hClose h)
#endif
byteStringInput :: L.ByteString -> StreamSpec 'STInput ()
byteStringInput lbs = mkStreamSpec P.CreatePipe $ \_ (Just h) -> do
void $ async $ do
L.hPut h lbs
hClose h
return ((), hClose h)
byteStringOutput :: StreamSpec 'STOutput (STM L.ByteString)
byteStringOutput = mkStreamSpec P.CreatePipe $ \pc (Just h) -> byteStringFromHandle pc h
byteStringFromHandle
:: ProcessConfig () () ()
-> Handle
-> IO (STM L.ByteString, IO ())
byteStringFromHandle pc h = do
mvar <- newEmptyTMVarIO
void $ async $ do
let loop front = do
bs <- S.hGetSome h defaultChunkSize
if S.null bs
then atomically $ putTMVar mvar $ Right $ L.fromChunks $ front []
else loop $ front . (bs:)
loop id `catch` \e -> do
atomically $ void $ tryPutTMVar mvar $ Left $ ByteStringOutputException e pc
throwIO e
return (readTMVar mvar >>= either throwSTM return, hClose h)
createPipe :: StreamSpec anyStreamType Handle
createPipe = mkStreamSpec P.CreatePipe $ \_ (Just h) -> return (h, hClose h)
useHandleOpen :: Handle -> StreamSpec anyStreamType ()
useHandleOpen h = mkStreamSpec (P.UseHandle h) $ \_ Nothing -> return ((), return ())
useHandleClose :: Handle -> StreamSpec anyStreamType ()
useHandleClose h = mkStreamSpec (P.UseHandle h) $ \_ Nothing -> return ((), hClose h)
startProcess :: MonadIO m
=> ProcessConfig stdin stdout stderr
-> m (Process stdin stdout stderr)
startProcess pConfig'@ProcessConfig {..} = liftIO $ do
ssStream pcStdin $ \realStdin ->
ssStream pcStdout $ \realStdout ->
ssStream pcStderr $ \realStderr -> do
let cp0 =
case pcCmdSpec of
P.ShellCommand cmd -> P.shell cmd
P.RawCommand cmd args -> P.proc cmd args
cp = cp0
{ P.std_in = realStdin
, P.std_out = realStdout
, P.std_err = realStderr
, P.cwd = pcWorkingDir
, P.env = pcEnv
, P.close_fds = pcCloseFds
, P.create_group = pcCreateGroup
, P.delegate_ctlc = pcDelegateCtlc
#if MIN_VERSION_process(1, 3, 0)
, P.detach_console = pcDetachConsole
, P.create_new_console = pcCreateNewConsole
, P.new_session = pcNewSession
#endif
#if MIN_VERSION_process(1, 4, 0) && !WINDOWS
, P.child_group = pcChildGroup
, P.child_user = pcChildUser
#endif
}
(minH, moutH, merrH, pHandle) <- P.createProcess_ "startProcess" cp
((pStdin, pStdout, pStderr), pCleanup1) <- runCleanup $ (,,)
<$> ssCreate pcStdin pConfig minH
<*> ssCreate pcStdout pConfig moutH
<*> ssCreate pcStderr pConfig merrH
pExitCode <- newEmptyTMVarIO
waitingThread <- asyncWithUnmask $ \unmask -> do
ec <- unmask $ -- make sure the masking state from a bracket isn't inherited
if multiThreadedRuntime
then P.waitForProcess pHandle
else do
switchTime <- fromIntegral . (`div` 1000) . ctxtSwitchTime
<$> getConcFlags
let minDelay = 1
maxDelay = max minDelay switchTime
loop delay = do
threadDelay delay
mec <- P.getProcessExitCode pHandle
case mec of
Nothing -> loop $ min maxDelay (delay * 2)
Just ec -> pure ec
loop minDelay
atomically $ putTMVar pExitCode ec
return ec
let pCleanup = pCleanup1 `finally` do
-- First: stop calling waitForProcess, so that we can
-- avoid race conditions where the process is removed from
-- the system process table while we're trying to
-- terminate it.
cancel waitingThread
-- Now check if the process had already exited
eec <- waitCatch waitingThread
case eec of
-- Process already exited, nothing to do
Right _ec -> return ()
-- Process didn't exit yet, let's terminate it and
-- then call waitForProcess ourselves
Left _ -> do
eres <- try $ P.terminateProcess pHandle
ec <-
case eres of
Left e
-- On Windows, with the single-threaded runtime, it
-- seems that if a process has already exited, the
-- call to terminateProcess will fail with a
-- permission denied error. To work around this, we
-- catch this exception and then immediately
-- waitForProcess. There's a chance that there may be
-- other reasons for this permission error to appear,
-- in which case this code may allow us to wait too
-- long for a child process instead of erroring out.
-- Recommendation: always use the multi-threaded
-- runtime!
| isPermissionError e && not multiThreadedRuntime && isWindows ->
P.waitForProcess pHandle
| otherwise -> throwIO e
Right () -> P.waitForProcess pHandle
success <- atomically $ tryPutTMVar pExitCode ec
evaluate $ assert success ()
return Process {..}
where
pConfig = clearStreams pConfig'
foreign import ccall unsafe "rtsSupportsBoundThreads"
multiThreadedRuntime :: Bool
isWindows :: Bool
#if WINDOWS
isWindows = True
#else
isWindows = False
#endif
stopProcess :: MonadIO m
=> Process stdin stdout stderr
-> m ()
stopProcess = liftIO . pCleanup
withProcessTerm :: (MonadUnliftIO m)
=> ProcessConfig stdin stdout stderr
-> (Process stdin stdout stderr -> m a)
-> m a
withProcessTerm config = bracket (startProcess config) stopProcess
withProcessWait :: (MonadUnliftIO m)
=> ProcessConfig stdin stdout stderr
-> (Process stdin stdout stderr -> m a)
-> m a
withProcessWait config f =
bracket
(startProcess config)
stopProcess
(\p -> f p <* waitExitCode p)
withProcess :: (MonadUnliftIO m)
=> ProcessConfig stdin stdout stderr
-> (Process stdin stdout stderr -> m a)
-> m a
withProcess = withProcessTerm
{-# DEPRECATED withProcess "Please consider using withProcessWait, or instead use withProcessTerm" #-}
withProcessTerm_ :: (MonadUnliftIO m)
=> ProcessConfig stdin stdout stderr
-> (Process stdin stdout stderr -> m a)
-> m a
withProcessTerm_ config = bracket
(startProcess config)
(\p -> stopProcess p `finally` checkExitCode p)
withProcessWait_ :: (MonadUnliftIO m)
=> ProcessConfig stdin stdout stderr
-> (Process stdin stdout stderr -> m a)
-> m a
withProcessWait_ config f = bracket
(startProcess config)
stopProcess
(\p -> f p <* checkExitCode p)
withProcess_ :: (MonadUnliftIO m)
=> ProcessConfig stdin stdout stderr
-> (Process stdin stdout stderr -> m a)
-> m a
withProcess_ = withProcessTerm_
{-# DEPRECATED withProcess_ "Please consider using withProcessWait_, or instead use withProcessTerm_" #-}
readProcess :: MonadIO m
=> ProcessConfig stdin stdoutIgnored stderrIgnored
-> m (ExitCode, L.ByteString, L.ByteString)
readProcess pc =
liftIO $ withProcess pc' $ \p -> atomically $ (,,)
<$> waitExitCodeSTM p
<*> getStdout p
<*> getStderr p
where
pc' = setStdout byteStringOutput
$ setStderr byteStringOutput pc
readProcess_ :: MonadIO m
=> ProcessConfig stdin stdoutIgnored stderrIgnored
-> m (L.ByteString, L.ByteString)
readProcess_ pc =
liftIO $ withProcess pc' $ \p -> atomically $ do
stdout <- getStdout p
stderr <- getStderr p
checkExitCodeSTM p `catchSTM` \ece -> throwSTM ece
{ eceStdout = stdout
, eceStderr = stderr
}
return (stdout, stderr)
where
pc' = setStdout byteStringOutput
$ setStderr byteStringOutput pc
readProcessStdout
:: MonadIO m
=> ProcessConfig stdin stdoutIgnored stderr
-> m (ExitCode, L.ByteString)
readProcessStdout pc =
liftIO $ withProcess pc' $ \p -> atomically $ (,)
<$> waitExitCodeSTM p
<*> getStdout p
where
pc' = setStdout byteStringOutput pc
readProcessStdout_
:: MonadIO m
=> ProcessConfig stdin stdoutIgnored stderr
-> m L.ByteString
readProcessStdout_ pc =
liftIO $ withProcess pc' $ \p -> atomically $ do
stdout <- getStdout p
checkExitCodeSTM p `catchSTM` \ece -> throwSTM ece
{ eceStdout = stdout
}
return stdout
where
pc' = setStdout byteStringOutput pc
readProcessStderr
:: MonadIO m
=> ProcessConfig stdin stdout stderrIgnored
-> m (ExitCode, L.ByteString)
readProcessStderr pc =
liftIO $ withProcess pc' $ \p -> atomically $ (,)
<$> waitExitCodeSTM p
<*> getStderr p
where
pc' = setStderr byteStringOutput pc
readProcessStderr_
:: MonadIO m
=> ProcessConfig stdin stdout stderrIgnored
-> m L.ByteString
readProcessStderr_ pc =
liftIO $ withProcess pc' $ \p -> atomically $ do
stderr <- getStderr p
checkExitCodeSTM p `catchSTM` \ece -> throwSTM ece
{ eceStderr = stderr
}
return stderr
where
pc' = setStderr byteStringOutput pc
withProcessInterleave :: (MonadUnliftIO m)
=> ProcessConfig stdin stdoutIgnored stderrIgnored
-> (Process stdin (STM L.ByteString) () -> m a)
-> m a
withProcessInterleave pc inner =
bracket P.createPipe (\(r, w) -> hClose r >> hClose w) $ \(readEnd, writeEnd) -> do
let pc' = setStdout (mkStreamSpec (P.UseHandle writeEnd) (\pc'' Nothing -> byteStringFromHandle pc'' readEnd))
$ setStderr (useHandleOpen writeEnd)
pc
withProcess pc' $ \p -> do
liftIO $ hClose writeEnd
inner p
readProcessInterleaved
:: MonadIO m
=> ProcessConfig stdin stdoutIgnored stderrIgnored
-> m (ExitCode, L.ByteString)
readProcessInterleaved pc =
liftIO $
withProcessInterleave pc $ \p ->
atomically $ (,)
<$> waitExitCodeSTM p
<*> getStdout p
readProcessInterleaved_
:: MonadIO m
=> ProcessConfig stdin stdoutIgnored stderrIgnored
-> m L.ByteString
readProcessInterleaved_ pc =
liftIO $
withProcessInterleave pc $ \p -> atomically $ do
stdout' <- getStdout p
checkExitCodeSTM p `catchSTM` \ece -> throwSTM ece
{ eceStdout = stdout'
}
return stdout'
runProcess :: MonadIO m
=> ProcessConfig stdin stdout stderr
-> m ExitCode
runProcess pc = liftIO $ withProcess pc waitExitCode
runProcess_ :: MonadIO m
=> ProcessConfig stdin stdout stderr
-> m ()
runProcess_ pc = liftIO $ withProcess pc checkExitCode
waitExitCode :: MonadIO m => Process stdin stdout stderr -> m ExitCode
waitExitCode = liftIO . atomically . waitExitCodeSTM
waitExitCodeSTM :: Process stdin stdout stderr -> STM ExitCode
waitExitCodeSTM = readTMVar . pExitCode
getExitCode :: MonadIO m => Process stdin stdout stderr -> m (Maybe ExitCode)
getExitCode = liftIO . atomically . getExitCodeSTM
getExitCodeSTM :: Process stdin stdout stderr -> STM (Maybe ExitCode)
getExitCodeSTM = tryReadTMVar . pExitCode
checkExitCode :: MonadIO m => Process stdin stdout stderr -> m ()
checkExitCode = liftIO . atomically . checkExitCodeSTM
checkExitCodeSTM :: Process stdin stdout stderr -> STM ()
checkExitCodeSTM p = do
ec <- readTMVar (pExitCode p)
case ec of
ExitSuccess -> return ()
_ -> throwSTM ExitCodeException
{ eceExitCode = ec
, eceProcessConfig = clearStreams (pConfig p)
, eceStdout = L.empty
, eceStderr = L.empty
}
clearStreams :: ProcessConfig stdin stdout stderr -> ProcessConfig () () ()
clearStreams pc = pc
{ pcStdin = inherit
, pcStdout = inherit
, pcStderr = inherit
}
getStdin :: Process stdin stdout stderr -> stdin
getStdin = pStdin
getStdout :: Process stdin stdout stderr -> stdout
getStdout = pStdout
getStderr :: Process stdin stdout stderr -> stderr
getStderr = pStderr
data ExitCodeException = ExitCodeException
{ eceExitCode :: ExitCode
, eceProcessConfig :: ProcessConfig () () ()
, eceStdout :: L.ByteString
, eceStderr :: L.ByteString
}
deriving Typeable
instance Exception ExitCodeException
instance Show ExitCodeException where
show ece = concat
[ "Received "
, show (eceExitCode ece)
, " when running\n"
, show (eceProcessConfig ece) { pcEnv = Nothing }
, if L.null (eceStdout ece)
then ""
else "Standard output:\n\n" ++ L8.unpack (eceStdout ece)
, if L.null (eceStderr ece)
then ""
else "Standard error:\n\n" ++ L8.unpack (eceStderr ece)
]
data ByteStringOutputException = ByteStringOutputException SomeException (ProcessConfig () () ())
deriving (Show, Typeable)
instance Exception ByteStringOutputException
unsafeProcessHandle :: Process stdin stdout stderr -> P.ProcessHandle
unsafeProcessHandle = pHandle
bracket :: MonadUnliftIO m => IO a -> (a -> IO b) -> (a -> m c) -> m c
bracket before after thing = withRunInIO $ \run -> E.bracket before after (run . thing)
finally :: MonadUnliftIO m => m a -> IO () -> m a
finally thing after = withRunInIO $ \run -> E.finally (run thing) after