{-# LANGUAGE OverloadedStrings #-} module Main where import Config import Control.Applicative 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 qualified Network.HTTP.Conduit as H import Network.Wai.Application.Classic import Network.Wai.Handler.Warp import Network.Wai.Logger import Network.Wai.Logger.Prefork import Prelude hiding (catch) import Route import System.Directory import System.Environment import System.Exit import System.FilePath import System.IO import System.Posix import Types main :: IO () main = do (opt,route) <- getOptRoute if opt_debug_mode opt then server opt route else daemonize $ server opt route where getOptRoute = getArgs >>= eachCase eachCase args | n == 0 = do root <- amIrootUser let opt = if root then defaultOption { opt_port = 80 } else defaultOption dir <- getCurrentDirectory let dst = fromString . addTrailingPathSeparator $ dir route = [Block ["localhost"] [RouteFile "/" dst]] return (opt, route) | n == 2 = do opt <- parseOption $ args !! 0 route <- parseRoute $ args !! 1 return (opt,route) | otherwise = do hPutStrLn stderr "Usage: mighty" hPutStrLn stderr " mighty config_file routing_file" exitFailure where n = length args ---------------------------------------------------------------- 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 = hPrint stderr 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 ignoreSigChild lgr <- logInit FromSocket logtype getInfo <- fileCacheInit mgr <- H.newManager H.def { -- FIXME H.managerConnCount = 1024 } runSettingsSocket setting s $ \req -> fileCgiApp (cspec lgr) (filespec getInfo) cgispec (revproxyspec mgr) route req where setting = defaultSettings { settingsPort = opt_port opt , settingsOnException = printStdout , settingsTimeout = opt_connection_timeout opt , settingsHost = HostAny } serverName = BS.pack $ opt_server_name opt cspec lgr = ClassicAppSpec { softwareName = serverName , logger = lgr , statusFileDir = fromString $ opt_status_file_dir opt } filespec getInfo = FileAppSpec { indexFile = fromString $ opt_index_file opt , isHTML = \x -> ".html" `isSuffixOf` x || ".htm" `isSuffixOf` x , getFileInfo = getInfo } cgispec = CgiAppSpec { indexCgi = "index.cgi" } revproxyspec mgr = RevProxyAppSpec { revProxyManager = mgr } 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 terminateHandler cids = Catch $ do mapM_ terminateChild cids exitImmediately ExitSuccess terminateChild cid = signalProcess sigTERM cid `catch` ignore initHandler :: Signal -> Handler -> IO Handler initHandler sig func = installHandler sig func Nothing ignoreSigChild :: IO Handler ignoreSigChild = initHandler sigCHLD Ignore ---------------------------------------------------------------- amIrootUser :: IO Bool amIrootUser = (== 0) <$> getRealUserID setGroupUser :: Option -> IO () setGroupUser opt = do root <- amIrootUser when root $ 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 _ = return () printStdout :: SomeException -> IO () printStdout = print