{-# LANGUAGE OverloadedStrings, DoAndIfThenElse #-} module Main where import Config import Control.Concurrent import Control.Exception (catch, handle, SomeException) import Control.Monad import qualified Data.ByteString.Char8 as BS import FileCGIApp import FileCache import Network import Network.Wai.Application.Classic import Network.Wai.Handler.Warp import Network.Wai.Logger.Prefork import Prelude hiding (catch) import Route import System.Environment import System.Exit import System.IO import System.Posix import Types main :: IO () main = do opt <- fileName 0 >>= parseOption route <- fileName 1 >>= parseRoute if opt_debug_mode opt then server opt route else daemonize $ server opt route where fileName n = do args <- getArgs when (length args /= 2) $ do hPutStrLn stderr "Usage: mighty config_file routing_file" exitFailure return $ args !! n ---------------------------------------------------------------- server :: Option -> RouteDB -> IO () server opt route = handle handler $ do s <- sOpen unless debug writePidFile setGroupUser opt logCheck logtype if workers == 1 then do forkIO $ single opt route s logtype myid <- getProcessID logController logtype [myid] else do cids <- multi opt route s logtype logController logtype cids where debug = opt_debug_mode opt port = opt_port opt sOpen = listenOn (PortNumber . fromIntegral $ port) pidfile = opt_pid_file opt workers = opt_worker_processes opt writePidFile = do pid <- getProcessID writeFile pidfile $ show pid ++ "\n" setFileMode pidfile 0o644 handler :: SomeException -> IO () handler e | debug = hPutStrLn stderr $ show e | otherwise = writeFile "/tmp/mighty_error" (show e) logspec = FileLogSpec { log_file = opt_log_file opt , log_file_size = fromIntegral $ opt_log_file_size opt , log_backup_number = opt_log_backup_number opt } logtype | not (opt_logging opt) = LogNone | debug = LogStdout | otherwise = LogFile logspec ---------------------------------------------------------------- single :: Option -> RouteDB -> Socket -> LogType -> IO () single opt route s logtype = do lgr <- logInit logtype getInfo <- fileCacheInit runSettingsSocket setting s $ fileCgiApp (spec lgr getInfo) route where setting = defaultSettings { settingsPort = opt_port opt , settingsOnException = printStdout , settingsTimeout = opt_connection_timeout opt } spec lgr getInfo = AppSpec { softwareName = BS.pack $ opt_server_name opt , indexFile = BS.pack $ opt_index_file opt , isHTML = \x -> ".html" `BS.isSuffixOf` x || ".htm" `BS.isSuffixOf` x , logger = lgr , getFileInfo = getInfo } multi :: Option -> RouteDB -> Socket -> LogType -> IO [ProcessID] multi opt route s logtype = do ignoreSigChild cids <- replicateM workers $ forkProcess (single opt route s logtype) sClose s initHandler sigTERM $ terminateHandler cids initHandler sigINT $ terminateHandler cids return cids where workers = opt_worker_processes opt initHandler sig func = installHandler sig func Nothing ignoreSigChild = initHandler sigCHLD Ignore terminateHandler cids = Catch $ do mapM_ terminateChild cids exitImmediately ExitSuccess terminateChild cid = signalProcess sigTERM cid `catch` ignore ---------------------------------------------------------------- setGroupUser :: Option -> IO () setGroupUser opt = do uid <- getRealUserID when (uid == 0) $ do getGroupEntryForName (opt_group opt) >>= setGroupID . groupID getUserEntryForName (opt_user opt) >>= setUserID . userID ---------------------------------------------------------------- daemonize :: IO () -> IO () daemonize program = ensureDetachTerminalCanWork $ do detachTerminal ensureNeverAttachTerminal $ do changeWorkingDirectory "/" setFileCreationMask 0 mapM_ closeFd [stdInput, stdOutput, stdError] program where ensureDetachTerminalCanWork p = do forkProcess p exitImmediately ExitSuccess ensureNeverAttachTerminal p = do forkProcess p exitImmediately ExitSuccess detachTerminal = createSession ---------------------------------------------------------------- ignore :: SomeException -> IO () ignore = const $ return () printStdout :: SomeException -> IO () printStdout e = print e