telnet: code shrink
[oweals/busybox.git] / runit / runsvdir.c
1 /*
2 Copyright (c) 2001-2006, Gerrit Pape
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
7
8    1. Redistributions of source code must retain the above copyright notice,
9       this list of conditions and the following disclaimer.
10    2. Redistributions in binary form must reproduce the above copyright
11       notice, this list of conditions and the following disclaimer in the
12       documentation and/or other materials provided with the distribution.
13    3. The name of the author may not be used to endorse or promote products
14       derived from this software without specific prior written permission.
15
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
29 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
30
31 //usage:#define runsvdir_trivial_usage
32 //usage:       "[-P] [-s SCRIPT] DIR"
33 //usage:#define runsvdir_full_usage "\n\n"
34 //usage:       "Start a runsv process for each subdirectory. If it exits, restart it.\n"
35 //usage:     "\n        -P              Put each runsv in a new session"
36 //usage:     "\n        -s SCRIPT       Run SCRIPT <signo> after signal is processed"
37
38 #include <sys/poll.h>
39 #include <sys/file.h>
40 #include "libbb.h"
41 #include "runit_lib.h"
42
43 #define MAXSERVICES 1000
44
45 /* Should be not needed - all dirs are on same FS, right? */
46 #define CHECK_DEVNO_TOO 0
47
48 struct service {
49 #if CHECK_DEVNO_TOO
50         dev_t dev;
51 #endif
52         ino_t ino;
53         pid_t pid;
54         smallint isgone;
55 };
56
57 struct globals {
58         struct service *sv;
59         char *svdir;
60         int svnum;
61 #if ENABLE_FEATURE_RUNSVDIR_LOG
62         char *rplog;
63         int rploglen;
64         struct fd_pair logpipe;
65         struct pollfd pfd[1];
66         unsigned stamplog;
67 #endif
68 } FIX_ALIASING;
69 #define G (*(struct globals*)&bb_common_bufsiz1)
70 #define sv          (G.sv          )
71 #define svdir       (G.svdir       )
72 #define svnum       (G.svnum       )
73 #define rplog       (G.rplog       )
74 #define rploglen    (G.rploglen    )
75 #define logpipe     (G.logpipe     )
76 #define pfd         (G.pfd         )
77 #define stamplog    (G.stamplog    )
78 #define INIT_G() do { } while (0)
79
80 static void fatal2_cannot(const char *m1, const char *m2)
81 {
82         bb_perror_msg_and_die("%s: fatal: can't %s%s", svdir, m1, m2);
83         /* was exiting 100 */
84 }
85 static void warn3x(const char *m1, const char *m2, const char *m3)
86 {
87         bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
88 }
89 static void warn2_cannot(const char *m1, const char *m2)
90 {
91         warn3x("can't ", m1, m2);
92 }
93 #if ENABLE_FEATURE_RUNSVDIR_LOG
94 static void warnx(const char *m1)
95 {
96         warn3x(m1, "", "");
97 }
98 #endif
99
100 /* inlining + vfork -> bigger code */
101 static NOINLINE pid_t runsv(const char *name)
102 {
103         pid_t pid;
104
105         /* If we got signaled, stop spawning children at once! */
106         if (bb_got_signal)
107                 return 0;
108
109         pid = vfork();
110         if (pid == -1) {
111                 warn2_cannot("vfork", "");
112                 return 0;
113         }
114         if (pid == 0) {
115                 /* child */
116                 if (option_mask32 & 1) /* -P option? */
117                         setsid();
118 /* man execv:
119  * "Signals set to be caught by the calling process image
120  *  shall be set to the default action in the new process image."
121  * Therefore, we do not need this: */
122 #if 0
123                 bb_signals(0
124                         | (1 << SIGHUP)
125                         | (1 << SIGTERM)
126                         , SIG_DFL);
127 #endif
128                 execlp("runsv", "runsv", name, (char *) NULL);
129                 fatal2_cannot("start runsv ", name);
130         }
131         return pid;
132 }
133
134 /* gcc 4.3.0 does better with NOINLINE */
135 static NOINLINE int do_rescan(void)
136 {
137         DIR *dir;
138         struct dirent *d;
139         int i;
140         struct stat s;
141         int need_rescan = 0;
142
143         dir = opendir(".");
144         if (!dir) {
145                 warn2_cannot("open directory ", svdir);
146                 return 1; /* need to rescan again soon */
147         }
148         for (i = 0; i < svnum; i++)
149                 sv[i].isgone = 1;
150
151         while (1) {
152                 errno = 0;
153                 d = readdir(dir);
154                 if (!d)
155                         break;
156                 if (d->d_name[0] == '.')
157                         continue;
158                 if (stat(d->d_name, &s) == -1) {
159                         warn2_cannot("stat ", d->d_name);
160                         continue;
161                 }
162                 if (!S_ISDIR(s.st_mode))
163                         continue;
164                 /* Do we have this service listed already? */
165                 for (i = 0; i < svnum; i++) {
166                         if ((sv[i].ino == s.st_ino)
167 #if CHECK_DEVNO_TOO
168                          && (sv[i].dev == s.st_dev)
169 #endif
170                         ) {
171                                 if (sv[i].pid == 0) /* restart if it has died */
172                                         goto run_ith_sv;
173                                 sv[i].isgone = 0; /* "we still see you" */
174                                 goto next_dentry;
175                         }
176                 }
177                 { /* Not found, make new service */
178                         struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
179                         if (!svnew) {
180                                 warn2_cannot("start runsv ", d->d_name);
181                                 need_rescan = 1;
182                                 continue;
183                         }
184                         sv = svnew;
185                         svnum++;
186 #if CHECK_DEVNO_TOO
187                         sv[i].dev = s.st_dev;
188 #endif
189                         sv[i].ino = s.st_ino;
190  run_ith_sv:
191                         sv[i].pid = runsv(d->d_name);
192                         sv[i].isgone = 0;
193                 }
194  next_dentry: ;
195         }
196         i = errno;
197         closedir(dir);
198         if (i) { /* readdir failed */
199                 warn2_cannot("read directory ", svdir);
200                 return 1; /* need to rescan again soon */
201         }
202
203         /* Send SIGTERM to runsv whose directories
204          * were no longer found (-> must have been removed) */
205         for (i = 0; i < svnum; i++) {
206                 if (!sv[i].isgone)
207                         continue;
208                 if (sv[i].pid)
209                         kill(sv[i].pid, SIGTERM);
210                 svnum--;
211                 sv[i] = sv[svnum];
212                 i--; /* so that we don't skip new sv[i] (bug was here!) */
213         }
214         return need_rescan;
215 }
216
217 int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
218 int runsvdir_main(int argc UNUSED_PARAM, char **argv)
219 {
220         struct stat s;
221         dev_t last_dev = last_dev; /* for gcc */
222         ino_t last_ino = last_ino; /* for gcc */
223         time_t last_mtime = 0;
224         int wstat;
225         int curdir;
226         pid_t pid;
227         unsigned deadline;
228         unsigned now;
229         unsigned stampcheck;
230         int i;
231         int need_rescan = 1;
232         char *opt_s_argv[3];
233
234         INIT_G();
235
236         opt_complementary = "-1";
237         opt_s_argv[0] = NULL;
238         opt_s_argv[2] = NULL;
239         getopt32(argv, "Ps:", &opt_s_argv[0]);
240         argv += optind;
241
242         bb_signals(0
243                 | (1 << SIGTERM)
244                 | (1 << SIGHUP)
245                 /* For busybox's init, SIGTERM == reboot,
246                  * SIGUSR1 == halt
247                  * SIGUSR2 == poweroff
248                  * so we need to intercept SIGUSRn too.
249                  * Note that we do not implement actual reboot
250                  * (killall(TERM) + umount, etc), we just pause
251                  * respawing and avoid exiting (-> making kernel oops).
252                  * The user is responsible for the rest. */
253                 | (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0)
254                 , record_signo);
255         svdir = *argv++;
256
257 #if ENABLE_FEATURE_RUNSVDIR_LOG
258         /* setup log */
259         if (*argv) {
260                 rplog = *argv;
261                 rploglen = strlen(rplog);
262                 if (rploglen < 7) {
263                         warnx("log must have at least seven characters");
264                 } else if (piped_pair(logpipe)) {
265                         warnx("can't create pipe for log");
266                 } else {
267                         close_on_exec_on(logpipe.rd);
268                         close_on_exec_on(logpipe.wr);
269                         ndelay_on(logpipe.rd);
270                         ndelay_on(logpipe.wr);
271                         if (dup2(logpipe.wr, 2) == -1) {
272                                 warnx("can't set filedescriptor for log");
273                         } else {
274                                 pfd[0].fd = logpipe.rd;
275                                 pfd[0].events = POLLIN;
276                                 stamplog = monotonic_sec();
277                                 goto run;
278                         }
279                 }
280                 rplog = NULL;
281                 warnx("log service disabled");
282         }
283  run:
284 #endif
285         curdir = open(".", O_RDONLY|O_NDELAY);
286         if (curdir == -1)
287                 fatal2_cannot("open current directory", "");
288         close_on_exec_on(curdir);
289
290         stampcheck = monotonic_sec();
291
292         for (;;) {
293                 /* collect children */
294                 for (;;) {
295                         pid = wait_any_nohang(&wstat);
296                         if (pid <= 0)
297                                 break;
298                         for (i = 0; i < svnum; i++) {
299                                 if (pid == sv[i].pid) {
300                                         /* runsv has died */
301                                         sv[i].pid = 0;
302                                         need_rescan = 1;
303                                 }
304                         }
305                 }
306
307                 now = monotonic_sec();
308                 if ((int)(now - stampcheck) >= 0) {
309                         /* wait at least a second */
310                         stampcheck = now + 1;
311
312                         if (stat(svdir, &s) != -1) {
313                                 if (need_rescan || s.st_mtime != last_mtime
314                                  || s.st_ino != last_ino || s.st_dev != last_dev
315                                 ) {
316                                         /* svdir modified */
317                                         if (chdir(svdir) != -1) {
318                                                 last_mtime = s.st_mtime;
319                                                 last_dev = s.st_dev;
320                                                 last_ino = s.st_ino;
321                                                 /* if the svdir changed this very second, wait until the
322                                                  * next second, because we won't be able to detect more
323                                                  * changes within this second */
324                                                 while (time(NULL) == last_mtime)
325                                                         usleep(100000);
326                                                 need_rescan = do_rescan();
327                                                 while (fchdir(curdir) == -1) {
328                                                         warn2_cannot("change directory, pausing", "");
329                                                         sleep(5);
330                                                 }
331                                         } else {
332                                                 warn2_cannot("change directory to ", svdir);
333                                         }
334                                 }
335                         } else {
336                                 warn2_cannot("stat ", svdir);
337                         }
338                 }
339
340 #if ENABLE_FEATURE_RUNSVDIR_LOG
341                 if (rplog) {
342                         if ((int)(now - stamplog) >= 0) {
343                                 write(logpipe.wr, ".", 1);
344                                 stamplog = now + 900;
345                         }
346                 }
347                 pfd[0].revents = 0;
348 #endif
349                 deadline = (need_rescan ? 1 : 5);
350                 sig_block(SIGCHLD);
351 #if ENABLE_FEATURE_RUNSVDIR_LOG
352                 if (rplog)
353                         poll(pfd, 1, deadline*1000);
354                 else
355 #endif
356                         sleep(deadline);
357                 sig_unblock(SIGCHLD);
358
359 #if ENABLE_FEATURE_RUNSVDIR_LOG
360                 if (pfd[0].revents & POLLIN) {
361                         char ch;
362                         while (read(logpipe.rd, &ch, 1) > 0) {
363                                 if (ch < ' ')
364                                         ch = ' ';
365                                 for (i = 6; i < rploglen; i++)
366                                         rplog[i-1] = rplog[i];
367                                 rplog[rploglen-1] = ch;
368                         }
369                 }
370 #endif
371                 if (!bb_got_signal)
372                         continue;
373
374                 /* -s SCRIPT: useful if we are init.
375                  * In this case typically script never returns,
376                  * it halts/powers off/reboots the system. */
377                 if (opt_s_argv[0]) {
378                         /* Single parameter: signal# */
379                         opt_s_argv[1] = utoa(bb_got_signal);
380                         pid = spawn(opt_s_argv);
381                         if (pid > 0) {
382                                 /* Remembering to wait for _any_ children,
383                                  * not just pid */
384                                 while (wait(NULL) != pid)
385                                         continue;
386                         }
387                 }
388
389                 if (bb_got_signal == SIGHUP) {
390                         for (i = 0; i < svnum; i++)
391                                 if (sv[i].pid)
392                                         kill(sv[i].pid, SIGTERM);
393                 }
394                 /* SIGHUP or SIGTERM (or SIGUSRn if we are init) */
395                 /* Exit unless we are init */
396                 if (getpid() != 1)
397                         return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
398
399                 /* init continues to monitor services forever */
400                 bb_got_signal = 0;
401         } /* for (;;) */
402 }