process 1.2.0.0 → 1.2.1.0
raw patch · 8 files changed
+1043/−972 lines, 8 filesdep ~basedep ~deepseqsetup-changed
Dependency ranges changed: base, deepseq
Files
- Setup.hs +1/−1
- System/Cmd.hs +3/−1
- System/Process.hs +0/−910
- System/Process.hsc +920/−0
- System/Process/Internals.hs +74/−33
- changelog +0/−15
- changelog.md +36/−0
- process.cabal +9/−12
Setup.hs view
@@ -3,4 +3,4 @@ import Distribution.Simple main :: IO ()-main = defaultMainWithHooks defaultUserHooks+main = defaultMainWithHooks autoconfUserHooks
System/Cmd.hs view
@@ -1,5 +1,7 @@ {-# LANGUAGE CPP #-}-#ifdef __GLASGOW_HASKELL__+#if __GLASGOW_HASKELL__ >= 709+{-# LANGUAGE Safe #-}+#elif __GLASGOW_HASKELL__ >= 703 {-# LANGUAGE Trustworthy #-} #endif
− System/Process.hs
@@ -1,910 +0,0 @@-{-# LANGUAGE CPP #-}-#ifdef __GLASGOW_HASKELL__-{-# LANGUAGE Trustworthy #-}-{-# LANGUAGE InterruptibleFFI #-}-#endif---------------------------------------------------------------------------------- |--- Module : System.Process--- Copyright : (c) The University of Glasgow 2004-2008--- License : BSD-style (see the file libraries/base/LICENSE)------ Maintainer : libraries@haskell.org--- Stability : experimental--- Portability : non-portable (requires concurrency)------ Operations for creating and interacting with sub-processes.------------------------------------------------------------------------------------- ToDo:--- * Flag to control whether exiting the parent also kills the child.--{- NOTES on createPipe:-- createPipe is no longer exported, because of the following problems:-- - it wasn't used to implement runInteractiveProcess on Unix, because- the file descriptors for the unused ends of the pipe need to be closed- in the child process.-- - on Windows, a special version of createPipe is needed that sets- the inheritance flags correctly on the ends of the pipe (see- mkAnonPipe below).--}--module System.Process (-#ifndef __HUGS__- -- * Running sub-processes- createProcess,- shell, proc,- CreateProcess(..),- CmdSpec(..),- StdStream(..),- ProcessHandle,-- -- ** Simpler functions for common tasks- callProcess,- callCommand,- spawnProcess,- spawnCommand,- readProcess,- readProcessWithExitCode,-- -- ** Related utilities- showCommandForUser,-- -- ** Control-C handling on Unix- -- $ctlc-handling-- -- * Process completion- waitForProcess,- getProcessExitCode,- terminateProcess,- interruptProcessGroupOf,-- -- * Old deprecated functions- -- | These functions pre-date 'createProcess' which is much more- -- flexible.- runProcess,- runCommand,- runInteractiveProcess,- runInteractiveCommand,-#endif- system,- rawSystem,- ) where--import Prelude hiding (mapM)--#ifndef __HUGS__-import System.Process.Internals--import Control.Exception (SomeException, mask, try, throwIO)-import Control.DeepSeq (rnf)-import System.IO.Error (mkIOError, ioeSetErrorString)-#if !defined(mingw32_HOST_OS)-import System.Posix.Types-import System.Posix.Process (getProcessGroupIDOf)-#endif-import qualified Control.Exception as C-import Control.Concurrent-import Control.Monad-import Foreign-import Foreign.C-import System.IO-import Data.Maybe-#endif-import System.Exit ( ExitCode(..) )--#ifdef __GLASGOW_HASKELL__-import GHC.IO.Exception ( ioException, IOErrorType(..), IOException(..) )-#if defined(mingw32_HOST_OS)-import System.Win32.Process (getProcessId)-import System.Win32.Console (generateConsoleCtrlEvent, cTRL_BREAK_EVENT)-#else-import System.Posix.Signals-#endif-#endif--#ifdef __HUGS__-import Hugs.System-#endif---#ifndef __HUGS__---- ------------------------------------------------------------------------------- createProcess---- | Construct a 'CreateProcess' record for passing to 'createProcess',--- representing a raw command with arguments.------ The 'FilePath' argument names the executable, and is interpreted according--- to the platform's standard policy for searching for--- executables. Specifically:------ * on Unix systems the--- <http://pubs.opengroup.org/onlinepubs/9699919799/functions/execvp.html execvp(3)>--- semantics is used, where if the executable filename does not--- contain a slash (@/@) then the @PATH@ environment variable is--- searched for the executable.------ * on Windows systems the Win32 @CreateProcess@ semantics is used.--- Briefly: if the filename does not contain a path, then the--- directory containing the parent executable is searched, followed--- by the current directory, then some standard locations, and--- finally the current @PATH@. An @.exe@ extension is added if the--- filename does not already have an extension. For full details--- see the--- <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365527%28v=vs.85%29.aspx documentation>--- for the Windows @SearchPath@ API.--proc :: FilePath -> [String] -> CreateProcess-proc cmd args = CreateProcess { cmdspec = RawCommand cmd args,- cwd = Nothing,- env = Nothing,- std_in = Inherit,- std_out = Inherit,- std_err = Inherit,- close_fds = False,- create_group = False,- delegate_ctlc = False}---- | Construct a 'CreateProcess' record for passing to 'createProcess',--- representing a command to be passed to the shell.-shell :: String -> CreateProcess-shell str = CreateProcess { cmdspec = ShellCommand str,- cwd = Nothing,- env = Nothing,- std_in = Inherit,- std_out = Inherit,- std_err = Inherit,- close_fds = False,- create_group = False,- delegate_ctlc = False}--{- |-This is the most general way to spawn an external process. The-process can be a command line to be executed by a shell or a raw command-with a list of arguments. The stdin, stdout, and stderr streams of-the new process may individually be attached to new pipes, to existing-'Handle's, or just inherited from the parent (the default.)--The details of how to create the process are passed in the-'CreateProcess' record. To make it easier to construct a-'CreateProcess', the functions 'proc' and 'shell' are supplied that-fill in the fields with default values which can be overriden as-needed.--'createProcess' returns @(/mb_stdin_hdl/, /mb_stdout_hdl/, /mb_stderr_hdl/, /ph/)@,-where-- * if @'std_in' == 'CreatePipe'@, then @/mb_stdin_hdl/@ will be @Just /h/@,- where @/h/@ is the write end of the pipe connected to the child- process's @stdin@.-- * otherwise, @/mb_stdin_hdl/ == Nothing@--Similarly for @/mb_stdout_hdl/@ and @/mb_stderr_hdl/@.--For example, to execute a simple @ls@ command:--> r <- createProcess (proc "ls" [])--To create a pipe from which to read the output of @ls@:--> (_, Just hout, _, _) <--> createProcess (proc "ls" []){ std_out = CreatePipe }--To also set the directory in which to run @ls@:--> (_, Just hout, _, _) <--> createProcess (proc "ls" []){ cwd = Just "\home\bob",-> std_out = CreatePipe }---}-createProcess- :: CreateProcess- -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)-createProcess cp = do- r <- createProcess_ "createProcess" cp- maybeCloseStd (std_in cp)- maybeCloseStd (std_out cp)- maybeCloseStd (std_err cp)- return r- where- maybeCloseStd :: StdStream -> IO ()- maybeCloseStd (UseHandle hdl)- | hdl /= stdin && hdl /= stdout && hdl /= stderr = hClose hdl- maybeCloseStd _ = return ()--{---- TODO: decide if we want to expose this to users--- | A 'C.bracketOnError'-style resource handler for 'createProcess'.------ In normal operation it adds nothing, you are still responsible for waiting--- for (or forcing) process termination and closing any 'Handle's. It only does--- automatic cleanup if there is an exception. If there is an exception in the--- body then it ensures that the process gets terminated and any 'CreatePipe'--- 'Handle's are closed. In particular this means that if the Haskell thread--- is killed (e.g. 'killThread'), that the external process is also terminated.------ e.g.------ > withCreateProcess (proc cmd args) { ... } $ \_ _ _ ph -> do--- > ...----withCreateProcess- :: CreateProcess- -> (Maybe Handle -> Maybe Handle -> Maybe Handle -> ProcessHandle -> IO a)- -> IO a-withCreateProcess c action =- C.bracketOnError (createProcess c) cleanupProcess- (\(m_in, m_out, m_err, ph) -> action m_in m_out m_err ph)--}---- wrapper so we can get exceptions with the appropriate function name.-withCreateProcess_- :: String- -> CreateProcess- -> (Maybe Handle -> Maybe Handle -> Maybe Handle -> ProcessHandle -> IO a)- -> IO a-withCreateProcess_ fun c action =- C.bracketOnError (createProcess_ fun c) cleanupProcess- (\(m_in, m_out, m_err, ph) -> action m_in m_out m_err ph)---cleanupProcess :: (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)- -> IO ()-cleanupProcess (mb_stdin, mb_stdout, mb_stderr, ph) = do- terminateProcess ph- -- Note, it's important that other threads that might be reading/writing- -- these handles also get killed off, since otherwise they might be holding- -- the handle lock and prevent us from closing, leading to deadlock.- maybe (return ()) (ignoreSigPipe . hClose) mb_stdin- maybe (return ()) hClose mb_stdout- maybe (return ()) hClose mb_stderr- -- terminateProcess does not guarantee that it terminates the process.- -- Indeed on Unix it's SIGTERM, which asks nicely but does not guarantee- -- that it stops. If it doesn't stop, we don't want to hang, so we wait- -- asynchronously using forkIO.- _ <- forkIO (waitForProcess ph >> return ())- return ()----- ------------------------------------------------------------------------------- spawnProcess/spawnCommand---- | Creates a new process to run the specified raw command with the given--- arguments. It does not wait for the program to finish, but returns the--- 'ProcessHandle'.------ /Since: 1.2.0.0/-spawnProcess :: FilePath -> [String] -> IO ProcessHandle-spawnProcess cmd args = do- (_,_,_,p) <- createProcess_ "spawnProcess" (proc cmd args)- return p---- | Creates a new process to run the specified shell command.--- It does not wait for the program to finish, but returns the 'ProcessHandle'.------ /Since: 1.2.0.0/-spawnCommand :: String -> IO ProcessHandle-spawnCommand cmd = do- (_,_,_,p) <- createProcess_ "spawnCommand" (shell cmd)- return p----- ------------------------------------------------------------------------------- callProcess/callCommand---- | Creates a new process to run the specified command with the given--- arguments, and wait for it to finish. If the command returns a non-zero--- exit code, an exception is raised.------ If an asynchronous exception is thrown to the thread executing--- @callProcess@. The forked process will be terminated and--- @callProcess@ will wait (block) until the process has been--- terminated.------ /Since: 1.2.0.0/-callProcess :: FilePath -> [String] -> IO ()-callProcess cmd args = do- exit_code <- withCreateProcess_ "callCommand"- (proc cmd args) { delegate_ctlc = True } $ \_ _ _ p ->- waitForProcess p- case exit_code of- ExitSuccess -> return ()- ExitFailure r -> processFailedException "callProcess" cmd args r---- | Creates a new process to run the specified shell command. If the--- command returns a non-zero exit code, an exception is raised.------ If an asynchronous exception is thrown to the thread executing--- @callCommand@. The forked process will be terminated and--- @callCommand@ will wait (block) until the process has been--- terminated.------ /Since: 1.2.0.0/-callCommand :: String -> IO ()-callCommand cmd = do- exit_code <- withCreateProcess_ "callCommand"- (shell cmd) { delegate_ctlc = True } $ \_ _ _ p ->- waitForProcess p- case exit_code of- ExitSuccess -> return ()- ExitFailure r -> processFailedException "callCommand" cmd [] r--processFailedException :: String -> String -> [String] -> Int -> IO a-processFailedException fun cmd args exit_code =- ioError (mkIOError OtherError (fun ++ ": " ++ cmd ++- concatMap ((' ':) . show) args ++- " (exit " ++ show exit_code ++ ")")- Nothing Nothing)----- ------------------------------------------------------------------------------- Control-C handling on Unix---- $ctlc-handling------ When running an interactive console process (such as a shell, console-based--- text editor or ghci), we typically want that process to be allowed to handle--- Ctl-C keyboard interrupts how it sees fit. For example, while most programs--- simply quit on a Ctl-C, some handle it specially. To allow this to happen,--- use the @'delegate_ctlc' = True@ option in the 'CreateProcess' options.------ The gory details:------ By default Ctl-C will generate a @SIGINT@ signal, causing a 'UserInterrupt'--- exception to be sent to the main Haskell thread of your program, which if--- not specially handled will terminate the program. Normally, this is exactly--- what is wanted: an orderly shutdown of the program in response to Ctl-C.------ Of course when running another interactive program in the console then we--- want to let that program handle Ctl-C. Under Unix however, Ctl-C sends--- @SIGINT@ to every process using the console. The standard solution is that--- while running an interactive program, ignore @SIGINT@ in the parent, and let--- it be handled in the child process. If that process then terminates due to--- the @SIGINT@ signal, then at that point treat it as if we had recieved the--- @SIGINT@ ourselves and begin an orderly shutdown.------ This behaviour is implemented by 'createProcess' (and--- 'waitForProcess' \/ 'getProcessExitCode') when the @'delegate_ctlc' = True@--- option is set. In particular, the @SIGINT@ signal will be ignored until--- 'waitForProcess' returns (or 'getProcessExitCode' returns a non-Nothing--- result), so it becomes especially important to use 'waitForProcess' for every--- processes created.------ In addition, in 'delegate_ctlc' mode, 'waitForProcess' and--- 'getProcessExitCode' will throw a 'UserInterrupt' exception if the process--- terminated with @'ExitFailure' (-SIGINT)@. Typically you will not want to--- catch this exception, but let it propagate, giving a normal orderly shutdown.--- One detail to be aware of is that the 'UserInterrupt' exception is thrown--- /synchronously/ in the thread that calls 'waitForProcess', whereas normally--- @SIGINT@ causes the exception to be thrown /asynchronously/ to the main--- thread.------ For even more detail on this topic, see--- <http://www.cons.org/cracauer/sigint.html "Proper handling of SIGINT/SIGQUIT">.---- --------------------------------------------------------------------------------- | @readProcess@ forks an external process, reads its standard output--- strictly, blocking until the process terminates, and returns the output--- string.------ If an asynchronous exception is thrown to the thread executing--- @readProcess@. The forked process will be terminated and @readProcess@ will--- wait (block) until the process has been terminated.------ Output is returned strictly, so this is not suitable for--- interactive applications.------ This function throws an 'IOError' if the process 'ExitCode' is--- anything other than 'ExitSuccess'.------ Users of this function should compile with @-threaded@ if they--- want other Haskell threads to keep running while waiting on--- the result of readProcess.------ > > readProcess "date" [] []--- > "Thu Feb 7 10:03:39 PST 2008\n"------ The arguments are:------ * The command to run, which must be in the $PATH, or an absolute path------ * A list of separate command line arguments to the program------ * A string to pass on the standard input to the program.----readProcess- :: FilePath -- ^ Filename of the executable (see 'proc' for details)- -> [String] -- ^ any arguments- -> String -- ^ standard input- -> IO String -- ^ stdout-readProcess cmd args input = do- let cp_opts = (proc cmd args) {- std_in = CreatePipe,- std_out = CreatePipe,- std_err = Inherit- }- (ex, output) <- withCreateProcess_ "readProcess" cp_opts $- \(Just inh) (Just outh) _ ph -> do-- -- fork off a thread to start consuming the output- output <- hGetContents outh- withForkWait (C.evaluate $ rnf output) $ \waitOut -> do-- -- now write any input- unless (null input) $- ignoreSigPipe $ hPutStr inh input- -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE- ignoreSigPipe $ hClose inh-- -- wait on the output- waitOut- hClose outh-- -- wait on the process- ex <- waitForProcess ph- return (ex, output)-- case ex of- ExitSuccess -> return output- ExitFailure r -> processFailedException "readProcess" cmd args r--{- |-@readProcessWithExitCode@ creates an external process, reads its-standard output and standard error strictly, waits until the process-terminates, and then returns the 'ExitCode' of the process,-the standard output, and the standard error.--If an asynchronous exception is thrown to the thread executing-@readProcessWithExitCode@. The forked process will be terminated and-@readProcessWithExitCode@ will wait (block) until the process has been-terminated.--'readProcess' and 'readProcessWithExitCode' are fairly simple wrappers-around 'createProcess'. Constructing variants of these functions is-quite easy: follow the link to the source code to see how-'readProcess' is implemented.--On Unix systems, see 'waitForProcess' for the meaning of exit codes-when the process died as the result of a signal.--}--readProcessWithExitCode- :: FilePath -- ^ Filename of the executable (see 'proc' for details)- -> [String] -- ^ any arguments- -> String -- ^ standard input- -> IO (ExitCode,String,String) -- ^ exitcode, stdout, stderr-readProcessWithExitCode cmd args input = do- let cp_opts = (proc cmd args) {- std_in = CreatePipe,- std_out = CreatePipe,- std_err = CreatePipe- }- withCreateProcess_ "readProcessWithExitCode" cp_opts $- \(Just inh) (Just outh) (Just errh) ph -> do-- out <- hGetContents outh- err <- hGetContents errh-- -- fork off threads to start consuming stdout & stderr- withForkWait (C.evaluate $ rnf out) $ \waitOut ->- withForkWait (C.evaluate $ rnf err) $ \waitErr -> do-- -- now write any input- unless (null input) $- ignoreSigPipe $ hPutStr inh input- -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE- ignoreSigPipe $ hClose inh-- -- wait on the output- waitOut- waitErr-- hClose outh- hClose errh-- -- wait on the process- ex <- waitForProcess ph-- return (ex, out, err)---- | Fork a thread while doing something else, but kill it if there's an--- exception.------ This is important in the cases above because we want to kill the thread--- that is holding the Handle lock, because when we clean up the process we--- try to close that handle, which could otherwise deadlock.----withForkWait :: IO () -> (IO () -> IO a) -> IO a-withForkWait async body = do- waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))- mask $ \restore -> do- tid <- forkIO $ try (restore async) >>= putMVar waitVar- let wait = takeMVar waitVar >>= either throwIO return- restore (body wait) `C.onException` killThread tid--ignoreSigPipe :: IO () -> IO ()-#if defined(__GLASGOW_HASKELL__)-ignoreSigPipe = C.handle $ \e -> case e of- IOError { ioe_type = ResourceVanished- , ioe_errno = Just ioe }- | Errno ioe == ePIPE -> return ()- _ -> throwIO e-#else-ignoreSigPipe = id-#endif---- ------------------------------------------------------------------------------- showCommandForUser---- | Given a program @/p/@ and arguments @/args/@,--- @showCommandForUser /p/ /args/@ returns a string suitable for pasting--- into @\/bin\/sh@ (on Unix systems) or @CMD.EXE@ (on Windows).-showCommandForUser :: FilePath -> [String] -> String-showCommandForUser cmd args = unwords (map translate (cmd : args))----- ------------------------------------------------------------------------------- waitForProcess--{- | Waits for the specified process to terminate, and returns its exit code.--GHC Note: in order to call @waitForProcess@ without blocking all the-other threads in the system, you must compile the program with-@-threaded@.--(/Since: 1.2.0.0/) On Unix systems, a negative value @'ExitFailure' -/signum/@-indicates that the child was terminated by signal @/signum/@.-The signal numbers are platform-specific, so to test for a specific signal use-the constants provided by "System.Posix.Signals" in the @unix@ package.-Note: core dumps are not reported, use "System.Posix.Process" if you need this-detail.---}-waitForProcess- :: ProcessHandle- -> IO ExitCode-waitForProcess ph@(ProcessHandle _ delegating_ctlc) = do- p_ <- modifyProcessHandle ph $ \p_ -> return (p_,p_)- case p_ of- ClosedHandle e -> return e- OpenHandle h -> do- -- don't hold the MVar while we call c_waitForProcess...- -- (XXX but there's a small race window here during which another- -- thread could close the handle or call waitForProcess)- e <- alloca $ \pret -> do- throwErrnoIfMinus1Retry_ "waitForProcess" (c_waitForProcess h pret)- modifyProcessHandle ph $ \p_' ->- case p_' of- ClosedHandle e -> return (p_',e)- OpenHandle ph' -> do- closePHANDLE ph'- code <- peek pret- let e = if (code == 0)- then ExitSuccess- else (ExitFailure (fromIntegral code))- return (ClosedHandle e, e)- when delegating_ctlc $- endDelegateControlC e- return e----- ------------------------------------------------------------------------------- getProcessExitCode--{- |-This is a non-blocking version of 'waitForProcess'. If the process is-still running, 'Nothing' is returned. If the process has exited, then-@'Just' e@ is returned where @e@ is the exit code of the process.--On Unix systems, see 'waitForProcess' for the meaning of exit codes-when the process died as the result of a signal.--}--getProcessExitCode :: ProcessHandle -> IO (Maybe ExitCode)-getProcessExitCode ph@(ProcessHandle _ delegating_ctlc) = do- (m_e, was_open) <- modifyProcessHandle ph $ \p_ ->- case p_ of- ClosedHandle e -> return (p_, (Just e, False))- OpenHandle h ->- alloca $ \pExitCode -> do- res <- throwErrnoIfMinus1Retry "getProcessExitCode" $- c_getProcessExitCode h pExitCode- code <- peek pExitCode- if res == 0- then return (p_, (Nothing, False))- else do- closePHANDLE h- let e | code == 0 = ExitSuccess- | otherwise = ExitFailure (fromIntegral code)- return (ClosedHandle e, (Just e, True))- case m_e of- Just e | was_open && delegating_ctlc -> endDelegateControlC e- _ -> return ()- return m_e----- ------------------------------------------------------------------------------- terminateProcess---- | Attempts to terminate the specified process. This function should--- not be used under normal circumstances - no guarantees are given regarding--- how cleanly the process is terminated. To check whether the process--- has indeed terminated, use 'getProcessExitCode'.------ On Unix systems, 'terminateProcess' sends the process the SIGTERM signal.--- On Windows systems, the Win32 @TerminateProcess@ function is called, passing--- an exit code of 1.------ Note: on Windows, if the process was a shell command created by--- 'createProcess' with 'shell', or created by 'runCommand' or--- 'runInteractiveCommand', then 'terminateProcess' will only--- terminate the shell, not the command itself. On Unix systems, both--- processes are in a process group and will be terminated together.--terminateProcess :: ProcessHandle -> IO ()-terminateProcess ph = do- withProcessHandle ph $ \p_ ->- case p_ of- ClosedHandle _ -> return ()- 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.----- ------------------------------------------------------------------------------- interruptProcessGroupOf---- | Sends an interrupt signal to the process group of the given process.------ On Unix systems, it sends the group the SIGINT signal.------ On Windows systems, it generates a CTRL_BREAK_EVENT and will only work for--- processes created using 'createProcess' and setting the 'create_group' flag--interruptProcessGroupOf- :: ProcessHandle -- ^ A process in the process group- -> IO ()-interruptProcessGroupOf ph = do- withProcessHandle ph $ \p_ -> do- case p_ of- ClosedHandle _ -> return ()- OpenHandle h -> do-#if mingw32_HOST_OS- pid <- getProcessId h- generateConsoleCtrlEvent cTRL_BREAK_EVENT pid--- We can't use an #elif here, because MIN_VERSION_unix isn't defined--- on Windows, so on Windows cpp fails:--- error: missing binary operator before token "("-#else- pgid <- getProcessGroupIDOf h- signalProcessGroup sigINT pgid-#endif- return ()----- ------------------------------------------------------------------------------- Interface to C bits--foreign import ccall unsafe "terminateProcess"- c_terminateProcess- :: PHANDLE- -> IO CInt--foreign import ccall unsafe "getProcessExitCode"- c_getProcessExitCode- :: PHANDLE- -> Ptr CInt- -> IO CInt--foreign import ccall interruptible "waitForProcess" -- NB. safe - can block- c_waitForProcess- :: PHANDLE- -> Ptr CInt- -> IO CInt----- ------------------------------------------------------------------------------- Old deprecated variants--- -------------------------------------------------------------------------------- TODO: We're not going to mark these functions as DEPRECATED immediately in--- process-1.2.0.0. That's because some of their replacements have not been--- around for all that long. But they should eventually be marked with a--- suitable DEPRECATED pragma after a release or two.----- ------------------------------------------------------------------------------- runCommand----TODO: in a later release {-# DEPRECATED runCommand "Use 'spawnCommand' instead" #-}--{- | Runs a command using the shell.- -}-runCommand- :: String- -> IO ProcessHandle--runCommand string = do- (_,_,_,ph) <- createProcess_ "runCommand" (shell string)- return ph----- ------------------------------------------------------------------------------- runProcess----TODO: in a later release {-# DEPRECATED runProcess "Use 'spawnProcess' or 'createProcess' instead" #-}--{- | Runs a raw command, optionally specifying 'Handle's from which to- take the @stdin@, @stdout@ and @stderr@ channels for the new- process (otherwise these handles are inherited from the current- process).-- Any 'Handle's passed to 'runProcess' are placed immediately in the- closed state.-- Note: consider using the more general 'createProcess' instead of- 'runProcess'.--}-runProcess- :: FilePath -- ^ Filename of the executable (see 'proc' for details)- -> [String] -- ^ Arguments to pass to the executable- -> Maybe FilePath -- ^ Optional path to the working directory- -> Maybe [(String,String)] -- ^ Optional environment (otherwise inherit)- -> Maybe Handle -- ^ Handle to use for @stdin@ (Nothing => use existing @stdin@)- -> Maybe Handle -- ^ Handle to use for @stdout@ (Nothing => use existing @stdout@)- -> Maybe Handle -- ^ Handle to use for @stderr@ (Nothing => use existing @stderr@)- -> IO ProcessHandle--runProcess cmd args mb_cwd mb_env mb_stdin mb_stdout mb_stderr = do- (_,_,_,ph) <-- createProcess_ "runProcess"- (proc cmd args){ cwd = mb_cwd,- env = mb_env,- std_in = mbToStd mb_stdin,- std_out = mbToStd mb_stdout,- std_err = mbToStd mb_stderr }- maybeClose mb_stdin- maybeClose mb_stdout- maybeClose mb_stderr- return ph- where- maybeClose :: Maybe Handle -> IO ()- maybeClose (Just hdl)- | hdl /= stdin && hdl /= stdout && hdl /= stderr = hClose hdl- maybeClose _ = return ()-- mbToStd :: Maybe Handle -> StdStream- mbToStd Nothing = Inherit- mbToStd (Just hdl) = UseHandle hdl----- ------------------------------------------------------------------------------- runInteractiveCommand----TODO: in a later release {-# DEPRECATED runInteractiveCommand "Use 'createProcess' instead" #-}--{- | Runs a command using the shell, and returns 'Handle's that may- be used to communicate with the process via its @stdin@, @stdout@,- and @stderr@ respectively. The 'Handle's are initially in binary- mode; if you need them to be in text mode then use 'hSetBinaryMode'.--}-runInteractiveCommand- :: String- -> IO (Handle,Handle,Handle,ProcessHandle)--runInteractiveCommand string =- runInteractiveProcess1 "runInteractiveCommand" (shell string)----- ------------------------------------------------------------------------------- runInteractiveProcess----TODO: in a later release {-# DEPRECATED runInteractiveCommand "Use 'createProcess' instead" #-}--{- | Runs a raw command, and returns 'Handle's that may be used to communicate- with the process via its @stdin@, @stdout@ and @stderr@ respectively.-- For example, to start a process and feed a string to its stdin:--> (inp,out,err,pid) <- runInteractiveProcess "..."-> forkIO (hPutStr inp str)-- The 'Handle's are initially in binary mode; if you need them to be- in text mode then use 'hSetBinaryMode'.--}-runInteractiveProcess- :: FilePath -- ^ Filename of the executable (see 'proc' for details)- -> [String] -- ^ Arguments to pass to the executable- -> Maybe FilePath -- ^ Optional path to the working directory- -> Maybe [(String,String)] -- ^ Optional environment (otherwise inherit)- -> IO (Handle,Handle,Handle,ProcessHandle)--runInteractiveProcess cmd args mb_cwd mb_env = do- runInteractiveProcess1 "runInteractiveProcess"- (proc cmd args){ cwd = mb_cwd, env = mb_env }--runInteractiveProcess1- :: String- -> CreateProcess- -> IO (Handle,Handle,Handle,ProcessHandle)-runInteractiveProcess1 fun cmd = do- (mb_in, mb_out, mb_err, p) <-- createProcess_ fun- cmd{ std_in = CreatePipe,- std_out = CreatePipe,- std_err = CreatePipe }- return (fromJust mb_in, fromJust mb_out, fromJust mb_err, p)-#endif /* !__HUGS__ */----- ------------------------------------------------------------------------------ system & rawSystem----TODO: in a later release {-# DEPRECATED system "Use 'callCommand' (or 'spawnCommand' and 'waitForProcess') instead" #-}--{-|-Computation @system cmd@ returns the exit code produced when the-operating system runs the shell command @cmd@.--This computation may fail with one of the following-'System.IO.Error.IOErrorType' exceptions:--[@PermissionDenied@]-The process has insufficient privileges to perform the operation.--[@ResourceExhausted@]-Insufficient resources are available to perform the operation.--[@UnsupportedOperation@]-The implementation does not support system calls.--On Windows, 'system' passes the command to the Windows command-interpreter (@CMD.EXE@ or @COMMAND.COM@), hence Unixy shell tricks-will not work.--On Unix systems, see 'waitForProcess' for the meaning of exit codes-when the process died as the result of a signal.--}-#ifdef __GLASGOW_HASKELL__-system :: String -> IO ExitCode-system "" = ioException (ioeSetErrorString (mkIOError InvalidArgument "system" Nothing Nothing) "null command")-system str = do- (_,_,_,p) <- createProcess_ "system" (shell str) { delegate_ctlc = True }- waitForProcess p-#endif /* __GLASGOW_HASKELL__ */-----TODO: in a later release {-# DEPRECATED rawSystem "Use 'callProcess' (or 'spawnProcess' and 'waitForProcess') instead" #-}--{-|-The computation @'rawSystem' /cmd/ /args/@ runs the operating system command-@/cmd/@ in such a way that it receives as arguments the @/args/@ strings-exactly as given, with no funny escaping or shell meta-syntax expansion.-It will therefore behave more portably between operating systems than 'system'.--The return codes and possible failures are the same as for 'system'.--}-rawSystem :: String -> [String] -> IO ExitCode-#ifdef __GLASGOW_HASKELL__-rawSystem cmd args = do- (_,_,_,p) <- createProcess_ "rawSystem" (proc cmd args) { delegate_ctlc = True }- waitForProcess p-#elif !mingw32_HOST_OS--- crude fallback implementation: could do much better than this under Unix-rawSystem cmd args = system (showCommandForUser cmd args)-#elif __HUGS__-rawSystem cmd args = system (cmd ++ showCommandForUser "" args)-#else-rawSystem cmd args = system (showCommandForUser cmd args)-#endif
+ System/Process.hsc view
@@ -0,0 +1,920 @@+{-# LANGUAGE CPP, ForeignFunctionInterface #-}+#ifdef __GLASGOW_HASKELL__+#if __GLASGOW_HASKELL__ >= 709+{-# LANGUAGE Safe #-}+#else+{-# LANGUAGE Trustworthy #-}+#endif+{-# LANGUAGE InterruptibleFFI #-}+#endif++-----------------------------------------------------------------------------+-- |+-- Module : System.Process+-- Copyright : (c) The University of Glasgow 2004-2008+-- License : BSD-style (see the file libraries/base/LICENSE)+--+-- Maintainer : libraries@haskell.org+-- Stability : experimental+-- Portability : non-portable (requires concurrency)+--+-- Operations for creating and interacting with sub-processes.+--+-----------------------------------------------------------------------------++-- ToDo:+-- * Flag to control whether exiting the parent also kills the child.++module System.Process (+ -- * Running sub-processes+ createProcess,+ createProcess_,+ shell, proc,+ CreateProcess(..),+ CmdSpec(..),+ StdStream(..),+ ProcessHandle,++ -- ** Simpler functions for common tasks+ callProcess,+ callCommand,+ spawnProcess,+ spawnCommand,+ readProcess,+ readProcessWithExitCode,++ -- ** Related utilities+ showCommandForUser,++ -- ** Control-C handling on Unix+ -- $ctlc-handling++ -- * Process completion+ waitForProcess,+ getProcessExitCode,+ terminateProcess,+ interruptProcessGroupOf,++ -- Interprocess communication+ createPipe,++ -- * Old deprecated functions+ -- | These functions pre-date 'createProcess' which is much more+ -- flexible.+ runProcess,+ runCommand,+ runInteractiveProcess,+ runInteractiveCommand,+ system,+ rawSystem,+ ) where++import Prelude hiding (mapM)++import System.Process.Internals++import Control.Concurrent+import Control.DeepSeq (rnf)+import Control.Exception (SomeException, mask, try, throwIO)+import qualified Control.Exception as C+import Control.Monad+import Data.Maybe+import Foreign+import Foreign.C+import System.Exit ( ExitCode(..) )+import System.IO+import System.IO.Error (mkIOError, ioeSetErrorString)++#if defined(mingw32_HOST_OS)+# include <io.h> /* for _close and _pipe */+# include <fcntl.h> /* for _O_BINARY */+import Control.Exception (onException)+import Foreign.C.Types (CInt(..), CUInt(..))+#else+import System.Posix.Process (getProcessGroupIDOf)+import qualified System.Posix.IO as Posix+import System.Posix.Types+#endif++#ifdef __GLASGOW_HASKELL__+import GHC.IO.Exception ( ioException, IOErrorType(..), IOException(..) )+# if defined(mingw32_HOST_OS)+import System.Win32.Console (generateConsoleCtrlEvent, cTRL_BREAK_EVENT)+import System.Win32.Process (getProcessId)+# else+import System.Posix.Signals+# endif+#endif++-- ----------------------------------------------------------------------------+-- createProcess++-- | Construct a 'CreateProcess' record for passing to 'createProcess',+-- representing a raw command with arguments.+--+-- See 'RawCommand' for precise semantics of the specified @FilePath@.+proc :: FilePath -> [String] -> CreateProcess+proc cmd args = CreateProcess { cmdspec = RawCommand cmd args,+ cwd = Nothing,+ env = Nothing,+ std_in = Inherit,+ std_out = Inherit,+ std_err = Inherit,+ close_fds = False,+ create_group = False,+ delegate_ctlc = False}++-- | Construct a 'CreateProcess' record for passing to 'createProcess',+-- representing a command to be passed to the shell.+shell :: String -> CreateProcess+shell str = CreateProcess { cmdspec = ShellCommand str,+ cwd = Nothing,+ env = Nothing,+ std_in = Inherit,+ std_out = Inherit,+ std_err = Inherit,+ close_fds = False,+ create_group = False,+ delegate_ctlc = False}++{- |+This is the most general way to spawn an external process. The+process can be a command line to be executed by a shell or a raw command+with a list of arguments. The stdin, stdout, and stderr streams of+the new process may individually be attached to new pipes, to existing+'Handle's, or just inherited from the parent (the default.)++The details of how to create the process are passed in the+'CreateProcess' record. To make it easier to construct a+'CreateProcess', the functions 'proc' and 'shell' are supplied that+fill in the fields with default values which can be overriden as+needed.++'createProcess' returns @(/mb_stdin_hdl/, /mb_stdout_hdl/, /mb_stderr_hdl/, /ph/)@,+where++ * if @'std_in' == 'CreatePipe'@, then @/mb_stdin_hdl/@ will be @Just /h/@,+ where @/h/@ is the write end of the pipe connected to the child+ process's @stdin@.++ * otherwise, @/mb_stdin_hdl/ == Nothing@++Similarly for @/mb_stdout_hdl/@ and @/mb_stderr_hdl/@.++For example, to execute a simple @ls@ command:++> r <- createProcess (proc "ls" [])++To create a pipe from which to read the output of @ls@:++> (_, Just hout, _, _) <-+> createProcess (proc "ls" []){ std_out = CreatePipe }++To also set the directory in which to run @ls@:++> (_, Just hout, _, _) <-+> createProcess (proc "ls" []){ cwd = Just "\home\bob",+> std_out = CreatePipe }++Note that @Handle@s provided for @std_in@, @std_out@, or @std_err@ via the+@UseHandle@ constructor will be closed by calling this function. This is not+always the desired behavior. In cases where you would like to leave the+@Handle@ open after spawning the child process, please use 'createProcess_'+instead.++-}+createProcess+ :: CreateProcess+ -> IO (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)+createProcess cp = do+ r <- createProcess_ "createProcess" cp+ maybeCloseStd (std_in cp)+ maybeCloseStd (std_out cp)+ maybeCloseStd (std_err cp)+ return r+ where+ maybeCloseStd :: StdStream -> IO ()+ maybeCloseStd (UseHandle hdl)+ | hdl /= stdin && hdl /= stdout && hdl /= stderr = hClose hdl+ maybeCloseStd _ = return ()++{-+-- TODO: decide if we want to expose this to users+-- | A 'C.bracketOnError'-style resource handler for 'createProcess'.+--+-- In normal operation it adds nothing, you are still responsible for waiting+-- for (or forcing) process termination and closing any 'Handle's. It only does+-- automatic cleanup if there is an exception. If there is an exception in the+-- body then it ensures that the process gets terminated and any 'CreatePipe'+-- 'Handle's are closed. In particular this means that if the Haskell thread+-- is killed (e.g. 'killThread'), that the external process is also terminated.+--+-- e.g.+--+-- > withCreateProcess (proc cmd args) { ... } $ \_ _ _ ph -> do+-- > ...+--+withCreateProcess+ :: CreateProcess+ -> (Maybe Handle -> Maybe Handle -> Maybe Handle -> ProcessHandle -> IO a)+ -> IO a+withCreateProcess c action =+ C.bracketOnError (createProcess c) cleanupProcess+ (\(m_in, m_out, m_err, ph) -> action m_in m_out m_err ph)+-}++-- wrapper so we can get exceptions with the appropriate function name.+withCreateProcess_+ :: String+ -> CreateProcess+ -> (Maybe Handle -> Maybe Handle -> Maybe Handle -> ProcessHandle -> IO a)+ -> IO a+withCreateProcess_ fun c action =+ C.bracketOnError (createProcess_ fun c) cleanupProcess+ (\(m_in, m_out, m_err, ph) -> action m_in m_out m_err ph)+++cleanupProcess :: (Maybe Handle, Maybe Handle, Maybe Handle, ProcessHandle)+ -> IO ()+cleanupProcess (mb_stdin, mb_stdout, mb_stderr, ph) = do+ terminateProcess ph+ -- Note, it's important that other threads that might be reading/writing+ -- these handles also get killed off, since otherwise they might be holding+ -- the handle lock and prevent us from closing, leading to deadlock.+ maybe (return ()) (ignoreSigPipe . hClose) mb_stdin+ maybe (return ()) hClose mb_stdout+ maybe (return ()) hClose mb_stderr+ -- terminateProcess does not guarantee that it terminates the process.+ -- Indeed on Unix it's SIGTERM, which asks nicely but does not guarantee+ -- that it stops. If it doesn't stop, we don't want to hang, so we wait+ -- asynchronously using forkIO.+ _ <- forkIO (waitForProcess ph >> return ())+ return ()+++-- ----------------------------------------------------------------------------+-- spawnProcess/spawnCommand++-- | Creates a new process to run the specified raw command with the given+-- arguments. It does not wait for the program to finish, but returns the+-- 'ProcessHandle'.+--+-- /Since: 1.2.0.0/+spawnProcess :: FilePath -> [String] -> IO ProcessHandle+spawnProcess cmd args = do+ (_,_,_,p) <- createProcess_ "spawnProcess" (proc cmd args)+ return p++-- | Creates a new process to run the specified shell command.+-- It does not wait for the program to finish, but returns the 'ProcessHandle'.+--+-- /Since: 1.2.0.0/+spawnCommand :: String -> IO ProcessHandle+spawnCommand cmd = do+ (_,_,_,p) <- createProcess_ "spawnCommand" (shell cmd)+ return p+++-- ----------------------------------------------------------------------------+-- callProcess/callCommand++-- | Creates a new process to run the specified command with the given+-- arguments, and wait for it to finish. If the command returns a non-zero+-- exit code, an exception is raised.+--+-- If an asynchronous exception is thrown to the thread executing+-- @callProcess@. The forked process will be terminated and+-- @callProcess@ will wait (block) until the process has been+-- terminated.+--+-- /Since: 1.2.0.0/+callProcess :: FilePath -> [String] -> IO ()+callProcess cmd args = do+ exit_code <- withCreateProcess_ "callCommand"+ (proc cmd args) { delegate_ctlc = True } $ \_ _ _ p ->+ waitForProcess p+ case exit_code of+ ExitSuccess -> return ()+ ExitFailure r -> processFailedException "callProcess" cmd args r++-- | Creates a new process to run the specified shell command. If the+-- command returns a non-zero exit code, an exception is raised.+--+-- If an asynchronous exception is thrown to the thread executing+-- @callCommand@. The forked process will be terminated and+-- @callCommand@ will wait (block) until the process has been+-- terminated.+--+-- /Since: 1.2.0.0/+callCommand :: String -> IO ()+callCommand cmd = do+ exit_code <- withCreateProcess_ "callCommand"+ (shell cmd) { delegate_ctlc = True } $ \_ _ _ p ->+ waitForProcess p+ case exit_code of+ ExitSuccess -> return ()+ ExitFailure r -> processFailedException "callCommand" cmd [] r++processFailedException :: String -> String -> [String] -> Int -> IO a+processFailedException fun cmd args exit_code =+ ioError (mkIOError OtherError (fun ++ ": " ++ cmd +++ concatMap ((' ':) . show) args +++ " (exit " ++ show exit_code ++ ")")+ Nothing Nothing)+++-- ----------------------------------------------------------------------------+-- Control-C handling on Unix++-- $ctlc-handling+--+-- When running an interactive console process (such as a shell, console-based+-- text editor or ghci), we typically want that process to be allowed to handle+-- Ctl-C keyboard interrupts how it sees fit. For example, while most programs+-- simply quit on a Ctl-C, some handle it specially. To allow this to happen,+-- use the @'delegate_ctlc' = True@ option in the 'CreateProcess' options.+--+-- The gory details:+--+-- By default Ctl-C will generate a @SIGINT@ signal, causing a 'UserInterrupt'+-- exception to be sent to the main Haskell thread of your program, which if+-- not specially handled will terminate the program. Normally, this is exactly+-- what is wanted: an orderly shutdown of the program in response to Ctl-C.+--+-- Of course when running another interactive program in the console then we+-- want to let that program handle Ctl-C. Under Unix however, Ctl-C sends+-- @SIGINT@ to every process using the console. The standard solution is that+-- while running an interactive program, ignore @SIGINT@ in the parent, and let+-- it be handled in the child process. If that process then terminates due to+-- the @SIGINT@ signal, then at that point treat it as if we had recieved the+-- @SIGINT@ ourselves and begin an orderly shutdown.+--+-- This behaviour is implemented by 'createProcess' (and+-- 'waitForProcess' \/ 'getProcessExitCode') when the @'delegate_ctlc' = True@+-- option is set. In particular, the @SIGINT@ signal will be ignored until+-- 'waitForProcess' returns (or 'getProcessExitCode' returns a non-Nothing+-- result), so it becomes especially important to use 'waitForProcess' for every+-- processes created.+--+-- In addition, in 'delegate_ctlc' mode, 'waitForProcess' and+-- 'getProcessExitCode' will throw a 'UserInterrupt' exception if the process+-- terminated with @'ExitFailure' (-SIGINT)@. Typically you will not want to+-- catch this exception, but let it propagate, giving a normal orderly shutdown.+-- One detail to be aware of is that the 'UserInterrupt' exception is thrown+-- /synchronously/ in the thread that calls 'waitForProcess', whereas normally+-- @SIGINT@ causes the exception to be thrown /asynchronously/ to the main+-- thread.+--+-- For even more detail on this topic, see+-- <http://www.cons.org/cracauer/sigint.html "Proper handling of SIGINT/SIGQUIT">.++-- -----------------------------------------------------------------------------++-- | @readProcess@ forks an external process, reads its standard output+-- strictly, blocking until the process terminates, and returns the output+-- string.+--+-- If an asynchronous exception is thrown to the thread executing+-- @readProcess@. The forked process will be terminated and @readProcess@ will+-- wait (block) until the process has been terminated.+--+-- Output is returned strictly, so this is not suitable for+-- interactive applications.+--+-- This function throws an 'IOError' if the process 'ExitCode' is+-- anything other than 'ExitSuccess'.+--+-- Users of this function should compile with @-threaded@ if they+-- want other Haskell threads to keep running while waiting on+-- the result of readProcess.+--+-- > > readProcess "date" [] []+-- > "Thu Feb 7 10:03:39 PST 2008\n"+--+-- The arguments are:+--+-- * The command to run, which must be in the $PATH, or an absolute or relative path+--+-- * A list of separate command line arguments to the program+--+-- * A string to pass on standard input to the forked process.+--+readProcess+ :: FilePath -- ^ Filename of the executable (see 'RawCommand' for details)+ -> [String] -- ^ any arguments+ -> String -- ^ standard input+ -> IO String -- ^ stdout+readProcess cmd args input = do+ let cp_opts = (proc cmd args) {+ std_in = CreatePipe,+ std_out = CreatePipe,+ std_err = Inherit+ }+ (ex, output) <- withCreateProcess_ "readProcess" cp_opts $+ \(Just inh) (Just outh) _ ph -> do++ -- fork off a thread to start consuming the output+ output <- hGetContents outh+ withForkWait (C.evaluate $ rnf output) $ \waitOut -> do++ -- now write any input+ unless (null input) $+ ignoreSigPipe $ hPutStr inh input+ -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE+ ignoreSigPipe $ hClose inh++ -- wait on the output+ waitOut+ hClose outh++ -- wait on the process+ ex <- waitForProcess ph+ return (ex, output)++ case ex of+ ExitSuccess -> return output+ ExitFailure r -> processFailedException "readProcess" cmd args r++{- |+@readProcessWithExitCode@ creates an external process, reads its+standard output and standard error strictly, waits until the process+terminates, and then returns the 'ExitCode' of the process,+the standard output, and the standard error.++If an asynchronous exception is thrown to the thread executing+@readProcessWithExitCode@. The forked process will be terminated and+@readProcessWithExitCode@ will wait (block) until the process has been+terminated.++'readProcess' and 'readProcessWithExitCode' are fairly simple wrappers+around 'createProcess'. Constructing variants of these functions is+quite easy: follow the link to the source code to see how+'readProcess' is implemented.++On Unix systems, see 'waitForProcess' for the meaning of exit codes+when the process died as the result of a signal.+-}++readProcessWithExitCode+ :: FilePath -- ^ Filename of the executable (see 'RawCommand' for details)+ -> [String] -- ^ any arguments+ -> String -- ^ standard input+ -> IO (ExitCode,String,String) -- ^ exitcode, stdout, stderr+readProcessWithExitCode cmd args input = do+ let cp_opts = (proc cmd args) {+ std_in = CreatePipe,+ std_out = CreatePipe,+ std_err = CreatePipe+ }+ withCreateProcess_ "readProcessWithExitCode" cp_opts $+ \(Just inh) (Just outh) (Just errh) ph -> do++ out <- hGetContents outh+ err <- hGetContents errh++ -- fork off threads to start consuming stdout & stderr+ withForkWait (C.evaluate $ rnf out) $ \waitOut ->+ withForkWait (C.evaluate $ rnf err) $ \waitErr -> do++ -- now write any input+ unless (null input) $+ ignoreSigPipe $ hPutStr inh input+ -- hClose performs implicit hFlush, and thus may trigger a SIGPIPE+ ignoreSigPipe $ hClose inh++ -- wait on the output+ waitOut+ waitErr++ hClose outh+ hClose errh++ -- wait on the process+ ex <- waitForProcess ph++ return (ex, out, err)++-- | Fork a thread while doing something else, but kill it if there's an+-- exception.+--+-- This is important in the cases above because we want to kill the thread+-- that is holding the Handle lock, because when we clean up the process we+-- try to close that handle, which could otherwise deadlock.+--+withForkWait :: IO () -> (IO () -> IO a) -> IO a+withForkWait async body = do+ waitVar <- newEmptyMVar :: IO (MVar (Either SomeException ()))+ mask $ \restore -> do+ tid <- forkIO $ try (restore async) >>= putMVar waitVar+ let wait = takeMVar waitVar >>= either throwIO return+ restore (body wait) `C.onException` killThread tid++ignoreSigPipe :: IO () -> IO ()+#if defined(__GLASGOW_HASKELL__)+ignoreSigPipe = C.handle $ \e -> case e of+ IOError { ioe_type = ResourceVanished+ , ioe_errno = Just ioe }+ | Errno ioe == ePIPE -> return ()+ _ -> throwIO e+#else+ignoreSigPipe = id+#endif++-- ----------------------------------------------------------------------------+-- showCommandForUser++-- | Given a program @/p/@ and arguments @/args/@,+-- @showCommandForUser /p/ /args/@ returns a string suitable for pasting+-- into @\/bin\/sh@ (on Unix systems) or @CMD.EXE@ (on Windows).+showCommandForUser :: FilePath -> [String] -> String+showCommandForUser cmd args = unwords (map translate (cmd : args))+++-- ----------------------------------------------------------------------------+-- waitForProcess++{- | Waits for the specified process to terminate, and returns its exit code.++GHC Note: in order to call @waitForProcess@ without blocking all the+other threads in the system, you must compile the program with+@-threaded@.++(/Since: 1.2.0.0/) On Unix systems, a negative value @'ExitFailure' -/signum/@+indicates that the child was terminated by signal @/signum/@.+The signal numbers are platform-specific, so to test for a specific signal use+the constants provided by "System.Posix.Signals" in the @unix@ package.+Note: core dumps are not reported, use "System.Posix.Process" if you need this+detail.++-}+waitForProcess+ :: ProcessHandle+ -> IO ExitCode+waitForProcess ph@(ProcessHandle _ delegating_ctlc) = do+ p_ <- modifyProcessHandle ph $ \p_ -> return (p_,p_)+ case p_ of+ ClosedHandle e -> return e+ OpenHandle h -> do+ -- don't hold the MVar while we call c_waitForProcess...+ -- (XXX but there's a small race window here during which another+ -- thread could close the handle or call waitForProcess)+ e <- alloca $ \pret -> do+ throwErrnoIfMinus1Retry_ "waitForProcess" (c_waitForProcess h pret)+ modifyProcessHandle ph $ \p_' ->+ case p_' of+ ClosedHandle e -> return (p_',e)+ OpenHandle ph' -> do+ closePHANDLE ph'+ code <- peek pret+ let e = if (code == 0)+ then ExitSuccess+ else (ExitFailure (fromIntegral code))+ return (ClosedHandle e, e)+ when delegating_ctlc $+ endDelegateControlC e+ return e+++-- ----------------------------------------------------------------------------+-- getProcessExitCode++{- |+This is a non-blocking version of 'waitForProcess'. If the process is+still running, 'Nothing' is returned. If the process has exited, then+@'Just' e@ is returned where @e@ is the exit code of the process.++On Unix systems, see 'waitForProcess' for the meaning of exit codes+when the process died as the result of a signal.+-}++getProcessExitCode :: ProcessHandle -> IO (Maybe ExitCode)+getProcessExitCode ph@(ProcessHandle _ delegating_ctlc) = do+ (m_e, was_open) <- modifyProcessHandle ph $ \p_ ->+ case p_ of+ ClosedHandle e -> return (p_, (Just e, False))+ OpenHandle h ->+ alloca $ \pExitCode -> do+ res <- throwErrnoIfMinus1Retry "getProcessExitCode" $+ c_getProcessExitCode h pExitCode+ code <- peek pExitCode+ if res == 0+ then return (p_, (Nothing, False))+ else do+ closePHANDLE h+ let e | code == 0 = ExitSuccess+ | otherwise = ExitFailure (fromIntegral code)+ return (ClosedHandle e, (Just e, True))+ case m_e of+ Just e | was_open && delegating_ctlc -> endDelegateControlC e+ _ -> return ()+ return m_e+++-- ----------------------------------------------------------------------------+-- terminateProcess++-- | Attempts to terminate the specified process. This function should+-- not be used under normal circumstances - no guarantees are given regarding+-- how cleanly the process is terminated. To check whether the process+-- has indeed terminated, use 'getProcessExitCode'.+--+-- On Unix systems, 'terminateProcess' sends the process the SIGTERM signal.+-- On Windows systems, the Win32 @TerminateProcess@ function is called, passing+-- an exit code of 1.+--+-- Note: on Windows, if the process was a shell command created by+-- 'createProcess' with 'shell', or created by 'runCommand' or+-- 'runInteractiveCommand', then 'terminateProcess' will only+-- terminate the shell, not the command itself. On Unix systems, both+-- processes are in a process group and will be terminated together.++terminateProcess :: ProcessHandle -> IO ()+terminateProcess ph = do+ withProcessHandle ph $ \p_ ->+ case p_ of+ ClosedHandle _ -> return ()+ 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.+++-- ----------------------------------------------------------------------------+-- interruptProcessGroupOf++-- | Sends an interrupt signal to the process group of the given process.+--+-- On Unix systems, it sends the group the SIGINT signal.+--+-- On Windows systems, it generates a CTRL_BREAK_EVENT and will only work for+-- processes created using 'createProcess' and setting the 'create_group' flag++interruptProcessGroupOf+ :: ProcessHandle -- ^ A process in the process group+ -> IO ()+interruptProcessGroupOf ph = do+ withProcessHandle ph $ \p_ -> do+ case p_ of+ ClosedHandle _ -> return ()+ OpenHandle h -> do+#if mingw32_HOST_OS+ pid <- getProcessId h+ generateConsoleCtrlEvent cTRL_BREAK_EVENT pid+-- We can't use an #elif here, because MIN_VERSION_unix isn't defined+-- on Windows, so on Windows cpp fails:+-- error: missing binary operator before token "("+#else+ pgid <- getProcessGroupIDOf h+ signalProcessGroup sigINT pgid+#endif+ return ()+++-- ----------------------------------------------------------------------------+-- Interface to C bits++foreign import ccall unsafe "terminateProcess"+ c_terminateProcess+ :: PHANDLE+ -> IO CInt++foreign import ccall unsafe "getProcessExitCode"+ c_getProcessExitCode+ :: PHANDLE+ -> Ptr CInt+ -> IO CInt++foreign import ccall interruptible "waitForProcess" -- NB. safe - can block+ c_waitForProcess+ :: PHANDLE+ -> Ptr CInt+ -> IO CInt+++-- ----------------------------------------------------------------------------+-- Old deprecated variants+-- ----------------------------------------------------------------------------++-- TODO: We're not going to mark these functions as DEPRECATED immediately in+-- process-1.2.0.0. That's because some of their replacements have not been+-- around for all that long. But they should eventually be marked with a+-- suitable DEPRECATED pragma after a release or two.+++-- ----------------------------------------------------------------------------+-- runCommand++--TODO: in a later release {-# DEPRECATED runCommand "Use 'spawnCommand' instead" #-}++{- | Runs a command using the shell.+ -}+runCommand+ :: String+ -> IO ProcessHandle++runCommand string = do+ (_,_,_,ph) <- createProcess_ "runCommand" (shell string)+ return ph+++-- ----------------------------------------------------------------------------+-- runProcess++--TODO: in a later release {-# DEPRECATED runProcess "Use 'spawnProcess' or 'createProcess' instead" #-}++{- | Runs a raw command, optionally specifying 'Handle's from which to+ take the @stdin@, @stdout@ and @stderr@ channels for the new+ process (otherwise these handles are inherited from the current+ process).++ Any 'Handle's passed to 'runProcess' are placed immediately in the+ closed state.++ Note: consider using the more general 'createProcess' instead of+ 'runProcess'.+-}+runProcess+ :: FilePath -- ^ Filename of the executable (see 'RawCommand' for details)+ -> [String] -- ^ Arguments to pass to the executable+ -> Maybe FilePath -- ^ Optional path to the working directory+ -> Maybe [(String,String)] -- ^ Optional environment (otherwise inherit)+ -> Maybe Handle -- ^ Handle to use for @stdin@ (Nothing => use existing @stdin@)+ -> Maybe Handle -- ^ Handle to use for @stdout@ (Nothing => use existing @stdout@)+ -> Maybe Handle -- ^ Handle to use for @stderr@ (Nothing => use existing @stderr@)+ -> IO ProcessHandle++runProcess cmd args mb_cwd mb_env mb_stdin mb_stdout mb_stderr = do+ (_,_,_,ph) <-+ createProcess_ "runProcess"+ (proc cmd args){ cwd = mb_cwd,+ env = mb_env,+ std_in = mbToStd mb_stdin,+ std_out = mbToStd mb_stdout,+ std_err = mbToStd mb_stderr }+ maybeClose mb_stdin+ maybeClose mb_stdout+ maybeClose mb_stderr+ return ph+ where+ maybeClose :: Maybe Handle -> IO ()+ maybeClose (Just hdl)+ | hdl /= stdin && hdl /= stdout && hdl /= stderr = hClose hdl+ maybeClose _ = return ()++ mbToStd :: Maybe Handle -> StdStream+ mbToStd Nothing = Inherit+ mbToStd (Just hdl) = UseHandle hdl+++-- ----------------------------------------------------------------------------+-- runInteractiveCommand++--TODO: in a later release {-# DEPRECATED runInteractiveCommand "Use 'createProcess' instead" #-}++{- | Runs a command using the shell, and returns 'Handle's that may+ be used to communicate with the process via its @stdin@, @stdout@,+ and @stderr@ respectively. The 'Handle's are initially in binary+ mode; if you need them to be in text mode then use 'hSetBinaryMode'.+-}+runInteractiveCommand+ :: String+ -> IO (Handle,Handle,Handle,ProcessHandle)++runInteractiveCommand string =+ runInteractiveProcess1 "runInteractiveCommand" (shell string)+++-- ----------------------------------------------------------------------------+-- runInteractiveProcess++--TODO: in a later release {-# DEPRECATED runInteractiveCommand "Use 'createProcess' instead" #-}++{- | Runs a raw command, and returns 'Handle's that may be used to communicate+ with the process via its @stdin@, @stdout@ and @stderr@ respectively.++ For example, to start a process and feed a string to its stdin:++> (inp,out,err,pid) <- runInteractiveProcess "..."+> forkIO (hPutStr inp str)++ The 'Handle's are initially in binary mode; if you need them to be+ in text mode then use 'hSetBinaryMode'.+-}+runInteractiveProcess+ :: FilePath -- ^ Filename of the executable (see 'RawCommand' for details)+ -> [String] -- ^ Arguments to pass to the executable+ -> Maybe FilePath -- ^ Optional path to the working directory+ -> Maybe [(String,String)] -- ^ Optional environment (otherwise inherit)+ -> IO (Handle,Handle,Handle,ProcessHandle)++runInteractiveProcess cmd args mb_cwd mb_env = do+ runInteractiveProcess1 "runInteractiveProcess"+ (proc cmd args){ cwd = mb_cwd, env = mb_env }++runInteractiveProcess1+ :: String+ -> CreateProcess+ -> IO (Handle,Handle,Handle,ProcessHandle)+runInteractiveProcess1 fun cmd = do+ (mb_in, mb_out, mb_err, p) <-+ createProcess_ fun+ cmd{ std_in = CreatePipe,+ std_out = CreatePipe,+ std_err = CreatePipe }+ return (fromJust mb_in, fromJust mb_out, fromJust mb_err, p)+++-- ---------------------------------------------------------------------------+-- system & rawSystem++--TODO: in a later release {-# DEPRECATED system "Use 'callCommand' (or 'spawnCommand' and 'waitForProcess') instead" #-}++{-|+Computation @system cmd@ returns the exit code produced when the+operating system runs the shell command @cmd@.++This computation may fail with one of the following+'System.IO.Error.IOErrorType' exceptions:++[@PermissionDenied@]+The process has insufficient privileges to perform the operation.++[@ResourceExhausted@]+Insufficient resources are available to perform the operation.++[@UnsupportedOperation@]+The implementation does not support system calls.++On Windows, 'system' passes the command to the Windows command+interpreter (@CMD.EXE@ or @COMMAND.COM@), hence Unixy shell tricks+will not work.++On Unix systems, see 'waitForProcess' for the meaning of exit codes+when the process died as the result of a signal.+-}+#ifdef __GLASGOW_HASKELL__+system :: String -> IO ExitCode+system "" = ioException (ioeSetErrorString (mkIOError InvalidArgument "system" Nothing Nothing) "null command")+system str = do+ (_,_,_,p) <- createProcess_ "system" (shell str) { delegate_ctlc = True }+ waitForProcess p+#endif /* __GLASGOW_HASKELL__ */+++--TODO: in a later release {-# DEPRECATED rawSystem "Use 'callProcess' (or 'spawnProcess' and 'waitForProcess') instead" #-}++{-|+The computation @'rawSystem' /cmd/ /args/@ runs the operating system command+@/cmd/@ in such a way that it receives as arguments the @/args/@ strings+exactly as given, with no funny escaping or shell meta-syntax expansion.+It will therefore behave more portably between operating systems than 'system'.++The return codes and possible failures are the same as for 'system'.+-}+rawSystem :: String -> [String] -> IO ExitCode+#ifdef __GLASGOW_HASKELL__+rawSystem cmd args = do+ (_,_,_,p) <- createProcess_ "rawSystem" (proc cmd args) { delegate_ctlc = True }+ waitForProcess p+#elif !mingw32_HOST_OS+-- crude fallback implementation: could do much better than this under Unix+rawSystem cmd args = system (showCommandForUser cmd args)+#else+rawSystem cmd args = system (showCommandForUser cmd args)+#endif++-- ---------------------------------------------------------------------------+-- createPipe++-- | Create a pipe for interprocess communication and return a+-- @(readEnd, writeEnd)@ `Handle` pair.+--+-- /Since: 1.2.1.0/+createPipe :: IO (Handle, Handle)+#if !mingw32_HOST_OS+createPipe = do+ (readfd, writefd) <- Posix.createPipe+ readh <- Posix.fdToHandle readfd+ writeh <- Posix.fdToHandle writefd+ return (readh, writeh)+#else+createPipe = do+ (readfd, writefd) <- allocaArray 2 $ \ pfds -> do+ throwErrnoIfMinus1_ "_pipe" $ c__pipe pfds 2 (#const _O_BINARY)+ readfd <- peek pfds+ writefd <- peekElemOff pfds 1+ return (readfd, writefd)+ (do readh <- fdToHandle readfd+ writeh <- fdToHandle writefd+ return (readh, writeh)) `onException` (close readfd >> close writefd)++close :: CInt -> IO ()+close = throwErrnoIfMinus1_ "_close" . c__close++foreign import ccall "io.h _pipe" c__pipe ::+ Ptr CInt -> CUInt -> CInt -> IO CInt++foreign import ccall "io.h _close" c__close ::+ CInt -> IO CInt+#endif
System/Process/Internals.hs view
@@ -1,5 +1,5 @@ {-# LANGUAGE CPP, RecordWildCards, BangPatterns #-}-{-# OPTIONS_HADDOCK hide #-}+{-# OPTIONS_HADDOCK not-home #-} #ifdef __GLASGOW_HASKELL__ {-# LANGUAGE Trustworthy #-} {-# LANGUAGE InterruptibleFFI #-}@@ -15,48 +15,51 @@ -- Stability : experimental -- Portability : portable ----- Operations for creating and interacting with sub-processes.+-- __Note:__ This module exports internal implementation details that may+-- change anytime. If you want a more stable API, use "System.Process"+-- instead. -- ----------------------------------------------------------------------------- module System.Process.Internals (- ProcessHandle(..), ProcessHandle__(..),- PHANDLE, closePHANDLE, mkProcessHandle,- modifyProcessHandle, withProcessHandle,+ ProcessHandle(..), ProcessHandle__(..),+ PHANDLE, closePHANDLE, mkProcessHandle,+ modifyProcessHandle, withProcessHandle, #ifdef __GLASGOW_HASKELL__- CreateProcess(..),- CmdSpec(..), StdStream(..),- createProcess_,- runGenProcess_, --deprecated-#endif- startDelegateControlC,- endDelegateControlC,-#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)- pPrPr_disableITimers, c_execvpe,- ignoreSignal, defaultSignal,+ CreateProcess(..),+ CmdSpec(..), StdStream(..),+ createProcess_,+ runGenProcess_, --deprecated #endif- withFilePathException, withCEnvironment,- translate,- fdToHandle,- ) where-+ startDelegateControlC,+ endDelegateControlC, #if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)-import Control.Monad-import Data.Char-import System.Posix.Types-import System.Posix.Process.Internals ( pPrPr_disableITimers, c_execvpe )-import System.IO+ pPrPr_disableITimers, c_execvpe,+ ignoreSignal, defaultSignal, #endif+ withFilePathException, withCEnvironment,+ translate,+ fdToHandle,+ ) where import Control.Concurrent import Control.Exception import Data.Bits+import Data.String import Foreign.C import Foreign.Marshal import Foreign.Ptr import Foreign.Storable import System.IO.Unsafe +#if !defined(mingw32_HOST_OS) && !defined(__MINGW32__)+import Control.Monad+import Data.Char+import System.IO+import System.Posix.Process.Internals ( pPrPr_disableITimers, c_execvpe )+import System.Posix.Types+#endif+ #ifdef __GLASGOW_HASKELL__ import System.Posix.Internals import GHC.IO.Exception@@ -68,12 +71,12 @@ import GHC.IO.Handle.Types hiding (ClosedHandle) import System.IO.Error import Data.Typeable-#if defined(mingw32_HOST_OS)+# if defined(mingw32_HOST_OS) import GHC.IO.IOMode import System.Win32.DebugApi (PHANDLE)-#else+# else import System.Posix.Signals as Sig-#endif+# endif #endif #if defined(mingw32_HOST_OS)@@ -99,7 +102,7 @@ -- ProcessHandle type {- | A handle to a process, which can be used to wait for termination- of the process using 'waitForProcess'.+ of the process using 'System.Process.waitForProcess'. None of the process-creation functions in this library wait for termination: they all return a 'ProcessHandle' which may be used@@ -182,12 +185,37 @@ data CmdSpec = ShellCommand String- -- ^ a command line to execute using the shell+ -- ^ A command line to execute using the shell | RawCommand FilePath [String]- -- ^ the filename of an executable with a list of arguments.- -- see 'System.Process.proc' for the precise interpretation of- -- the @FilePath@ field.+ -- ^ The name of an executable with a list of arguments+ --+ -- The 'FilePath' argument names the executable, and is interpreted+ -- according to the platform's standard policy for searching for+ -- executables. Specifically:+ --+ -- * on Unix systems the+ -- <http://pubs.opengroup.org/onlinepubs/9699919799/functions/execvp.html execvp(3)>+ -- semantics is used, where if the executable filename does not+ -- contain a slash (@/@) then the @PATH@ environment variable is+ -- searched for the executable.+ --+ -- * on Windows systems the Win32 @CreateProcess@ semantics is used.+ -- Briefly: if the filename does not contain a path, then the+ -- directory containing the parent executable is searched, followed+ -- by the current directory, then some standard locations, and+ -- finally the current @PATH@. An @.exe@ extension is added if the+ -- filename does not already have an extension. For full details+ -- see the+ -- <http://msdn.microsoft.com/en-us/library/windows/desktop/aa365527%28v=vs.85%29.aspx documentation>+ -- for the Windows @SearchPath@ API. ++-- | construct a `ShellCommand` from a string literal+--+-- /Since: 1.2.1.0/+instance IsString CmdSpec where+ fromString = ShellCommand+ data StdStream = Inherit -- ^ Inherit Handle from parent | UseHandle Handle -- ^ Use the supplied Handle@@ -196,6 +224,19 @@ -- and newline translation mode (just -- like @Handle@s created by @openFile@). +-- | This function is almost identical to+-- 'System.Process.createProcess'. The only differences are:+--+-- * 'Handle's provided via 'UseHandle' are not closed automatically.+--+-- * This function takes an extra @String@ argument to be used in creating+-- error messages.+--+-- This function has been available from the "System.Process.Internals" module+-- for some time, and is part of the "System.Process" module since version+-- 1.2.1.0.+--+-- /Since: 1.2.1.0/ createProcess_ :: String -- ^ function name (for error messages) -> CreateProcess
− changelog
@@ -1,15 +0,0 @@-1.2.0.0 Dec 2013-- * Update to Cabal 1.10 format- * Remove NHC specific code- * Add support for `base-4.7.0.0`- * Improve `showCommandForUser` to reduce redundant quoting- * New functions `callProcess`, `callCommand`, `spawnProcess` and `spawnCommand`- * Implement WCE handling according to http://www.cons.org/cracauer/sigint.html- * New `delegate_ctlc` field in `CreateProcess` for WCE handling- * Use `ExitFailure (-signum)` on Unix when a proc is terminated due to- a signal.- * Deprecate `module System.Cmd`- * On non-Windows, the child thread now comunicates any errors back- to the parent thread via pipes.- * Fix deadlocks in `readProcess` and `readProcessWithExitCode`
+ changelog.md view
@@ -0,0 +1,36 @@+# Changelog for [`process` package](http://hackage.haskell.org/package/process)++## 1.2.1.0 *Dec 2014*++ * Add support for `base-4.8.0.0`++ * Remove Hugs98 specific code++ * New `IsString CmdSpec` instance++ * Expose documentation for `System.Process.Internals`++ * With GHC 7.10, `System.Cmd` and `System.Process` are now `Safe`+ (when compiled with older GHC versions they are just `Trustworthy`)++ * Expose `createProcess_` function, and document behavior of `UseHandle` for+ `createProcess`. See [issue #2](https://github.com/haskell/process/issues/2).++ * New `System.Process.createPipe` operation.+ See also [GHC #8943](https://ghc.haskell.org/trac/ghc/ticket/8943)++## 1.2.0.0 *Dec 2013*++ * Update to Cabal 1.10 format+ * Remove NHC specific code+ * Add support for `base-4.7.0.0`+ * Improve `showCommandForUser` to reduce redundant quoting+ * New functions `callProcess`, `callCommand`, `spawnProcess` and `spawnCommand`+ * Implement WCE handling according to http://www.cons.org/cracauer/sigint.html+ * New `delegate_ctlc` field in `CreateProcess` for WCE handling+ * Use `ExitFailure (-signum)` on Unix when a proc is terminated due to+ a signal.+ * Deprecate `module System.Cmd`+ * On non-Windows, the child thread now comunicates any errors back+ to the parent thread via pipes.+ * Fix deadlocks in `readProcess` and `readProcessWithExitCode`
process.cabal view
@@ -1,10 +1,10 @@ name: process-version: 1.2.0.0--- GHC 7.6.1 released with 1.1.0.2+version: 1.2.1.0+-- NOTE: Don't forget to update ./changelog.md license: BSD3 license-file: LICENSE maintainer: libraries@haskell.org-bug-reports: http://ghc.haskell.org/trac/ghc/newticket?component=libraries/process+bug-reports: https://github.com/haskell/process/issues synopsis: Process libraries category: System build-type: Configure@@ -14,7 +14,7 @@ extra-source-files: aclocal.m4- changelog+ changelog.md configure configure.ac include/HsProcessConfig.h.in@@ -28,12 +28,7 @@ source-repository head type: git- location: http://git.haskell.org/packages/process.git--source-repository this- type: git- location: http://git.haskell.org/packages/process.git- tag: process-1.2.0.0-release+ location: https://github.com/haskell/process.git library default-language: Haskell2010@@ -43,6 +38,8 @@ InterruptibleFFI RecordWildCards Trustworthy+ if impl(ghc>=7.9)+ other-extensions: Safe exposed-modules: System.Cmd@@ -62,10 +59,10 @@ ghc-options: -Wall - build-depends: base >= 4.4 && < 4.8,+ build-depends: base >= 4.4 && < 4.9, directory >= 1.1 && < 1.3, filepath >= 1.2 && < 1.4,- deepseq >= 1.1 && < 1.4+ deepseq >= 1.1 && < 1.5 if os(windows) build-depends: Win32 >=2.2 && < 2.4 extra-libraries: kernel32