hsfcsh-0.0.1: hsfcsh.hs
module Main
(
main
)
where
import Prelude hiding (catch)
import Network
import System.Exit
import System.IO
import System.Process
import System.Environment
import System.Posix.Daemonize
import System.Log.Logger
import System.Log.Handler.Syslog
import System.Console.GetOpt
import Control.Exception
import Control.Monad
import Control.Concurrent
import Control.Concurrent.Chan
import Data.Maybe
data Options = Options {
optFcshCmd :: String,
optPort :: PortNumber,
optDaemonize :: Bool,
optSyslog :: Bool
} deriving Show
defaultOptions = Options {
optFcshCmd = "fcsh",
optPort = 1234,
optDaemonize = True,
optSyslog = True
}
options :: [OptDescr (Options -> Options)]
options = [
Option ['c'] ["with-fcsh"] (ReqArg (\o opts -> opts {optFcshCmd = o}) "CMD") "fcsh command",
Option ['p'] ["port"] (ReqArg (\o opts -> opts {optPort = fromIntegral $ read o}) "PORT") "port to listen on",
Option ['f'] ["foreground"] (NoArg (\opts -> opts {optDaemonize = False})) "don't daemonize",
Option ['d'] ["daemonize"] (NoArg (\opts -> opts {optDaemonize = True})) "daemonize",
Option ['s'] ["silent"] (NoArg (\opts -> opts {optSyslog = False})) "don't log to the syslog",
Option ['v'] ["verbose"] (NoArg (\opts -> opts {optSyslog = True})) "log to the syslog"
]
log_ :: String
log_ = "hsfcsh"
parseOpts :: [String] -> IO (Options, [String])
parseOpts argv =
case getOpt Permute options argv of
(o, n, []) -> return (foldl (flip id) defaultOptions o, n)
(_, _, errs) -> ioError (userError $ concat errs ++ usageInfo header options)
where
header = "Usage: hsfcsh [OPTION...]"
main :: IO ()
main = do
(opts, _) <- getArgs >>= parseOpts
(if optDaemonize opts then daemonize else id) $ do
updateGlobalLogger rootLoggerName (setLevel INFO)
s <- openlog "hsfcsh" [PID] USER DEBUG
when (optSyslog opts) $ updateGlobalLogger rootLoggerName (setHandlers [s])
infoM log_ "started"
fcsh <- runInteractiveCommand $ optFcshCmd opts
threadId <- myThreadId
chan <- newChan
forkIO $ reader (getOut fcsh) chan threadId
forkIO $ reader (getErr fcsh) chan threadId
waitReady chan Nothing
withSocketsDo $ main' opts fcsh chan
terminateProcess $ getPH fcsh
infoM log_ "shutdown"
mPutStrLn :: Maybe Handle -> String -> IO ()
mPutStrLn Nothing _ = return ()
mPutStrLn (Just handle) str = hPutStrLn handle str >> hFlush handle
waitReady :: Chan Char -> Maybe Handle -> IO ()
waitReady chan handle = waitReady' []
where
waitReady' cs = do
c <- readChan chan
if c == '\n'
then mPutStrLn handle (reverse cs) >> waitReady' []
else if (c:cs) == " )hscf("
then mPutStrLn handle "<FCSH END>"
else waitReady' (c:cs)
main' :: Options -> FCSH -> Chan Char -> IO ()
main' opts fcsh chan = do
sock <- listenOn (PortNumber $ optPort opts)
acceptLoop fcsh sock chan
sClose sock
type FCSH = (Handle, Handle, Handle, ProcessHandle)
getIn :: FCSH -> Handle
getIn (i, _, _, _) = i
getOut :: FCSH -> Handle
getOut (_, o, _, _) = o
getErr :: FCSH -> Handle
getErr (_, _, e, _) = e
getPH :: FCSH -> ProcessHandle
getPH (_, _, _, p) = p
acceptLoop :: FCSH -> Socket -> Chan Char -> IO ()
acceptLoop fcsh sock chan = do
(handle, host, _) <- accept sock
infoM log_ $ "accept from " ++ host
hSetBinaryMode handle False
hSetNewlineMode handle universalNewlineMode
cont <- catch (serve fcsh handle chan) (\e -> warningM log_ (show (e :: SomeException)) >> return True)
hClose handle
when cont $ acceptLoop fcsh sock chan
serve :: FCSH -> Handle -> Chan Char -> IO Bool
serve fcsh handle chan = do
cmd <- hGetLine handle
case cmd of
"q" -> return False
"t" -> do
tgt <- hGetLine handle
debugM log_ $ "command: " ++ tgt
hPutStrLn (getIn fcsh) $ tgt
hFlush (getIn fcsh)
waitReady chan (Just handle)
serve fcsh handle chan
"n" -> debugM log_ "NOP" >> return True
_ -> warningM log_ ("unknown command :" ++ cmd) >> return True
reader :: Handle -> Chan Char -> ThreadId -> IO ()
reader handle chan mainThread = do
hSetBinaryMode handle False
let lp h = catch (hGetChar h) (\e -> errorM log_ (show (e :: SomeException)) >> killThread mainThread >> undefined) >>= writeChan chan >> lp h
lp handle