]> gitweb.michael.orlitzky.com - dead/htsn.git/blob - src/Unix.hs
Move the Report, Logging, and Terminal modules into their own namespace.
[dead/htsn.git] / src / Unix.hs
1 -- | Non-portable code for daemonizing on unix.
2 --
3 module Unix
4 where
5
6 import Control.Concurrent ( ThreadId, myThreadId )
7 import Control.Exception ( throwTo )
8 import System.Exit ( ExitCode( ExitSuccess ) )
9 import System.IO.Error ( catchIOError )
10 import System.Posix (
11 GroupEntry ( groupID ),
12 GroupID,
13 Handler ( Catch ),
14 UserEntry ( userID ),
15 UserID,
16 exitImmediately,
17 getGroupEntryForName,
18 getProcessID,
19 getRealGroupID,
20 getRealUserID,
21 getUserEntryForName,
22 installHandler,
23 removeLink,
24 setFileCreationMask,
25 setGroupID,
26 setUserID,
27 sigTERM )
28 import System.Posix.Daemonize ( daemonize )
29
30 import Configuration (
31 Configuration( pidfile,
32 run_as_group,
33 run_as_user ))
34 import Network.Services.TSN.Logging ( log_info, log_error )
35 import Network.Services.TSN.Terminal ( display_error )
36
37 -- | Retrieve the uid associated with the given system user name. We
38 -- take a Maybe String as an argument so the user name can be passed
39 -- in directly from the config.
40 --
41 get_user_id :: Maybe String -> IO UserID
42 get_user_id Nothing = getRealUserID
43 get_user_id (Just s) = fmap userID (getUserEntryForName s)
44
45
46 -- | Retrieve the gid associated with the given system group name. We
47 -- take a Maybe String as an argument so the group name can be
48 -- passed in directly from the config.
49 --
50 get_group_id :: Maybe String -> IO GroupID
51 get_group_id Nothing = getRealGroupID
52 get_group_id (Just s) = fmap groupID (getGroupEntryForName s)
53
54
55 -- | This function will be called in response to a SIGTERM; i.e. when
56 -- someone tries to kill our process. We simply delete the PID file
57 -- and signal our parent thread to quit (successfully).
58 --
59 -- If that doesn't work, report the error and quit rudely.
60 --
61 graceful_shutdown :: Configuration -> ThreadId -> IO ()
62 graceful_shutdown cfg main_thread_id = do
63 log_info "SIGTERM received, removing PID file and shutting down."
64 catchIOError try_nicely (\e -> do
65 display_error (show e)
66 log_error (show e)
67 exitImmediately ExitSuccess )
68 where
69 try_nicely = do
70 removeLink (pidfile cfg)
71 throwTo main_thread_id ExitSuccess
72
73
74 -- | Write a PID file, install a SIGTERM handler, drop privileges, and
75 -- finally do the daemonization dance.
76 --
77 full_daemonize :: Configuration -> IO () -> IO ()
78 full_daemonize cfg program = do
79 -- The call to 'daemonize' will set the umask to zero, but we want
80 -- to retain it. So, we set the umask to zero before 'daemonize'
81 -- can, so that we can record the previous umask value (returned by
82 -- setFileCreationMask).
83 orig_umask <- setFileCreationMask 0
84 -- This is the 'daemonize' from System.Posix.Daemonize. If it
85 -- doesn't work, we report the error and do not much else.
86 catchIOError (daemonize (program' orig_umask))
87 (\e -> do
88 display_error (show e)
89 log_error (show e))
90 where
91 -- We need to do all this stuff *after* we daemonize.
92 program' orig_umask = do
93 -- First we install a signal handler for sigTERM. We need to
94 -- pass the thread ID to the signal handler so it knows which
95 -- process to "exit."
96 tid <- myThreadId
97 _ <- installHandler sigTERM (Catch (graceful_shutdown cfg tid)) Nothing
98
99 -- Next we drop privileges. Group ID has to go first, otherwise
100 -- you ain't root to change groups.
101 get_group_id (run_as_group cfg) >>= setGroupID
102 get_user_id (run_as_user cfg) >>= setUserID
103
104 -- Now we create the PID file.
105 pid <- getProcessID
106
107 -- The PID file needs to be read-only for anyone but its
108 -- owner. Hopefully the umask accomplishes this!
109 _ <- setFileCreationMask orig_umask
110
111 -- When we later attempt to delete the PID file, it requires
112 -- write permission to the parent directory and not to the PID
113 -- file itself. Therefore, if that's going to work, this has to
114 -- work, even as a limited user.
115 writeFile (pidfile cfg) (show pid)
116
117 -- Finally run the program we were asked to.
118 program