]> gitweb.michael.orlitzky.com - dead/htsn.git/blob - src/Unix.hs
Add two periods at the end of info messages.
[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 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 )
13 import System.Posix (
14 GroupEntry ( groupID ),
15 GroupID,
16 Handler ( Catch ),
17 UserEntry ( userID ),
18 UserID,
19 exitImmediately,
20 getGroupEntryForName,
21 getProcessID,
22 getRealGroupID,
23 getRealUserID,
24 getUserEntryForName,
25 installHandler,
26 removeLink,
27 setFileCreationMask,
28 setGroupID,
29 setOwnerAndGroup,
30 setUserID,
31 sigTERM )
32 import System.Posix.Daemonize ( daemonize )
33
34 import Configuration (
35 Configuration( pidfile,
36 run_as_group,
37 run_as_user ))
38 import Network.Services.TSN.Report ( report_error, report_info )
39
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.
43 --
44 get_user_id :: Maybe String -> IO UserID
45 get_user_id Nothing = getRealUserID
46 get_user_id (Just s) = fmap userID (getUserEntryForName s)
47
48
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.
52 --
53 get_group_id :: Maybe String -> IO GroupID
54 get_group_id Nothing = getRealGroupID
55 get_group_id (Just s) = fmap groupID (getGroupEntryForName s)
56
57
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).
61 --
62 -- If that doesn't work, report the error and quit rudely.
63 --
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
68 report_error (show e)
69 exitImmediately ExitSuccess )
70 where
71 try_nicely = do
72 removeLink (pidfile cfg)
73 throwTo main_thread_id ExitSuccess
74
75
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.
81 --
82 -- This is intended to create one level beneath either /var/run or
83 -- /run which often do not survive a reboot.
84 --
85 -- If the directory already exists, it is left alone; that is, we
86 -- don't change its owner/group.
87 --
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.
91 -> IO ()
92 create_pid_directory pid_directory uid gid = do
93 it_exists <- doesDirectoryExist pid_directory
94 unless it_exists $ do
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
100
101 -- | Write a PID file, install a SIGTERM handler, drop privileges, and
102 -- finally do the daemonization dance.
103 --
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)
108
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
113
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
119
120 -- This is the 'daemonize' from System.Posix.Daemonize.
121 daemonize (program' orig_umask uid gid)
122 where
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."
128 tid <- myThreadId
129 _ <- installHandler sigTERM (Catch (graceful_shutdown cfg tid)) Nothing
130
131 -- Next we drop privileges. Group ID has to go first, otherwise
132 -- you ain't root to change groups.
133 setGroupID gid
134 setUserID uid
135
136 -- Now we create the PID file.
137 pid <- getProcessID
138
139 -- The PID file needs to be read-only for anyone but its
140 -- owner. Hopefully the umask accomplishes this!
141 _ <- setFileCreationMask orig_umask
142
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)
148
149 -- Finally run the program we were asked to.
150 program