GF.Infra.SIO.hs: adding the SIO monad (where S = Shell or Safe)

The SIO monad is a restriction of the IO monad with two purposes:

+ Access to arbitrary IO operations can be turned off by setting the environment
  variable GF_RESTRICTED. There is a limited set of IO operations that are
  considered safe and always allowed.

+ It allows output to stdout to be captured. This can be used in gf -server
  mode, where output of GF shell commands is made part of HTTP responses
  returned to clients.
This commit is contained in:
hallgren
2012-09-25 18:38:13 +00:00
parent 69de623c17
commit 1adc0ed9f7

View File

@@ -0,0 +1,91 @@
-- | Shell IO: a monad that can restrict acesss to arbitrary IO and has the
-- ability to capture output that normally would be sent to stdout.
module GF.Infra.SIO(
-- * The SIO monad
SIO,
-- * Running SIO operations
runSIO,hRunSIO,captureSIO,
-- * Unrestricted, safe operations
-- ** From the standard libraries
getCPUTime,getCurrentDirectory,getLibraryDirectory,
newStdGen,print,putStrLn,
-- ** Specific to GF
importGrammar,importSource,
putStrLnFlush,runInterruptibly,
-- * Restricted accesss to arbitrary (potentially unsafe) IO operations
-- | If the environment variable GF_RESTRICTED is defined, these
-- operations will fail. Otherwise, they will be executed normally.
-- Output to stdout will /not/ be captured or redirected.
restricted,restrictedSystem
) where
import Prelude hiding (putStrLn,print)
import Control.Monad(liftM)
import System.IO(Handle,hPutStrLn,hFlush,stdout)
import System.IO.Error(try)
import System.Cmd(system)
import System.Environment(getEnv)
import Control.Concurrent.Chan(newChan,writeChan,getChanContents)
import qualified System.CPUTime as IO(getCPUTime)
import qualified System.Directory as IO(getCurrentDirectory)
import qualified System.Random as IO(newStdGen)
import qualified GF.Infra.UseIO as IO(getLibraryDirectory)
import qualified GF.System.Signal as IO(runInterruptibly)
import qualified GF.Command.Importing as GF(importGrammar, importSource)
-- * The SIO monad
type PutStrLn = String -> IO ()
newtype SIO a = SIO {unS::PutStrLn->IO a}
instance Functor SIO where fmap = liftM
instance Monad SIO where
return x = SIO (const (return x))
SIO m1 >>= xm2 = SIO $ \ h -> m1 h >>= \ x -> unS (xm2 x) h
-- * Running SIO operations
-- | Run normally
runSIO = hRunSIO stdout
-- | Redirect 'stdout' to the given handle
hRunSIO h sio = unS sio (\s->hPutStrLn h s>>hFlush h)
-- | Capture 'stdout'
captureSIO sio = do ch <- newChan
result <- unS sio (writeChan ch . Just)
writeChan ch Nothing
output <- fmap takeJust (getChanContents ch)
return (output,result)
where
takeJust (Just xs:ys) = xs++takeJust ys
takeJust _ = []
-- * Restricted accesss to arbitrary (potentially unsafe) IO operations
restricted io = SIO (const (restrictedIO io))
restrictedSystem = restricted . system
restrictedIO io =
either (const io) (const $ fail message) =<< try (getEnv "GF_RESTRICTED")
where
message =
"This operation is not allowed when GF is running in restricted mode."
-- * Unrestricted, safe IO operations
lift0 io = SIO $ const io
lift1 f io = SIO $ f . unS io
putStrLn = putStrLnFlush
putStrLnFlush s = SIO ($ s)
print x = putStrLn (show x)
getCPUTime = lift0 IO.getCPUTime
getCurrentDirectory = lift0 IO.getCurrentDirectory
getLibraryDirectory = lift0 . IO.getLibraryDirectory
newStdGen = lift0 IO.newStdGen
runInterruptibly = lift1 IO.runInterruptibly
importGrammar pgf opts files = lift0 $ GF.importGrammar pgf opts files
importSource src opts files = lift0 $ GF.importSource src opts files