-- Copyright (c) 2004-6 Don Stewart - http://www.cse.unsw.edu.au/~dons
-- GPL version 2 or later (see http://www.gnu.org/copyleft/gpl.html)
-- | A Posix.popen compatibility mapping.
module Lambdabot.Process (popen, run) where
import System.Exit
import System.IO
import System.Process
import Control.Concurrent (forkIO, newEmptyMVar, putMVar, takeMVar)
import qualified Control.Exception
run :: FilePath -> String -> (String -> String) -> IO String
run binary src scrub = do
(out,err,_) <- popen binary [] (Just src)
let o = scrub out
e = scrub err
return $ case () of {_
| null o && null e -> "Done."
| null o -> e
| otherwise -> o
}
--
-- Ignoring exit status for now.
--
-- You have to ignore SIGPIPE, otherwise popening a non-existing executable
-- will result in an attempt to write to a closed pipe and crash the wholw
-- program.
--
-- XXX there are still issues. Large amounts of output can cause what
-- seems to be a dead lock on the pipe write from runplugs, for example.
-- Posix.popen doesn't have this problem, so maybe we can reproduce its
-- pipe handling somehow.
-- | popen lets you run a binary with specified arguments. This bypasses the shell.
popen :: FilePath -- ^ The binary to execute
-> [String] -- ^ A list of arguments to pass to the binary. No need to
-- space separate them
-> Maybe String -- ^ stdin
-> IO (String,String,ExitCode)
popen file args minput =
Control.Exception.handle (\e -> return ([],show e,error (show e))) $ do
(inp,out,err,pid) <- runInteractiveProcess file args Nothing Nothing
case minput of
Just input -> hPutStr inp input >> hClose inp -- importante!
Nothing -> return ()
-- Now, grab the input
output <- hGetContents out
errput <- hGetContents err
-- SimonM sez:
-- ... avoids blocking the main thread, but ensures that all the
-- data gets pulled as it becomes available. you have to force the
-- output strings before waiting for the process to terminate.
--
-- Samb says:
-- Might as well try to avoid hanging my system...
-- make sure it happens FIRST.
outMVar <- newEmptyMVar
errMVar <- newEmptyMVar
forkIO (Control.Exception.evaluate (length output) >> putMVar outMVar ())
forkIO (Control.Exception.evaluate (length errput) >> putMVar errMVar ())
takeMVar outMVar
takeMVar errMVar
-- And now we wait. We must wait after we read, unsurprisingly.
-- blocks without -threaded, you're warned.
-- and maybe the process has already completed..
e <- Control.Exception.catch (waitForProcess pid) (\_ -> return ExitSuccess)
return (output,errput,e)