-- 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, killThread)
import qualified Control.Exception as E
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
}
-- | popen lets you run a binary with specified arguments. This bypasses the shell.
-- | It'll also terminate (SIGTERM) the spawned process in case of
-- | exception, this is very important if the timeout for a Plugin
-- | expires while it is waiting for the result of a looping process.
-- | It's fundamental to link the final executable with -threaded.
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 =
E.handle (\(E.SomeException e) -> return ([],show e,error (show e))) $
E.bracketOnError (runInteractiveProcess file args Nothing Nothing) (\(_,_,_,pid) -> terminateProcess pid) $
\(inp,out,err,pid) -> do
case minput of
Just input -> hPutStr inp input >> E.catch (hClose inp)
(\(E.SomeException _) -> return ())
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
E.bracketOnError (do t1 <- forkIO (E.evaluate (length output) >> putMVar outMVar ())
t2 <- forkIO (E.evaluate (length errput) >> putMVar errMVar ())
return (t1,t2))
(\(t1,t2) -> killThread t1 >> killThread t2 )
(\_ -> 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 <- waitForProcess pid
return (output,errput,e)