voyeur-0.1.0.1: src/System/Process/Voyeur.hs
{-# LANGUAGE TypeSynonymInstances #-}
-- | This package provides bindings to libvoyeur, a library for
-- observing the private activity of processes. Libvoyeur observes
-- a child process and all of its descendants, so it works even
-- when the child process calls out to other processes to do the
-- actual work.
--
-- To observe a process, use 'withVoyeur' to create an
-- 'FFI.VoyeurContext', then register handlers for the events you want
-- to observe using functions like 'FFI.observeExec'. When you've
-- set up all your handlers, use 'FFI.prepareEnvironment' to create a
-- special environment that will inject libvoyeur code into the
-- child process, and pass that environment to a function like
-- 'System.Process.runProcess'. Finally, pass the resulting 'ProcessHandle'
-- or 'ProcessID' to 'startObserving', and your handlers will be
-- called as events happen.
--
-- A simple program that prints a message every time a child
-- process opened a file might look like this:
--
-- > import Control.Monad
-- > import Data.Maybe
-- > import System.Environment
-- > import System.Process
-- > import System.Process.Voyeur
-- >
-- > main = do
-- > (program : args) <- getArgs
-- > withVoyeur $ \ctx -> do
-- > -- Set up a handler.
-- > observeOpen ctx defaultOpenFlags $
-- > \path _ _ _ _ pid -> putStrLn $ show pid ++ " opened " ++ show path
-- >
-- > -- Set up the environment.
-- > curEnv <- getEnvironment
-- > newEnv <- prepareEnvironment ctx curEnv
-- >
-- > when (isJust newEnv) $ do
-- > -- Start the child process.
-- > handle <- runProcess program args Nothing newEnv Nothing Nothing Nothing
-- >
-- > -- Observe it! startObserving only returns when the child process
-- > -- exits, so we don't need to wait.
-- > void $ startObserving ctx handle
--
-- A larger example program is included with the source code to this package.
module System.Process.Voyeur
(
-- * Observing a process
withVoyeur
, FFI.prepareEnvironment
, startObserving
-- * Observing \'exec\' calls
, FFI.ObserveExecFlags(..)
, FFI.defaultExecFlags
, FFI.ObserveExecHandler
, FFI.observeExec
-- * Observing \'exit\' calls
, FFI.ObserveExitHandler
, FFI.observeExit
-- * Observing \'open\' calls
, FFI.ObserveOpenFlags(..)
, FFI.defaultOpenFlags
, FFI.ObserveOpenHandler
, FFI.observeOpen
-- * Observing \'close\' calls
, FFI.ObserveCloseHandler
, FFI.observeClose
-- * Types
, FFI.VoyeurContext
, HasPid
) where
import Control.Concurrent.MVar (readMVar)
import Control.Exception (bracket)
import System.Exit (ExitCode(..))
import System.Posix.Types (ProcessID)
import System.Process.Internals (ProcessHandle(..), ProcessHandle__(..))
import Paths_voyeur (getDataFileName)
import qualified System.Process.Voyeur.FFI as FFI
-- | Creates a 'FFI.VoyeurContext' and runs an IO action that observes
-- a process using it.
withVoyeur :: (FFI.VoyeurContext -> IO a) -> IO a
withVoyeur = bracket initContext FFI.destroyContext
where
initContext = do
c <- FFI.createContext
rPath <- getDataFileName $ "/"
FFI.setResourcePath c rPath
return c
-- | The class of values that contain a 'ProcessID'. This is used to
-- abstract over the different representations of a process used by
-- the various process libraries.
class HasPid a where
toPid :: a -> IO (Maybe ProcessID)
instance HasPid ProcessID where
toPid = return . Just
-- Unfortunately, we have to reach into the internals of
-- System.Process for this one.
#if MIN_VERSION_process(1,2,0)
instance HasPid ProcessHandle where
toPid (ProcessHandle m _) = do
p <- readMVar m
case p of
(OpenHandle pid) -> return $ Just pid
_ -> return Nothing
#else
instance HasPid ProcessHandle where
toPid (ProcessHandle m) = do
p <- readMVar m
case p of
(OpenHandle pid) -> return $ Just pid
_ -> return Nothing
#endif
-- | Start observing a child process. Your handlers will be called
-- while the process runs. Note that no handlers will be called if
-- you didn't start the process with an environment produced by
-- 'FFI.prepareEnvironment'.
--
-- When the child process exits, 'startObserving' will terminate the
-- server component of libvoyeur and return. This means that
-- 'startObserving' implicitly waits for the child process, so you
-- don't need to do this on your own.
startObserving :: HasPid a
=> FFI.VoyeurContext -- ^ The context.
-> a -- ^ The child process to observe.
-> IO ExitCode -- ^ The exit status of the child process.
startObserving c p = do
mayPid <- toPid p
case mayPid of
Just pid -> FFI.startObserving c pid
Nothing -> return $ ExitFailure (-1)