rawfilepath-0.2.0: src/RawFilePath/Process/Common.hs
module RawFilePath.Process.Common
( Process(..)
, ProcessConf(..)
, proc
, processStdin
, processStdout
, processStderr
, StreamType
, mbFd
, willCreateHandle
, CreatePipe(..)
, Inherit(..)
, NoStream(..)
, UseHandle(..)
, setStdin
, setStdout
, setStderr
, PHANDLE
, ProcessHandle__(..)
, modifyProcessHandle
, withProcessHandle
, fdStdin
, fdStdout
, fdStderr
, mbPipe
) where
import RawFilePath.Import
-- extra modules
import System.Posix.Internals (FD)
import qualified GHC.IO.FD as FD
-- Original declarations
-- | The process configuration that is needed for creating new processes. Use
-- 'proc' to make one.
data ProcessConf stdin stdout stderr = ProcessConf
{ cmdargs :: [ByteString]
-- ^ Executable & arguments, or shell command
, cwd :: Maybe RawFilePath
-- ^ Optional path to the working directory for the new process
, env :: Maybe [(ByteString, ByteString)]
-- ^ Optional environment (otherwise inherit from the current process)
, cfgStdin :: stdin
-- ^ How to determine stdin
, cfgStdout :: stdout
-- ^ How to determine stdout
, cfgStderr :: stderr
-- ^ How to determine stderr
, closeFds :: Bool
-- ^ Close all file descriptors except stdin, stdout and stderr in the new
-- process
, createGroup :: Bool
-- ^ Create a new process group
, delegateCtlc :: Bool
-- ^ Delegate control-C handling. Use this for interactive console
-- processes to let them handle control-C themselves (see below for
-- details).
, createNewConsole :: Bool
-- ^ Use the windows CREATE_NEW_CONSOLE flag when creating the process;
-- does nothing on other platforms.
--
-- Default: @False@
, newSession :: Bool
-- ^ Use posix setsid to start the new process in a new session; does nothing on other platforms.
, childGroup :: Maybe GroupID
-- ^ Use posix setgid to set child process's group id.
--
-- Default: @Nothing@
, childUser :: Maybe UserID
-- ^ Use posix setuid to set child process's user id.
--
-- Default: @Nothing@
}
-- | Create a process configuration with the default settings.
proc
:: RawFilePath -- ^ Command to run
-> [ByteString] -- ^ Arguments to the command
-> ProcessConf Inherit Inherit Inherit
proc cmd args = ProcessConf
{ cmdargs = cmd : args
, cwd = Nothing
, env = Nothing
, cfgStdin = Inherit
, cfgStdout = Inherit
, cfgStderr = Inherit
, closeFds = False
, createGroup = False
, delegateCtlc = False
, createNewConsole = False
, newSession = False
, childGroup = Nothing
, childUser = Nothing
}
-- | Control how the standard input of the process will be initialized.
setStdin
:: (StreamType newStdin)
=> ProcessConf oldStdin stdout stderr
-> newStdin
-> ProcessConf newStdin stdout stderr
setStdin p newStdin = p { cfgStdin = newStdin }
infixl 4 `setStdin`
-- | Control how the standard output of the process will be initialized.
setStdout
:: (StreamType newStdout)
=> ProcessConf stdin oldStdout stderr
-> newStdout
-> ProcessConf stdin newStdout stderr
setStdout p newStdout = p { cfgStdout = newStdout }
infixl 4 `setStdout`
-- | Control how the standard error of the process will be initialized.
setStderr
:: (StreamType newStderr)
=> ProcessConf stdin stdout oldStderr
-> newStderr
-> ProcessConf stdin stdout newStderr
setStderr p newStderr = p { cfgStderr = newStderr }
infixl 4 `setStderr`
-- | The process type. The three type variables denote how its standard
-- streams were initialized.
data Process stdin stdout stderr = Process
{ procStdin :: Maybe Handle
, procStdout :: Maybe Handle
, procStderr :: Maybe Handle
, phandle :: !(MVar ProcessHandle__)
, mbDelegateCtlc :: !Bool
, waitpidLock :: !(MVar ())
}
-- | Take a process and return its standard input handle.
processStdin :: Process CreatePipe stdout stderr -> Handle
processStdin Process{..} = fromMaybe err procStdin
where
err = error "This can't happen: stdin is CreatePipe but missing"
-- | Take a process and return its standard output handle.
processStdout :: Process stdin CreatePipe stderr -> Handle
processStdout Process{..} = fromMaybe err procStdout
where
err = error "This can't happen: stdout is CreatePipe but missing"
-- | Take a process and return its standard error handle.
processStderr :: Process stdin stdout CreatePipe -> Handle
processStderr Process{..} = fromMaybe err procStderr
where
err = error "This can't happen: stderr is CreatePipe but missing"
-- | Create a new pipe for the stream. You get a new 'Handle'.
data CreatePipe = CreatePipe deriving Show
-- | Inherit the parent (current) process handle. The child will share the
-- stream. For example, if the child writes anything to stdout, it will all go
-- to the parent's stdout.
data Inherit = Inherit deriving Show
-- | No stream handle will be passed. Use when you don't want to communicate
-- with a stream. For example, to run something silently.
data NoStream = NoStream deriving Show
-- | Use the supplied 'Handle'.
data UseHandle = UseHandle Handle deriving Show
-- | The class of types that determine the standard stream of a sub-process.
-- You can decide how to initialize the standard streams (stdin, stdout, and
-- stderr) of a sub-process with the instances of this class.
class StreamType c where
mbFd :: FD -> c -> IO FD
willCreateHandle :: c -> Bool
#if __GLASGOW_HASKELL__ >= 780
mbFd = undefined
willCreateHandle = undefined
{-# MINIMAL #-}
#endif
instance StreamType CreatePipe where
mbFd _ _ = return (-1)
willCreateHandle _ = True
instance StreamType Inherit where
mbFd std _ = return std
willCreateHandle _ = False
instance StreamType NoStream where
mbFd _ _ = return (-2)
willCreateHandle _ = False
instance StreamType UseHandle where
mbFd _std (UseHandle hdl) =
withHandle "" hdl $ \Handle__{haDevice=dev,..} -> case cast dev of
Just fd -> do
-- clear the O_NONBLOCK flag on this FD, if it is set, since
-- we're exposing it externally (see #3316 of 'process')
fd' <- FD.setNonBlockingMode fd False
return (Handle__{haDevice=fd',..}, FD.fdFD fd')
Nothing -> ioError $ mkIOError illegalOperationErrorType
"createProcess" (Just hdl) Nothing
`ioeSetErrorString` "handle is not a file descriptor"
willCreateHandle _ = False
-- Declarations from the process package (modified)
type PHANDLE = CPid
data ProcessHandle__ = OpenHandle PHANDLE
| OpenExtHandle PHANDLE PHANDLE PHANDLE
| ClosedHandle ExitCode
modifyProcessHandle
:: Process stdin stdout stderr
-> (ProcessHandle__ -> IO (ProcessHandle__, a))
-> IO a
modifyProcessHandle p = modifyMVar (phandle p)
withProcessHandle
:: Process stdin stdout stderr -> (ProcessHandle__ -> IO a) -> IO a
withProcessHandle p = withMVar (phandle p)
fdStdin, fdStdout, fdStderr :: FD
fdStdin = 0
fdStdout = 1
fdStderr = 2
mbPipe :: StreamType c => c -> Ptr FD -> IOMode -> IO (Maybe Handle)
mbPipe streamConf pfd mode = if willCreateHandle streamConf
then fmap Just (pfdToHandle pfd mode)
else return Nothing
pfdToHandle :: Ptr FD -> IOMode -> IO Handle
pfdToHandle pfd mode = do
fd <- peek pfd
let filepath = "fd:" ++ show fd
(fD,fd_type) <- FD.mkFD (fromIntegral fd) mode
(Just (Stream,0,0)) -- avoid calling fstat()
False {-is_socket-}
False {-non-blocking-}
fD' <- FD.setNonBlockingMode fD True -- see #3316
#if __GLASGOW_HASKELL__ >= 704
enc <- getLocaleEncoding
#else
let enc = localeEncoding
#endif
mkHandleFromFD fD' fd_type filepath mode False {-is_socket-} (Just enc)