unix-process-conduit 0.1.0.1 → 0.2.0
raw patch · 4 files changed
+86/−148 lines, 4 filesdep +processdep ~unixPVP ok
version bump matches the API change (PVP)
Dependencies added: process
Dependency ranges changed: unix
API changes (from Hackage documentation)
+ Data.Conduit.Process.Unix: signalProcessHandle :: Signal -> ProcessHandle -> IO ()
+ Data.Conduit.Process.Unix: signalProcessHandleGroup :: Signal -> ProcessHandle -> IO ()
+ Data.Conduit.Process.Unix: terminateProcess :: ProcessHandle -> IO ()
- Data.Conduit.Process.Unix: forkExecuteFile :: ByteString -> Bool -> [ByteString] -> Maybe [(ByteString, ByteString)] -> Maybe ByteString -> Maybe (Source IO ByteString) -> Maybe (Sink ByteString IO ()) -> Maybe (Sink ByteString IO ()) -> IO ProcessID
+ Data.Conduit.Process.Unix: forkExecuteFile :: ByteString -> [ByteString] -> Maybe [(ByteString, ByteString)] -> Maybe ByteString -> Maybe (Source IO ByteString) -> Maybe (Sink ByteString IO ()) -> Maybe (Sink ByteString IO ()) -> IO ProcessHandle
- Data.Conduit.Process.Unix: killProcess :: ProcessID -> IO ()
+ Data.Conduit.Process.Unix: killProcess :: ProcessHandle -> IO ()
- Data.Conduit.Process.Unix: waitForProcess :: ProcessID -> IO ProcessStatus
+ Data.Conduit.Process.Unix: waitForProcess :: ProcessHandle -> IO ExitCode
Files
- Data/Conduit/Process/Unix.hs +65/−72
- cbits/forkExecute.c +0/−67
- test/Data/Conduit/Process/UnixSpec.hs +18/−7
- unix-process-conduit.cabal +3/−2
Data/Conduit/Process/Unix.hs view
@@ -1,20 +1,27 @@-{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings #-}+{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings, ScopedTypeVariables #-} module Data.Conduit.Process.Unix ( forkExecuteFile , killProcess+ , terminateProcess , waitForProcess , ProcessStatus (..)+ , signalProcessHandle+ , signalProcessHandleGroup ) where +import Control.Applicative ((<$>), (<*>))+import Control.Arrow ((***)) import Control.Concurrent (forkIO)-import Control.Exception (finally, mask, onException)+import Control.Exception (finally, mask, onException, handle, SomeException) import Control.Monad (unless, void, zipWithM_, when) import Control.Monad.Trans.Class (lift) import Data.ByteString (ByteString, null, concat, append, singleton)+import qualified Data.ByteString.Char8 as S8 import Data.ByteString.Unsafe (unsafePackCStringFinalizer, unsafeUseAsCStringLen, unsafeUseAsCString) import Data.Conduit (Sink, Source, yield, ($$))+import Data.Conduit.Binary (sinkHandle, sourceHandle) import Data.Conduit.List (mapM_) import Foreign.Marshal.Alloc (free, mallocBytes, allocaBytes) import Foreign.Ptr (castPtr, Ptr, nullPtr)@@ -23,34 +30,49 @@ Monad (..), flip, fromIntegral, fst, maybe, snd, ($), (.), (==), error, id, length, (*),- head, map)+ head, map, const, fmap) import System.Posix.Types (Fd) import System.Posix.IO.ByteString (closeFd, createPipe, fdReadBuf, fdWriteBuf, setFdOption, FdOption (CloseOnExec)) import System.Posix.Process.ByteString (ProcessStatus (..),- getProcessStatus)-import System.Posix.Signals (sigKILL, signalProcess)+ getProcessStatus, getProcessGroupIDOf)+import System.Posix.Signals (sigKILL, signalProcess, Signal, signalProcessGroup) import System.Posix.Types (ProcessID)+import System.IO (hClose) import Foreign.C.Types import Foreign.C.String+import System.Process+import System.Process.Internals -- | Kill a process by sending it the KILL (9) signal. -- -- Since 0.1.0-killProcess :: ProcessID -> IO ()-killProcess = signalProcess sigKILL+killProcess :: ProcessHandle -> IO ()+killProcess ph = withProcessHandle_ ph $ \p_ ->+ case p_ of+ ClosedHandle _ -> return p_+ OpenHandle h -> do+ signalProcess sigKILL h+ return p_ -foreign import ccall "forkExecuteFile"- c_forkExecuteFile :: Ptr CString- -> CInt- -> CString- -> Ptr CString- -> CInt- -> CInt- -> CInt- -> IO CInt+signalProcessHandle :: Signal -> ProcessHandle -> IO ()+signalProcessHandle signal ph = withProcessHandle_ ph $ \p_ ->+ case p_ of+ ClosedHandle _ -> return p_+ OpenHandle h -> do+ signalProcess signal h+ return p_ +signalProcessHandleGroup :: Signal -> ProcessHandle -> IO ()+signalProcessHandleGroup signal ph = withProcessHandle_ ph $ \p_ ->+ case p_ of+ ClosedHandle _ -> return p_+ OpenHandle h -> do+ pgid <- getProcessGroupIDOf h+ signalProcessGroup signal pgid+ return p_+ -- | Fork a new process and execute the given command. -- -- This is a wrapper around with fork() and exec*() syscalls, set up to work@@ -64,60 +86,40 @@ -- -- Since 0.1.0 forkExecuteFile :: ByteString -- ^ command- -> Bool -- ^ search on PATH? -> [ByteString] -- ^ args -> Maybe [(ByteString, ByteString)] -- ^ environment -> Maybe ByteString -- ^ working directory -> Maybe (Source IO ByteString) -- ^ stdin -> Maybe (Sink ByteString IO ()) -- ^ stdout -> Maybe (Sink ByteString IO ()) -- ^ stderr- -> IO ProcessID-forkExecuteFile cmd path args menv mwdir mstdin mstdout mstderr = do- min <- withIn mstdin- mout <- withOut mstdout- merr <- withOut mstderr-- maybe (return ()) (closeOnExec . snd) min- maybe (return ()) (closeOnExec . fst) mout- maybe (return ()) (closeOnExec . fst) merr-- pid <- withArgs (cmd : args) $ \args' ->- withMString mwdir $ \mwdir' ->- withEnv menv $ \menv' ->- c_forkExecuteFile- args'- (if path then 1 else 0)- mwdir'- menv'- (maybe (-1) (fromIntegral . fst) min)- (maybe (-1) (fromIntegral . snd) mout)- (maybe (-1) (fromIntegral . snd) merr)- when (pid == -1) $ error "Failure with forkExecuteFile"- maybe (return ()) (closeFd . fst) min- maybe (return ()) (closeFd . snd) mout- maybe (return ()) (closeFd . snd) merr- return $ fromIntegral pid+ -> IO ProcessHandle+forkExecuteFile cmd args menv mwdir mstdin mstdout mstderr = do+ (min, mout, merr, ph) <- createProcess cp+ case (,) <$> mstdin <*> min of+ Just (source, h) -> void $ forkIO $ ignoreExceptions $+ (source $$ sinkHandle h) `finally` hClose h+ Nothing -> return ()+ case (,) <$> mstdout <*> mout of+ Just (sink, h) -> void $ forkIO $ ignoreExceptions $+ (sourceHandle h $$ sink) `finally` hClose h+ Nothing -> return ()+ case (,) <$> mstderr <*> merr of+ Just (sink, h) -> void $ forkIO $ ignoreExceptions $+ (sourceHandle h $$ sink) `finally` hClose h+ Nothing -> return ()+ return ph where- withIn Nothing = return Nothing- withIn (Just src) = do- (fdRead, fdWrite) <- createPipe- let sink = mapM_ $ flip unsafeUseAsCStringLen $ \(ptr, size) -> void $ fdWriteBuf fdWrite (castPtr ptr) (fromIntegral size)- void $ forkIO $ (src $$ sink) `finally` closeFd fdWrite- return $ Just (fdRead, fdWrite)- withOut Nothing = return Nothing- withOut (Just sink) = do- (fdRead, fdWrite) <- createPipe- let buffSize = 4096- let src = do- bs <- lift $ mask $ \restore -> do- ptr <- mallocBytes buffSize- bytesRead <- restore (fdReadBuf fdRead ptr $ fromIntegral buffSize) `onException` free ptr- unsafePackCStringFinalizer ptr (fromIntegral bytesRead) (free ptr)- unless (null bs) $ do- yield bs- src- void $ forkIO $ (src $$ sink) `finally` closeFd fdRead- return $ Just (fdRead, fdWrite)+ ignoreExceptions = handle (\(_ :: SomeException) -> return ())+ cp = CreateProcess+ { cmdspec = RawCommand (S8.unpack cmd) (map S8.unpack args)+ , cwd = S8.unpack <$> mwdir+ , env = map (S8.unpack *** S8.unpack) <$> menv+ , std_in = maybe Inherit (const CreatePipe) mstdin+ , std_out = maybe Inherit (const CreatePipe) mstdout+ , std_err = maybe Inherit (const CreatePipe) mstderr+ , close_fds = True+ , create_group = True+ } closeOnExec :: Fd -> IO () closeOnExec fd = setFdOption fd CloseOnExec True@@ -144,12 +146,3 @@ run ptrs = allocaBytes (length ptrs * sizeOf (head ptrs)) $ \res -> zipWithM_ (pokeElemOff res) [0..] ptrs >> f res---- | Wait until the given process has died, and return its @ProcessStatus@.------ Since 0.1.0-waitForProcess :: ProcessID -> IO ProcessStatus-waitForProcess pid =- loop- where- loop = getProcessStatus True False pid >>= maybe loop return
− cbits/forkExecute.c
@@ -1,67 +0,0 @@-#define _GNU_SOURCE--#include <unistd.h>--extern void blockUserSignals(void);-extern void unblockUserSignals(void);-extern void stopTimer(void);-extern void startTimer(void);--int forkExecuteFile- ( char *const args[]- , int path- , char *workingDirectory- , char **environment- , int fdStdIn- , int fdStdOut- , int fdStdErr- )-{- int pid;-- blockUserSignals();- stopTimer();-- switch (pid = fork()) {- case -1:- unblockUserSignals();- startTimer();- if (fdStdIn != -1) close(fdStdIn);- if (fdStdOut != -1) close(fdStdOut);- if (fdStdErr != -1) close(fdStdErr);- return -1;- case 0:- unblockUserSignals();- if (workingDirectory) {- if (chdir (workingDirectory) < 0) {- _exit(126);- }- }- if (fdStdIn != -1) {- dup2(fdStdIn, 0);- close(fdStdIn);- }- if (fdStdOut != -1) {- dup2(fdStdOut, 1);- close(fdStdOut);- }- if (fdStdErr != -1) {- dup2(fdStdErr, 2);- close(fdStdErr);- }-- // FIXME close file descriptors- if (environment) {- if (path) execvpe(args[0], args, environment);- else execve (args[0], args, environment);- } else {- if (path) execvp(args[0], args);- else execv (args[0], args);- }- _exit(127);- default: break;- }- unblockUserSignals();- startTimer();- return pid;-}
test/Data/Conduit/Process/UnixSpec.hs view
@@ -9,9 +9,10 @@ import Data.ByteString (ByteString) import qualified Data.IORef as I import qualified Data.Conduit.List as CL-import System.Exit (ExitCode (ExitSuccess))+import System.Exit (ExitCode (ExitSuccess, ExitFailure)) import Control.Monad.Trans.Class (lift) import Control.Concurrent (threadDelay)+import System.Posix.Signals (sigKILL, sigTERM) iorefSink :: IO (Sink ByteString IO (), IO L.ByteString) iorefSink = do@@ -33,7 +34,6 @@ (sink, getLBS) <- iorefSink pid <- forkExecuteFile "cat"- True [] Nothing Nothing@@ -42,13 +42,25 @@ Nothing res <- waitForProcess pid lbs <- getLBS- res `shouldBe` Exited ExitSuccess+ res `shouldBe` ExitSuccess lbs `shouldBe` expected+ it "terminateProcess works" $ do+ let src = lift (threadDelay 1000000) >> src+ pid <- forkExecuteFile+ "cat"+ []+ Nothing+ Nothing+ (Just src)+ Nothing+ Nothing+ terminateProcess pid+ res <- waitForProcess pid+ res `shouldBe` ExitFailure (fromIntegral sigTERM) it "killProcess works" $ do let src = lift (threadDelay 1000000) >> src pid <- forkExecuteFile "cat"- True [] Nothing Nothing@@ -57,12 +69,11 @@ Nothing killProcess pid res <- waitForProcess pid- res `shouldBe` Terminated 9+ res `shouldBe` ExitFailure (fromIntegral sigKILL) it "environment is set" $ do (sink, getLBS) <- iorefSink pid <- forkExecuteFile "env"- True [] (Just [("foo", S.take (read "3") $ "barbarbar")]) Nothing@@ -71,5 +82,5 @@ Nothing res <- waitForProcess pid lbs <- getLBS- res `shouldBe` Exited ExitSuccess+ res `shouldBe` ExitSuccess lbs `shouldBe` L.fromChunks ["foo=bar\n"]
unix-process-conduit.cabal view
@@ -2,7 +2,7 @@ -- documentation, see http://haskell.org/cabal/users-guide/ name: unix-process-conduit-version: 0.1.0.1+version: 0.2.0 synopsis: Run processes on Unix systems, with a conduit interface description: This library allows you to provide @conduit@ datatypes for the input and output streams. Note that you must compile your programs with @-threaded@. homepage: https://github.com/snoyberg/conduit@@ -21,7 +21,7 @@ , bytestring , conduit >= 0.5 && < 0.6 , unix >= 2.5- c-sources: cbits/forkExecute.c+ , process >= 1.1 test-suite test hs-source-dirs: test@@ -34,6 +34,7 @@ , conduit , bytestring , hspec >= 1.3+ , unix ghc-options: -Wall -threaded source-repository head