packages feed

unix-process-conduit 0.2.0.2 → 0.2.1

raw patch · 5 files changed

+575/−66 lines, 5 filesdep +directorydep +filepathdep +stm

Dependencies added: directory, filepath, stm, time

Files

+ Data/Conduit/LogFile.hs view
@@ -0,0 +1,141 @@+{-# LANGUAGE OverloadedStrings #-}+{-# LANGUAGE TemplateHaskell   #-}+module Data.Conduit.LogFile+    ( RotatingLog+    , openRotatingLog+    , addChunk+    , close+    , defaultMaxTotal+    , dummy+    ) where++import           Control.Concurrent             (forkIO)+import           Control.Concurrent.STM         (atomically)+import           Control.Concurrent.STM.TBQueue+import           Control.Concurrent.STM.TVar+import           Control.Exception              (bracket, bracketOnError,+                                                 finally)+import           Control.Monad                  (void, when)+import qualified Data.ByteString                as S+import           Data.Time                      (UTCTime, getCurrentTime)+import           Data.Word                      (Word)+import           System.Directory               (createDirectoryIfMissing,+                                                 doesFileExist, renameFile)+import           System.FilePath                ((<.>), (</>))+import qualified System.IO                      as SIO+import           System.IO.Unsafe               (unsafePerformIO)+import           System.Mem.Weak                (addFinalizer)++data Command = AddChunk !S.ByteString+             | Close++-- | Represents a folder used for totating log files.+--+-- Since 0.2.1+data RotatingLog = RotatingLog !(TVar State)+-- Use a data instead of a newtype so that we can attach a finalizer.++-- | A @RotatingLog@ which performs no logging.+--+-- Since 0.2.1+dummy :: RotatingLog+dummy = RotatingLog $! unsafePerformIO $! newTVarIO Closed++data State = Closed+           | Running !SIO.Handle !(TBQueue Command)++queue :: Command -> RotatingLog -> IO ()+queue cmd (RotatingLog ts) = atomically $ do+    s <- readTVar ts+    case s of+        Closed -> return ()+        Running _ q -> writeTBQueue q cmd++addChunk :: RotatingLog -> S.ByteString -> IO ()+addChunk lf bs = queue (AddChunk bs) lf++close :: RotatingLog -> IO ()+close = queue Close++-- | Create a new @RotatingLog@.+--+-- Since 0.2.1+openRotatingLog :: FilePath -- ^ folder to contain logs+                -> Word -- ^ maximum log file size, in bytes+                -> IO RotatingLog+openRotatingLog dir maxTotal = do+    createDirectoryIfMissing True dir+    bracketOnError (moveCurrent dir) SIO.hClose $ \handle -> do+        queue <- newTBQueueIO 5+        let s = Running handle queue+        ts <- newTVarIO s+        void $ forkIO $ loop dir ts maxTotal+        let rl = RotatingLog ts+        addFinalizer rl (atomically (writeTBQueue queue Close))+        return rl++current :: FilePath -- ^ folder containing logs+        -> FilePath+current = (</> "current.log")++moveCurrent :: FilePath -- ^ folder containing logs+            -> IO SIO.Handle -- ^ new handle+moveCurrent dir = do+    let curr = current dir+    x <- doesFileExist curr+    when x $ do+        now <- getCurrentTime+        renameFile curr $ dir </> suffix now+    SIO.openFile curr SIO.WriteMode++suffix :: UTCTime -> FilePath+suffix now =+    (concatMap fix $ takeWhile (/= '.') $ show now) <.> "log"+  where+    fix ' ' = "_"+    fix c | '0' <= c && c <= '9' = [c]+    fix _ = ""++loop :: FilePath -- ^ folder containing logs+     -> TVar State+     -> Word -- ^ maximum total log size+     -> IO ()+loop dir ts maxTotal =+    go 0 `finally` (closeCurrentHandle `finally` atomically (writeTVar ts Closed))+  where+    closeCurrentHandle = bracket+        (atomically $ do+            s <- readTVar ts+            case s of+                Closed -> return Nothing+                Running h _ -> return $! Just h)+        (maybe (return ()) SIO.hClose)+        (const $ return ())++    go total = do+        res <- atomically $ do+            s <- readTVar ts+            case s of+                Closed -> return Nothing+                Running handle queue -> do+                    cmd <- readTBQueue queue+                    case cmd of+                        Close -> return Nothing+                        AddChunk bs -> return $! Just (handle, queue, bs)+        case res of+            Nothing -> return ()+            Just (handle, queue, bs) -> do+                let total' = total + fromIntegral (S.length bs)+                S.hPut handle bs+                SIO.hFlush handle+                if total' > maxTotal+                    then do+                        bracket+                            (SIO.hClose handle >> moveCurrent dir)+                            (\handle' -> atomically $ writeTVar ts $ Running handle' queue)+                            (const $ return ())+                        go 0+                    else go total'++defaultMaxTotal :: Word+defaultMaxTotal = 5 * 1024 * 1024 -- 5 MB
Data/Conduit/Process/Unix.hs view
@@ -1,49 +1,96 @@-{-# LANGUAGE ForeignFunctionInterface, OverloadedStrings, ScopedTypeVariables #-}+{-# LANGUAGE DeriveDataTypeable       #-}+{-# LANGUAGE ForeignFunctionInterface #-}+{-# LANGUAGE OverloadedStrings        #-}+{-# LANGUAGE ScopedTypeVariables      #-} module Data.Conduit.Process.Unix-    ( forkExecuteFile+    ( -- * Starting processes+      forkExecuteFile     , killProcess     , terminateProcess     , waitForProcess     , ProcessStatus (..)     , signalProcessHandle     , signalProcessHandleGroup+      -- * Process tracking+      -- $processTracker++      -- ** Types+    , ProcessTracker+    , TrackedProcess+    , ProcessTrackerException (..)+      -- ** Functions+    , initProcessTracker+    , trackProcess+    , untrackProcess++      -- * Logging+    , RotatingLog+    , openRotatingLog+    , forkExecuteLog++      -- * Monitored process+    , MonitoredProcess+    , monitorProcess+    , terminateMonitoredProcess     ) where -import           Control.Applicative               ((<$>), (<*>))-import           Control.Arrow                     ((***))-import           Control.Concurrent                (forkIO)-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)-import           Foreign.Storable                  (sizeOf, pokeElemOff)-import           Prelude                           (Bool (..), IO, Maybe (..),-                                                    Monad (..), flip,-                                                    fromIntegral, fst, maybe,-                                                    snd, ($), (.), (==), error, id, length, (*),-                                                    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, 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+import           Control.Applicative             ((<$>), (<*>))+import           Control.Arrow                   ((***))+import           Control.Concurrent              (forkIO)+import           Control.Concurrent              (threadDelay)+import           Control.Concurrent.MVar         (MVar, modifyMVar, newMVar,+                                                  readMVar, swapMVar)+import           Control.Exception               (Exception, SomeException,+                                                  bracketOnError, finally,+                                                  handle, mask, mask_,+                                                  onException, throwIO, try)+import           Control.Monad                   (unless, void, when, zipWithM_)+import           Control.Monad.Trans.Class       (lift)+import           Data.ByteString                 (ByteString, append, concat,+                                                  null, singleton)+import qualified Data.ByteString.Char8           as S8+import           Data.ByteString.Unsafe          (unsafePackCStringFinalizer,+                                                  unsafeUseAsCString,+                                                  unsafeUseAsCStringLen)+import           Data.Conduit                    (Sink, Source, yield, ($$))+import           Data.Conduit.Binary             (sinkHandle, sourceHandle)+import           Data.Conduit.List               (mapM_)+import qualified Data.Conduit.List               as CL+import           Data.Conduit.LogFile+import           Data.IORef                      (IORef, newIORef, readIORef,+                                                  writeIORef)+import           Data.Time                       (getCurrentTime)+import           Data.Time                       (diffUTCTime)+import           Data.Typeable                   (Typeable)+import           Foreign.C.String+import           Foreign.C.Types+import           Foreign.Marshal.Alloc           (allocaBytes, free,+                                                  mallocBytes)+import           Foreign.Ptr                     (Ptr, castPtr, nullPtr)+import           Foreign.Storable                (pokeElemOff, sizeOf)+import           Prelude                         (Bool (..), Either (..), IO,+                                                  Maybe (..), Monad (..), Show,+                                                  const, error, flip, fmap,+                                                  fromIntegral, fst, head, id,+                                                  length, map, maybe, show, snd,+                                                  ($), ($!), (*), (.), (<),+                                                  (==))+import           System.IO                       (hClose)+import           System.Posix.IO.ByteString      (FdOption (CloseOnExec),+                                                  closeFd, createPipe,+                                                  fdReadBuf, fdToHandle,+                                                  fdWriteBuf, setFdOption)+import           System.Posix.Process.ByteString (ProcessStatus (..),+                                                  getProcessGroupIDOf,+                                                  getProcessStatus)+import           System.Posix.Signals            (Signal, sigKILL,+                                                  signalProcess,+                                                  signalProcessGroup)+import           System.Posix.Types              (CPid (..), Fd, ProcessID)+import           System.Process+import           System.Process.Internals        (ProcessHandle (..),+                                                  ProcessHandle__ (..),+                                                  withProcessHandle_)  -- | Kill a process by sending it the KILL (9) signal. --@@ -109,7 +156,6 @@         Nothing -> return ()     return ph   where-    ignoreExceptions = handle (\(_ :: SomeException) -> return ())     cp = CreateProcess         { cmdspec = RawCommand (S8.unpack cmd) (map S8.unpack args)         , cwd = S8.unpack <$> mwdir@@ -121,6 +167,9 @@         , create_group = True         } +ignoreExceptions :: IO () -> IO ()+ignoreExceptions = handle (\(_ :: SomeException) -> return ())+ closeOnExec :: Fd -> IO () closeOnExec fd = setFdOption fd CloseOnExec True @@ -146,3 +195,226 @@      run ptrs = allocaBytes (length ptrs * sizeOf (head ptrs)) $ \res ->         zipWithM_ (pokeElemOff res) [0..] ptrs >> f res++-- $processTracker+--+-- Ensure that child processes are killed, regardless of how the parent process exits.+--+-- The technique used here is:+--+-- * Create a pipe.+--+-- * Fork a new child process that listens on the pipe.+--+-- * In the current process, send updates about processes that should be auto-killed.+--+-- * When the parent process dies, listening on the pipe in the child process will get an EOF.+--+-- * When the child process receives that EOF, it kills all processes it was told to auto-kill.+--+-- This code was originally written for Keter, but was moved to unix-process+-- conduit in the 0.2.1 release.++foreign import ccall unsafe "launch_process_tracker"+    c_launch_process_tracker :: IO CInt++foreign import ccall unsafe "track_process"+    c_track_process :: ProcessTracker -> CPid -> CInt -> IO ()++-- | Represents the child process which handles process cleanup.+--+-- Since 0.2.1+newtype ProcessTracker = ProcessTracker CInt++-- | Represents a child process which is currently being tracked by the cleanup+-- child process.+--+-- Since 0.2.1+data TrackedProcess = TrackedProcess !ProcessTracker !(IORef MaybePid)++data MaybePid = NoPid | Pid !CPid++-- | Fork off the child cleanup process.+--+-- This will ideally only be run once for your entire application.+--+-- Since 0.2.1+initProcessTracker :: IO ProcessTracker+initProcessTracker = do+    i <- c_launch_process_tracker+    if i == -1+        then throwIO CannotLaunchProcessTracker+        else return $! ProcessTracker i++-- | Since 0.2.1+data ProcessTrackerException = CannotLaunchProcessTracker+    deriving (Show, Typeable)+instance Exception ProcessTrackerException++-- | Begin tracking the given process. If the 'ProcessHandle' refers to a+-- closed process, no tracking will occur. If the process is closed, then it+-- will be untracked automatically.+--+-- Note that you /must/ compile your program with @-threaded@; see+-- 'waitForProcess'.+--+-- Since 0.2.1+trackProcess :: ProcessTracker -> ProcessHandle -> IO TrackedProcess+trackProcess pt ph@(ProcessHandle mph) = mask_ $ do+    mpid <- readMVar mph+    mpid' <- case mpid of+        ClosedHandle{} -> return NoPid+        OpenHandle pid -> do+            c_track_process pt pid 1+            return $ Pid pid+    ipid <- newIORef mpid'+    let tp = TrackedProcess pt ipid+    case mpid' of+        NoPid -> return ()+        Pid _ -> void $ forkIO $ do+            void $ waitForProcess ph+            untrackProcess tp+    return $! tp++-- | Explicitly remove the given process from the tracked process list in the+-- cleanup process.+--+-- Since 0.2.1+untrackProcess :: TrackedProcess -> IO ()+untrackProcess (TrackedProcess pt ipid) = mask_ $ do+    mpid <- readIORef ipid+    case mpid of+        NoPid -> return ()+        Pid pid -> do+            c_track_process pt pid 0+            writeIORef ipid NoPid++-- | Fork and execute a subprocess, sending stdout and stderr to the specified+-- rotating log.+--+-- Since 0.2.1+forkExecuteLog :: ByteString -- ^ command+               -> [ByteString] -- ^ args+               -> Maybe [(ByteString, ByteString)] -- ^ environment+               -> Maybe ByteString -- ^ working directory+               -> Maybe (Source IO ByteString) -- ^ stdin+               -> RotatingLog -- ^ both stdout and stderr will be sent to this location+               -> IO ProcessHandle+forkExecuteLog cmd args menv mwdir mstdin rlog = bracketOnError+    setupPipe+    cleanupPipes+    usePipes+  where+    setupPipe = bracketOnError+        createPipe+        (\(x, y) -> closeFd x `finally` closeFd y)+        (\(x, y) -> (,) <$> fdToHandle x <*> fdToHandle y)+    cleanupPipes (x, y) = hClose x `finally` hClose y++    usePipes pipes@(readerH, writerH) = do+        (min, _, _, ph) <- createProcess 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 = UseHandle writerH+            , std_err = UseHandle writerH+            , close_fds = True+            , create_group = True+            }+        ignoreExceptions $ addAttachMessage pipes ph+        void $ forkIO $ ignoreExceptions $+            (sourceHandle readerH $$ CL.mapM_ (addChunk rlog)) `finally` hClose readerH+        case (min, mstdin) of+            (Just h, Just source) -> void $ forkIO $ ignoreExceptions $+                (source $$ sinkHandle h) `finally` hClose h+            (Nothing, Nothing) -> return ()+            _ -> error $ "Invariant violated: Data.Conduit.Process.Unix.forkExecuteLog"+        return ph++    addAttachMessage pipes ph = withProcessHandle_ ph $ \p_ -> do+        now <- getCurrentTime+        case p_ of+            ClosedHandle ec -> do+                addChunk rlog $ S8.concat+                    [ "\n\n"+                    , S8.pack $ show now+                    , ": Process immediately died with exit code "+                    , S8.pack $ show ec+                    , "\n\n"+                    ]+                cleanupPipes pipes+            OpenHandle h -> do+                addChunk rlog $ S8.concat+                    [ "\n\n"+                    , S8.pack $ show now+                    , ": Attached new process "+                    , S8.pack $ show h+                    , "\n\n"+                    ]+        return p_++data Status = NeedsRestart | NoRestart | Running ProcessHandle++-- | Run the given command, restarting if the process dies.+monitorProcess+    :: (ByteString -> IO ()) -- ^ log+    -> ProcessTracker+    -> Maybe S8.ByteString -- ^ setuid+    -> S8.ByteString -- ^ executable+    -> S8.ByteString -- ^ working directory+    -> [S8.ByteString] -- ^ command line parameter+    -> [(S8.ByteString, S8.ByteString)] -- ^ environment+    -> RotatingLog+    -> IO MonitoredProcess+monitorProcess log processTracker msetuid exec dir args env rlog = do+    mstatus <- newMVar NeedsRestart+    let loop mlast = do+            next <- modifyMVar mstatus $ \status ->+                case status of+                    NoRestart -> return (NoRestart, return ())+                    _ -> do+                        now <- getCurrentTime+                        case mlast of+                            Just last | diffUTCTime now last < 5 -> do+                                log $ "Process restarting too quickly, waiting before trying again: " `S8.append` exec+                                threadDelay $ 5 * 1000 * 1000+                            _ -> return ()+                        let (cmd, args') =+                                case msetuid of+                                    Nothing -> (exec, args)+                                    Just setuid -> ("sudo", "-E" : "-u" : setuid : "--" : exec : args)+                        res <- try $ forkExecuteLog+                            cmd+                            args'+                            (Just env)+                            (Just dir)+                            (Just $ return ())+                            rlog+                        case res of+                            Left e -> do+                                log $ "Data.Conduit.Process.Unix.monitorProcess: " `S8.append` S8.pack (show (e :: SomeException))+                                return (NeedsRestart, return ())+                            Right pid -> do+                                log $ "Process created: " `S8.append` exec+                                return (Running pid, do+                                    void $ trackProcess processTracker pid+                                    void $ waitForProcess pid+                                    loop (Just now))+            next+    forkIO $ loop Nothing+    return $ MonitoredProcess mstatus++-- | Abstract type containing information on a process which will be restarted.+newtype MonitoredProcess = MonitoredProcess (MVar Status)++-- | Terminate the process and prevent it from being restarted.+terminateMonitoredProcess :: MonitoredProcess -> IO ()+terminateMonitoredProcess (MonitoredProcess mstatus) = do+    status <- swapMVar mstatus NoRestart+    case status of+        Running pid -> do+            terminateProcess pid+            threadDelay 1000000+            killProcess pid+        _ -> return ()
LICENSE view
@@ -1,30 +1,20 @@-Copyright (c)2011, Michael Snoyman--All rights reserved.--Redistribution and use in source and binary forms, with or without-modification, are permitted provided that the following conditions are met:--    * Redistributions of source code must retain the above copyright-      notice, this list of conditions and the following disclaimer.+Copyright (c) 2012 Michael Snoyman, http://www.yesodweb.com/ -    * Redistributions in binary form must reproduce the above-      copyright notice, this list of conditions and the following-      disclaimer in the documentation and/or other materials provided-      with the distribution.+Permission is hereby granted, free of charge, to any person obtaining+a copy of this software and associated documentation files (the+"Software"), to deal in the Software without restriction, including+without limitation the rights to use, copy, modify, merge, publish,+distribute, sublicense, and/or sell copies of the Software, and to+permit persons to whom the Software is furnished to do so, subject to+the following conditions: -    * Neither the name of Michael Snoyman nor the names of other-      contributors may be used to endorse or promote products derived-      from this software without specific prior written permission.+The above copyright notice and this permission notice shall be+included in all copies or substantial portions of the Software. -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ cbits/process-tracker.c view
@@ -0,0 +1,100 @@+#include <unistd.h>+#include <stdlib.h>+#include <stdio.h>+#include <signal.h>++struct node {+    pid_t pid;+    struct node *next;+};++static struct node * add_node(pid_t pid, struct node *n) {+    struct node *n2 = malloc(sizeof(struct node));+    n2->pid = pid;+    n2->next = n;+    return n2;+}++static struct node * remove_node(pid_t pid, struct node *n) {+    if (!n) {+        return n;+    }+    else if (n->pid == pid) {+        struct node *n2 = n->next;+        free(n);+        return remove_node(pid, n2);+    }+    else {+        n->next = remove_node(pid, n->next);+        return n;+    }+}++extern void track_process(int fd, pid_t pid, int b) {+    unsigned int buffer[2];++    //printf("Tracking process %d, %d\n", pid, b);++    buffer[0] = pid;+    buffer[1] = b;+    if (! write(fd, buffer, sizeof(unsigned int) * 2)) {+        //printf("Error writing to fd %d\n", fd);+    }+}++// Returns FD to write to, or -1 on failure.+extern int launch_process_tracker(void) {+    int pipes[2];+    pid_t child;++    if (pipe(pipes)) {+        return -1;+    }++    child = fork();++    if (child < 0) {+        return -1;+    }+    else if (child == 0) {+        unsigned int buffer[2];+        struct node *n = 0, *n2;++        close(pipes[1]);++        // Prevent monitoring programs like Upstart from killing this+        // new process along with the parent+        setpgid(0, 0);++        while (read(pipes[0], buffer, sizeof(unsigned int) * 2) > 0) {+            if (buffer[1]) {+                //printf("Adding node %d\n", buffer[0]);+                n = add_node(buffer[0], n);+            }+            else {+                //printf("Removing node %d\n", buffer[0]);+                n = remove_node(buffer[0], n);+            }+        }++        for (n2 = n; n2; n2 = n2->next) {+            //printf("Sending process %d TERM signal\n", n2->pid);+            kill(n2->pid, SIGTERM);+        }++        sleep(2);++        while (n) {+            //printf("Sending process %d KILL signal\n", n->pid);+            kill(n2->pid, SIGKILL);++            n2 = n;+            n = n->next;+            free(n2);+        }+    }+    else {+        close(pipes[0]);+        return pipes[1];+    }+}
unix-process-conduit.cabal view
@@ -2,7 +2,7 @@ --  documentation, see http://haskell.org/cabal/users-guide/  name:                unix-process-conduit-version:             0.2.0.2+version:             0.2.1 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@@ -16,12 +16,18 @@  library   exposed-modules:     Data.Conduit.Process.Unix+                       Data.Conduit.LogFile   build-depends:       base              >= 4         && < 5                      , transformers                      , bytestring                      , conduit           >= 0.5       && < 1.1                      , unix              >= 2.5                      , process           >= 1.1+                     , time+                     , filepath+                     , stm+                     , directory+  c-sources:           cbits/process-tracker.c  test-suite test     hs-source-dirs: test