module Process (
Process(..),
ProcName(..),
textProcName, unpackProcName,
send,
outProc,
lineReadingLoop,
spawnOn,
closeProcess,
withProcess,
) where
import Control.Arrow
import Control.Concurrent
import Control.Concurrent.STM
import Control.Monad
import Control.Monad.Except
import Control.Monad.Reader
import Data.Function
import Data.Text (Text)
import qualified Data.Text as T
import qualified Data.Text.IO as T
import System.Exit
import System.IO
import System.IO.Error
import System.Posix.Signals
import System.Process
import {-# SOURCE #-} GDB
import Network
import Network.Ip
import Output
import Run.Monad
import Script.Expr.Class
data Process = Process
{ procName :: ProcName
, procHandle :: ProcessHandle
, procStdin :: Handle
, procOutput :: TVar [Text]
, procKillWith :: Maybe Signal
, procNode :: Node
}
instance Eq Process where
(==) = (==) `on` procStdin
instance ExprType Process where
textExprType _ = T.pack "proc"
textExprValue n = T.pack "p:" <> textProcName (procName n)
recordMembers = map (first T.pack)
[ ("node", RecordSelector $ procNode)
]
data ProcName = ProcName Text
| ProcNameTcpdump
| ProcNameGDB
deriving (Eq, Ord)
textProcName :: ProcName -> Text
textProcName (ProcName name) = name
textProcName ProcNameTcpdump = T.pack "tcpdump"
textProcName ProcNameGDB = T.pack "gdb"
unpackProcName :: ProcName -> String
unpackProcName = T.unpack . textProcName
send :: MonadIO m => Process -> Text -> m ()
send p line = liftIO $ do
T.hPutStrLn (procStdin p) line
hFlush (procStdin p)
outProc :: MonadOutput m => OutputType -> Process -> Text -> m ()
outProc otype p line = outLine otype (Just $ textProcName $ procName p) line
lineReadingLoop :: MonadOutput m => Process -> Handle -> (Text -> m ()) -> m ()
lineReadingLoop process h act =
liftIO (tryIOError (T.hGetLine h)) >>= \case
Left err
| isEOFError err -> return ()
| otherwise -> outProc OutputChildFail process $ T.pack $ "IO error: " ++ show err
Right line -> do
act line
lineReadingLoop process h act
spawnOn :: Either Network Node -> ProcName -> Maybe Signal -> String -> TestRun Process
spawnOn target pname killWith cmd = do
let netns = either getNetns getNetns target
let prefix = T.unpack $ "ip netns exec \"" <> textNetnsName netns <> "\" "
(Just hin, Just hout, Just herr, handle) <- liftIO $ createProcess (shell $ prefix ++ cmd)
{ std_in = CreatePipe, std_out = CreatePipe, std_err = CreatePipe
, cwd = Just (either netDir nodeDir target)
, env = Just [ ( "EREBOS_DIR", "." ) ]
}
pout <- liftIO $ newTVarIO []
let process = Process
{ procName = pname
, procHandle = handle
, procStdin = hin
, procOutput = pout
, procKillWith = killWith
, procNode = either (const undefined) id target
}
forkTest $ lineReadingLoop process hout $ \line -> do
outProc OutputChildStdout process line
liftIO $ atomically $ modifyTVar pout (++[line])
forkTest $ lineReadingLoop process herr $ \line -> do
case pname of
ProcNameTcpdump -> return ()
_ -> outProc OutputChildStderr process line
asks (teGDB . fst) >>= maybe (return Nothing) (liftIO . tryReadMVar) >>= \case
Just gdb | ProcName _ <- pname -> addInferior gdb process
_ -> return ()
return process
closeProcess :: (MonadIO m, MonadOutput m, MonadError Failed m) => Process -> m ()
closeProcess p = do
liftIO $ hClose $ procStdin p
case procKillWith p of
Nothing -> return ()
Just sig -> liftIO $ getPid (procHandle p) >>= \case
Nothing -> return ()
Just pid -> signalProcess sig pid
liftIO $ void $ forkIO $ do
threadDelay 1000000
terminateProcess $ procHandle p
liftIO (waitForProcess (procHandle p)) >>= \case
ExitSuccess -> return ()
ExitFailure code -> do
outProc OutputChildFail p $ T.pack $ "exit code: " ++ show code
throwError Failed
withProcess :: Either Network Node -> ProcName -> Maybe Signal -> String -> (Process -> TestRun a) -> TestRun a
withProcess target pname killWith cmd inner = do
procVar <- asks $ teProcesses . fst
process <- spawnOn target pname killWith cmd
liftIO $ modifyMVar_ procVar $ return . (process:)
inner process `finally` do
ps <- liftIO $ takeMVar procVar
closeProcess process `finally` do
liftIO $ putMVar procVar $ filter (/=process) ps