esotericbot-0.0.1: Esotericbot/Safely.hs
{-# OPTIONS -fglasgow-exts #-}
module Esotericbot.Safely where
import Prelude as P
import Data.List.Stream as L
import System.Posix
import System.IO as IO
import Control.Concurrent.STM
import Data.Maybe
import Data.Monoid
import Data.ByteString.Lazy.Char8 as BS
import Data.IORef
import Esotericbot.BSUtils
import Esotericbot.EBTypes
import Esotericbot.IRCCom
import Esotericbot.BSH
data SafeArgs =
SafeArgs {
output_limit :: Maybe Integer
, time_limit :: Maybe Integer
, mem_limit :: Maybe Integer
, hard_time_limit :: Maybe Integer
}
handle_alarm f =
installHandler realTimeAlarm
( Catch f )
Nothing
term_child dVar term_limit mkill_limit pid = if_not_done dVar $ do
signalProcess softwareTermination pid
maybe ( return ( ) )
( \ kill_limit ->
if ( kill_limit ) > term_limit
then do
handle_alarm $ kill_child dVar pid
scheduleAlarm_ $ kill_limit - term_limit
else
return ( )
)
mkill_limit
kill_child dVar pid = if_not_done dVar $
signalProcess killProcess pid
if_not_done d f = do
done <- atomically $ readTVar d
if done
then return ( )
else f
scheduleAlarm_ l = do
r <- scheduleAlarm l
if r < 0
then error "could not schedule alarm!"
else return ( )
close_on_error :: forall m . Monoid m => SmallBotM m -> Handle -> SmallBotM m
close_on_error f h = do
sb <- get
liftIO $ catch ( evalStateT f sb ) ( const $ do IO.hClose h; return mempty )
replace_newlines :: BS.ByteString -> BS.ByteString
replace_newlines bs =
BS.map nlToSp bs
where
nlToSp '\n' =
' '
nlToSp '\r' =
' '
nlToSp x =
x
safely :: IRCCommand cmd => Handle -> cmd -> FilePath -> [ String ] -> ByteString -> Maybe Integer -> SmallBotM ( )
safely h irc_command cmd input prog mmem = do
( parent_read , child_write ) <- liftIO createPipe
( child_read , parent_write ) <- liftIO createPipe
sb <- get
io_safe_limits <- safe_limits mmem $ mtime_limit sb
let run_child = do
closeFd parent_write
closeFd parent_read
io_safe_limits
dupTo child_read stdInput
dupTo child_write stdOutput
closeFd child_read
closeFd child_write
executeFile cmd True input Nothing
pid <- liftIO $ forkProcess run_child
liftIO $ do
closeFd child_write
closeFd child_read
w <- liftIO $ fdToHandle parent_write
close_on_error ( liftIO $ do
BS.hPut w prog
IO.hClose w
) w
dVar <- liftIO $ atomically $ newTVar False
term_ind <- maybe ( return False )
( \ term_limit -> liftIO $ do
handle_alarm $ term_child dVar term_limit ( mhard_time_limit sb >>= return . fromEnum ) pid
scheduleAlarm_ term_limit
return True
)
$ mtime_limit sb >>= return . fromEnum
if term_ind
then
return ( )
else
maybe ( return ( ) )
( \ kill_limit -> liftIO $ do
handle_alarm $ kill_child dVar pid
scheduleAlarm_ kill_limit
return ( )
)
$ mhard_time_limit sb >>= return . fromEnum
r <- liftIO $ fdToHandle parent_read
read <- liftIO $ newIORef False
close_on_error ( do
maybe ( do c <- liftIO $ BS.hGetContents r
res <- null_check $ replace_newlines c
priv_msg h irc_command res
)
( \ ol -> do
bs <- liftIO $ BS.hGet r $ 1 + fromIntegral ol
if BS.length bs > ol -- check if we need to add dots ( this looks silly but, the user probably would like to know )
then do
dots <- liftIO $ ls2bs 3 "..."#
priv_msg h irc_command $ replace_newlines ( BS.take ol bs ) `hAppend` dots
else do
n <- null_check $ replace_newlines bs
priv_msg h irc_command n
)
$ moutput_limit sb
liftIO $ writeIORef read True
liftIO $ hClose r
) r
is_read <- liftIO $ readIORef read
if is_read
then return ( )
else do n <- nullness
priv_msg h irc_command n
liftIO $ do
getProcessStatus True False pid
atomically $ writeTVar dVar True
where
null_check c =
if BS.null c
then nullness
else return c
nullness =
liftIO $ ls2bs 10 "No output."#
-- this is disgusting! XD
safe_limits :: Maybe Integer -> Maybe Integer -> SmallBotM ( IO ( ) )
safe_limits cmmem mtime_limit = do
mmem <- child_mem_limit cmmem
return $ do
maybe ( return ( ) )
( \ l ->
setResourceLimit ResourceCPUTime $ both_limits l
)
mtime_limit
maybe ( return ( ) )
( \ l ->
setResourceLimit ResourceTotalMemory $ both_limits l
)
mmem
child_mem_limit :: Maybe Integer -> SmallBotM ( Maybe Integer )
child_mem_limit mmem = do
dmmem <- default_mem
return $ maybe dmmem
Just
mmem
where
default_mem = do
sb <- get
return $ default_child_mem_limit sb
both_limits l =
ResourceLimits {
softLimit = ResourceLimit l
, hardLimit = ResourceLimit l
}