Blob


1 EDIT 2021/02/05: typos
3 Some daemons are able to restart themselves. I mean, a real in-place restart, not a naïve external stop+re-exec.
5 Why would you care if a daemon is able to restart in place or not? Well, it depends. For some daemons is almost a necessary feature (think of sshd, would you be happy if when you restart the daemon it would shut down every ongoing connections? I wouldn’t), in others a nice-to-have feature (httpd for instance), while in some case is an unnecessary complications.
7 Generally speaking, with a various degree of importance, for network-related daemons being able to restart in place is a good thing. It means that you (the server administrator) can adjust things while the daemon is running and this is almost invisible for the outside word: ongoing connection are preserved and new connections are subject to the new set of rules.
9 I just implemented something similar for gmid, my Gemini server, but the overall design can be used in various kind of daemons I guess.
11 => gemini://gemini.omarpolo.com/pages/gmid.gmi gmid
13 The solution I chose was to keep a parent process that on SIGHUP re-reads the configuration and forks(2) to execute the real daemon code. The other processes on SIGHUP simply stop accepting new connections and finish to process what they have.
15 Doing it this way simplifies the situation when you take into consideration that the daemon may want to chroot itself, or do any other kind of sandbox, or drop privileges and so on, since the main process remains outside the chroot/sandbox with the original privileges. It also isn’t a security concern since all it does is waiting on a signal (in other words, it cannot be influenced by the outside world.)
17 One thing to be wary are race-conditions induced by signal handlers. Consider this bit of code
19 ```
20 /* 1 when SIGHUP is received, 0 otherwise.
21 * This var is shared with the children. */
22 volatile sig_atomic_t hupped;
24 /* … */
26 for (;;) {
27 hupped = 0;
29 switch (fork()) {
30 case 0:
31 return daemon_main();
32 }
34 wait_sighup();
35 /* after this point hupped is 1 */
36 reload_config();
37 }
38 ```
40 You see the problem?
42 (spoiler: the reload_config call is there only to trick you)
44 We set ‘hupped’ to 0 before we fork, so our child starts with hupped set to 0, then we fork and wait. But what if we receive a SIGHUP after we set the variable to 0, but before the fork? Or right before wait_sighup? The children will exit and the main process would get stuck waiting for a SIGHUP that was already delivered.
46 Oh, and guarding the wait_sighup won’t work too
48 ```
49 if (!hupped) {
50 /* what happens if SIGHUP gets delivered
51 * here, before the wait? */
52 wait_sighup();
53 }
54 ```
56 Fortunately, we can block signals with sigprocmask and wait for specific signals with sigwait.
58 => gemini://gemini.omarpolo.com/cgi/man?sigprocmask sigprocmask(2)
59 => gemini://gemini.omarpolo.com/cgi/man?sigwait sigwait(2)
61 Frankly, I never used these “advanced” signals API before, as usually the “simplified” interface were enough, but it’s nice to learn new stuff.
63 The right order should be
64 * block all signals
65 * fork
66 * in the child, re-enable signals
67 * in the parent, wait for sighup
68 * re-enable signals
69 * repeat
71 or, if you prefer some real code, something along the lines of
73 ```C
74 sigset_t set;
76 void
77 block_signals(void)
78 {
79 sigset_t new;
81 sigemptyset(&new);
82 sigaddset(&new, SIGHUP);
83 sigprocmask(SIG_BLOCK, &new, &set);
84 }
86 void
87 unblock_signals(void)
88 {
89 sigprocmask(SIG_SETMASK, &set, NULL);
90 }
92 void
93 wait_sighup(void)
94 {
95 sigset_t mask;
96 int signo;
98 sigemptyset(&mask);
99 sigaddset(&mask, SIGHUP);
100 sigwait(&mask, &signo);
103 /* … */
105 volatile sig_atomic_t hupped;
107 /* … */
109 for (;;) {
110 block_signals();
111 hupped = 0;
113 switch (fork()) {
114 case 0:
115 unblock_signals();
116 return daemon_main();
119 wait_sighup();
120 unblock_signals();
121 reload_config();
123 ```