import Control.Concurrent ( ThreadId, myThreadId )
import Control.Exception ( throwTo )
import System.Exit ( ExitCode( ExitSuccess ) )
+import System.IO.Error ( catchIOError )
import System.Posix (
GroupEntry ( groupID ),
GroupID,
Handler ( Catch ),
UserEntry ( userID ),
UserID,
+ exitImmediately,
getGroupEntryForName,
getProcessID,
getRealGroupID,
getUserEntryForName,
installHandler,
removeLink,
+ setFileCreationMask,
setGroupID,
setUserID,
sigTERM )
Configuration( pidfile,
run_as_group,
run_as_user ))
-import Logging ( log_info )
+import Logging ( log_info, log_error )
+import Terminal ( display_error )
-- | Retrieve the uid associated with the given system user name. We
-- take a Maybe String as an argument so the user name can be passed
-- | This function will be called in response to a SIGTERM; i.e. when
-- someone tries to kill our process. We simply delete the PID file
-- and signal our parent thread to quit (successfully).
+--
+-- If that doesn't work, report the error and quit rudely.
+--
graceful_shutdown :: Configuration -> ThreadId -> IO ()
graceful_shutdown cfg main_thread_id = do
log_info "SIGTERM received, removing PID file and shutting down."
- removeLink (pidfile cfg)
- throwTo main_thread_id ExitSuccess
+ catchIOError try_nicely (\e -> do
+ display_error (show e)
+ log_error (show e)
+ exitImmediately ExitSuccess )
+ where
+ try_nicely = do
+ removeLink (pidfile cfg)
+ throwTo main_thread_id ExitSuccess
-- | Write a PID file, install a SIGTERM handler, drop privileges, and
-- finally do the daemonization dance.
--
full_daemonize :: Configuration -> IO () -> IO ()
-full_daemonize cfg program =
- -- This is the 'daemonize' from System.Posix.Daemonize.
- daemonize program'
+full_daemonize cfg program = do
+ -- The call to 'daemonize' will set the umask to zero, but we want
+ -- to retain it. So, we set the umask to zero before 'daemonize'
+ -- can, so that we can record the previous umask value (returned by
+ -- setFileCreationMask).
+ orig_umask <- setFileCreationMask 0
+ -- This is the 'daemonize' from System.Posix.Daemonize. If it
+ -- doesn't work, we report the error and do not much else.
+ catchIOError (daemonize (program' orig_umask))
+ (\e -> do
+ display_error (show e)
+ log_error (show e))
where
-- We need to do all this stuff *after* we daemonize.
- program' = do
- -- First write the PID file which probably requires root.
- pid <- getProcessID
- writeFile (pidfile cfg) (show pid)
-
- -- We need to pass the thread ID to the signal handler so it
- -- knows which process to "exit."
+ program' orig_umask = do
+ -- First we install a signal handler for sigTERM. We need to
+ -- pass the thread ID to the signal handler so it knows which
+ -- process to "exit."
tid <- myThreadId
_ <- installHandler sigTERM (Catch (graceful_shutdown cfg tid)) Nothing
- -- Then drop privileges.
- get_user_id (run_as_user cfg) >>= setUserID
+ -- Next we drop privileges. Group ID has to go first, otherwise
+ -- you ain't root to change groups.
get_group_id (run_as_group cfg) >>= setGroupID
+ get_user_id (run_as_user cfg) >>= setUserID
+
+ -- Now we create the PID file.
+ pid <- getProcessID
+
+ -- The PID file needs to be read-only for anyone but its
+ -- owner. Hopefully the umask accomplishes this!
+ _ <- setFileCreationMask orig_umask
+
+ -- When we later attempt to delete the PID file, it requires
+ -- write permission to the parent directory and not to the PID
+ -- file itself. Therefore, if that's going to work, this has to
+ -- work, even as a limited user.
+ writeFile (pidfile cfg) (show pid)
-- Finally run the program we were asked to.
program