1 -- | Non-portable code for daemonizing on unix.
6 import Control.Concurrent ( ThreadId, myThreadId )
7 import Control.Exception ( throwTo )
8 import Control.Monad ( unless )
9 import System.Directory ( createDirectory, doesDirectoryExist )
10 import System.Exit ( ExitCode( ExitSuccess ) )
11 import System.FilePath ( dropFileName, dropTrailingPathSeparator )
12 import System.IO.Error ( catchIOError )
14 GroupEntry ( groupID ),
32 import System.Posix.Daemonize ( daemonize )
34 import Configuration (
35 Configuration( pidfile,
38 import Network.Services.TSN.Report ( report_error, report_info )
40 -- | Retrieve the uid associated with the given system user name. We
41 -- take a Maybe String as an argument so the user name can be passed
42 -- in directly from the config.
44 get_user_id :: Maybe String -> IO UserID
45 get_user_id Nothing = getRealUserID
46 get_user_id (Just s) = fmap userID (getUserEntryForName s)
49 -- | Retrieve the gid associated with the given system group name. We
50 -- take a Maybe String as an argument so the group name can be
51 -- passed in directly from the config.
53 get_group_id :: Maybe String -> IO GroupID
54 get_group_id Nothing = getRealGroupID
55 get_group_id (Just s) = fmap groupID (getGroupEntryForName s)
58 -- | This function will be called in response to a SIGTERM; i.e. when
59 -- someone tries to kill our process. We simply delete the PID file
60 -- and signal our parent thread to quit (successfully).
62 -- If that doesn't work, report the error and quit rudely.
64 graceful_shutdown :: Configuration -> ThreadId -> IO ()
65 graceful_shutdown cfg main_thread_id = do
66 report_info "SIGTERM received, removing PID file and shutting down."
67 catchIOError try_nicely (\e -> do
69 exitImmediately ExitSuccess )
72 removeLink (pidfile cfg)
73 throwTo main_thread_id ExitSuccess
76 -- | Create the directory in which we intend to store the PID
77 -- file. This will *not* create any parent directories. The PID
78 -- directory will have its owner/group changed to the user/group
79 -- under which we'll be running. No permissions will be set; the
80 -- system's umask must allow owner-write.
82 -- This is intended to create one level beneath either /var/run or
83 -- /run which often do not survive a reboot.
85 -- If the directory already exists, it is left alone; that is, we
86 -- don't change its owner/group.
88 create_pid_directory :: FilePath -- ^ The directory to contain the PID file.
89 -> UserID -- ^ Owner of the new directory if created.
90 -> GroupID -- ^ Group of the new directory if created.
92 create_pid_directory pid_directory uid gid = do
93 it_exists <- doesDirectoryExist pid_directory
95 report_info $ "Creating PID directory " ++ pid_directory ++ "."
96 createDirectory pid_directory
97 report_info $ "Changing owner/group of " ++ pid_directory ++
98 " to " ++ (show uid) ++ "/" ++ (show gid) ++ "."
99 setOwnerAndGroup pid_directory uid gid
101 -- | Write a PID file, install a SIGTERM handler, drop privileges, and
102 -- finally do the daemonization dance.
104 full_daemonize :: Configuration -> IO () -> IO ()
105 full_daemonize cfg program = do
106 uid <- get_user_id (run_as_user cfg)
107 gid <- get_group_id (run_as_group cfg)
109 -- This will have to be done as root and the result chowned to our
110 -- user/group, so it must happen before daemonizing.
111 let pid_directory = dropTrailingPathSeparator $ dropFileName $ pidfile cfg
112 create_pid_directory pid_directory uid gid
114 -- The call to 'daemonize' will set the umask to zero, but we want
115 -- to retain it. So, we set the umask to zero before 'daemonize'
116 -- can, so that we can record the previous umask value (returned by
117 -- setFileCreationMask).
118 orig_umask <- setFileCreationMask 0
120 -- This is the 'daemonize' from System.Posix.Daemonize.
121 daemonize (program' orig_umask uid gid)
123 -- We need to do all this stuff *after* we daemonize.
124 program' orig_umask uid gid = do
125 -- First we install a signal handler for sigTERM. We need to
126 -- pass the thread ID to the signal handler so it knows which
127 -- process to "exit."
129 _ <- installHandler sigTERM (Catch (graceful_shutdown cfg tid)) Nothing
131 -- Next we drop privileges. Group ID has to go first, otherwise
132 -- you ain't root to change groups.
136 -- Now we create the PID file.
139 -- The PID file needs to be read-only for anyone but its
140 -- owner. Hopefully the umask accomplishes this!
141 _ <- setFileCreationMask orig_umask
143 -- When we later attempt to delete the PID file, it requires
144 -- write permission to the parent directory and not to the PID
145 -- file itself. Therefore, if that's going to work, this has to
146 -- work, even as a limited user.
147 writeFile (pidfile cfg) (show pid)
149 -- Finally run the program we were asked to.