rawfilepath-0.1.1: src/System/Process/RawFilePath.hs
-----------------------------------------------------------------------------
-- |
-- Module : System.Process.RawFilePath
-- Copyright : (C) XT et al. 2017
-- License : BSD-style (see the file LICENSE)
--
-- Maintainer : e@xtendo.org
-- Stability : experimental
-- Portability : POSIX
--
-- Welcome to @System.Process.RawFilePath@, a small part of the Haskell
-- community's effort to purge 'String' for the Greater Good.
--
-- With this module, you can create (and interact with) sub-processes without
-- the encoding problem of 'String'. The command and its arguments, all
-- 'ByteString's, never get converted from/to 'String' internally on its way
-- to the actual syscall. It also avoids the time/space waste of 'String'.
--
-- The interface, unlike the original @process@ package, uses types to prevent
-- unnecessary runtime errors when obtaining 'Handle's. This is inspired by
-- the @typed-process@ package which is awesome, although this module is much
-- simpler; it doesn't introduce any new requirement of language extension or
-- library package (for the sake of portability).
--
-- 'Handle' (accessible with 'processStdin', 'processStdout', and
-- 'processStderr') is what you can use to interact with the sub-process. For
-- example, use 'Data.ByteString.hGetContents' from "Data.ByteString" to read
-- from a 'Handle' as a 'ByteString'.
--
-- == Example
--
-- @
-- {-\# language OverloadedStrings \#-}
--
-- import System.Process.RawFilePath
-- import qualified Data.ByteString as B
--
-- main :: IO ()
-- main = do
-- p <- 'startProcess' $ 'proc' "echo" ["hello"]
-- \`setStdout\` 'CreatePipe'
-- result <- B.hGetContents ('processStdout' p)
-- _ <- 'waitForProcess' p
--
-- print (result == "hello\\n")
-- @
--
-----------------------------------------------------------------------------
module System.Process.RawFilePath
(
-- ** Configuring process
-- $configuring
ProcessConf
, proc
-- *** Configuring process standard streams
, StreamType
, CreatePipe(..)
, Inherit(..)
, NoStream(..)
, UseHandle(..)
, setStdin
, setStdout
, setStderr
-- ** Running process
, Process
, startProcess
-- ** Obtaining process streams
-- $obtaining
, processStdin
, processStdout
, processStderr
-- ** Process completion
, stopProcess
, terminateProcess
, waitForProcess
) where
-- base modules
import RawFilePath.Import hiding (ClosedHandle)
-- local modules
import System.Process.RawFilePath.Common
import System.Process.RawFilePath.Internal
import System.Process.RawFilePath.Posix
-- | Start a new sub-process with the given configuration.
startProcess
:: (StreamType stdin, StreamType stdout, StreamType stderr)
=> ProcessConf stdin stdout stderr
-> IO (Process stdin stdout stderr)
startProcess = createProcessInternal
-- | Stop a sub-process. For now it simply calls 'terminateProcess' and then
-- 'waitForProcess'.
stopProcess :: Process stdin stdout stderr -> IO ExitCode
stopProcess p = do
terminateProcess p
waitForProcess p
-- | Wait (block) for a sub-process to exit and obtain its exit code.
waitForProcess
:: Process stdin stdout stderr
-> IO ExitCode
waitForProcess ph = lockWaitpid $ do
p_ <- modifyProcessHandle ph $ \ p_ -> return (p_,p_)
case p_ of
ClosedHandle e -> return e
OpenHandle h -> do
e <- alloca $ \ pret -> do
-- don't hold the MVar while we call c_waitForProcess...
throwErrnoIfMinus1Retry_ "waitForProcess" (c_waitForProcess h pret)
modifyProcessHandle ph $ \ p_' ->
case p_' of
ClosedHandle e -> return (p_', e)
OpenExtHandle{} -> return (p_', ExitFailure (-1))
OpenHandle ph' -> do
closePHANDLE ph'
code <- peek pret
let e = if code == 0
then ExitSuccess
else ExitFailure (fromIntegral code)
return (ClosedHandle e, e)
when delegatingCtlc $
endDelegateControlC e
return e
OpenExtHandle _ _job _iocp ->
return $ ExitFailure (-1)
where
-- If more than one thread calls `waitpid` at a time, `waitpid` will
-- return the exit code to one of them and (-1) to the rest of them,
-- causing an exception to be thrown.
-- Cf. https://github.com/haskell/process/issues/46, and
-- https://github.com/haskell/process/pull/58 for further discussion
lockWaitpid m = withMVar (waitpidLock ph) $ \ () -> m
delegatingCtlc = mbDelegateCtlc ph
-- | Terminate a sub-process by sending SIGTERM to it.
terminateProcess :: Process stdin stdout stderr -> IO ()
terminateProcess p = withProcessHandle p $ \ case
ClosedHandle _ -> return ()
OpenExtHandle{} -> error
"terminateProcess with OpenExtHandle should not happen on POSIX."
OpenHandle h -> do
throwErrnoIfMinus1Retry_ "terminateProcess" $ c_terminateProcess h
return ()
-- does not close the handle, we might want to try terminating it
-- again, or get its exit code.
-- $configuring
--
-- Configuration of how a new sub-process will be launched.
--
-- $obtaining
--
-- As the type signature suggests, these functions only work on processes
-- whose stream in configured to 'CreatePipe'. This is the type-safe way of
-- obtaining 'Handle's instead of returning 'Maybe' 'Handle's like the
-- @process@ package does.