add packaging
[platform/upstream/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/file.h>
39 #include "libbb.h"
40 #include "runit_lib.h"
41
42 #define MAXSERVICES 1000
43
44 /* Should be not needed - all dirs are on same FS, right? */
45 #define CHECK_DEVNO_TOO 0
46
47 struct service {
48 #if CHECK_DEVNO_TOO
49         dev_t dev;
50 #endif
51         ino_t ino;
52         pid_t pid;
53         smallint isgone;
54 };
55
56 struct globals {
57         struct service *sv;
58         char *svdir;
59         int svnum;
60 #if ENABLE_FEATURE_RUNSVDIR_LOG
61         char *rplog;
62         int rploglen;
63         struct fd_pair logpipe;
64         struct pollfd pfd[1];
65         unsigned stamplog;
66 #endif
67 } FIX_ALIASING;
68 #define G (*(struct globals*)&bb_common_bufsiz1)
69 #define sv          (G.sv          )
70 #define svdir       (G.svdir       )
71 #define svnum       (G.svnum       )
72 #define rplog       (G.rplog       )
73 #define rploglen    (G.rploglen    )
74 #define logpipe     (G.logpipe     )
75 #define pfd         (G.pfd         )
76 #define stamplog    (G.stamplog    )
77 #define INIT_G() do { } while (0)
78
79 static void fatal2_cannot(const char *m1, const char *m2)
80 {
81         bb_perror_msg_and_die("%s: fatal: can't %s%s", svdir, m1, m2);
82         /* was exiting 100 */
83 }
84 static void warn3x(const char *m1, const char *m2, const char *m3)
85 {
86         bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3);
87 }
88 static void warn2_cannot(const char *m1, const char *m2)
89 {
90         warn3x("can't ", m1, m2);
91 }
92 #if ENABLE_FEATURE_RUNSVDIR_LOG
93 static void warnx(const char *m1)
94 {
95         warn3x(m1, "", "");
96 }
97 #endif
98
99 /* inlining + vfork -> bigger code */
100 static NOINLINE pid_t runsv(const char *name)
101 {
102         pid_t pid;
103
104         /* If we got signaled, stop spawning children at once! */
105         if (bb_got_signal)
106                 return 0;
107
108         pid = vfork();
109         if (pid == -1) {
110                 warn2_cannot("vfork", "");
111                 return 0;
112         }
113         if (pid == 0) {
114                 /* child */
115                 if (option_mask32 & 1) /* -P option? */
116                         setsid();
117 /* man execv:
118  * "Signals set to be caught by the calling process image
119  *  shall be set to the default action in the new process image."
120  * Therefore, we do not need this: */
121 #if 0
122                 bb_signals(0
123                         | (1 << SIGHUP)
124                         | (1 << SIGTERM)
125                         , SIG_DFL);
126 #endif
127                 execlp("runsv", "runsv", name, (char *) NULL);
128                 fatal2_cannot("start runsv ", name);
129         }
130         return pid;
131 }
132
133 /* gcc 4.3.0 does better with NOINLINE */
134 static NOINLINE int do_rescan(void)
135 {
136         DIR *dir;
137         struct dirent *d;
138         int i;
139         struct stat s;
140         int need_rescan = 0;
141
142         dir = opendir(".");
143         if (!dir) {
144                 warn2_cannot("open directory ", svdir);
145                 return 1; /* need to rescan again soon */
146         }
147         for (i = 0; i < svnum; i++)
148                 sv[i].isgone = 1;
149
150         while (1) {
151                 errno = 0;
152                 d = readdir(dir);
153                 if (!d)
154                         break;
155                 if (d->d_name[0] == '.')
156                         continue;
157                 if (stat(d->d_name, &s) == -1) {
158                         warn2_cannot("stat ", d->d_name);
159                         continue;
160                 }
161                 if (!S_ISDIR(s.st_mode))
162                         continue;
163                 /* Do we have this service listed already? */
164                 for (i = 0; i < svnum; i++) {
165                         if ((sv[i].ino == s.st_ino)
166 #if CHECK_DEVNO_TOO
167                          && (sv[i].dev == s.st_dev)
168 #endif
169                         ) {
170                                 if (sv[i].pid == 0) /* restart if it has died */
171                                         goto run_ith_sv;
172                                 sv[i].isgone = 0; /* "we still see you" */
173                                 goto next_dentry;
174                         }
175                 }
176                 { /* Not found, make new service */
177                         struct service *svnew = realloc(sv, (i+1) * sizeof(*sv));
178                         if (!svnew) {
179                                 warn2_cannot("start runsv ", d->d_name);
180                                 need_rescan = 1;
181                                 continue;
182                         }
183                         sv = svnew;
184                         svnum++;
185 #if CHECK_DEVNO_TOO
186                         sv[i].dev = s.st_dev;
187 #endif
188                         sv[i].ino = s.st_ino;
189  run_ith_sv:
190                         sv[i].pid = runsv(d->d_name);
191                         sv[i].isgone = 0;
192                 }
193  next_dentry: ;
194         }
195         i = errno;
196         closedir(dir);
197         if (i) { /* readdir failed */
198                 warn2_cannot("read directory ", svdir);
199                 return 1; /* need to rescan again soon */
200         }
201
202         /* Send SIGTERM to runsv whose directories
203          * were no longer found (-> must have been removed) */
204         for (i = 0; i < svnum; i++) {
205                 if (!sv[i].isgone)
206                         continue;
207                 if (sv[i].pid)
208                         kill(sv[i].pid, SIGTERM);
209                 svnum--;
210                 sv[i] = sv[svnum];
211                 i--; /* so that we don't skip new sv[i] (bug was here!) */
212         }
213         return need_rescan;
214 }
215
216 int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
217 int runsvdir_main(int argc UNUSED_PARAM, char **argv)
218 {
219         struct stat s;
220         dev_t last_dev = last_dev; /* for gcc */
221         ino_t last_ino = last_ino; /* for gcc */
222         time_t last_mtime = 0;
223         int wstat;
224         int curdir;
225         pid_t pid;
226         unsigned deadline;
227         unsigned now;
228         unsigned stampcheck;
229         int i;
230         int need_rescan = 1;
231         char *opt_s_argv[3];
232
233         INIT_G();
234
235         opt_complementary = "-1";
236         opt_s_argv[0] = NULL;
237         opt_s_argv[2] = NULL;
238         getopt32(argv, "Ps:", &opt_s_argv[0]);
239         argv += optind;
240
241         bb_signals(0
242                 | (1 << SIGTERM)
243                 | (1 << SIGHUP)
244                 /* For busybox's init, SIGTERM == reboot,
245                  * SIGUSR1 == halt
246                  * SIGUSR2 == poweroff
247                  * so we need to intercept SIGUSRn too.
248                  * Note that we do not implement actual reboot
249                  * (killall(TERM) + umount, etc), we just pause
250                  * respawing and avoid exiting (-> making kernel oops).
251                  * The user is responsible for the rest. */
252                 | (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0)
253                 , record_signo);
254         svdir = *argv++;
255
256 #if ENABLE_FEATURE_RUNSVDIR_LOG
257         /* setup log */
258         if (*argv) {
259                 rplog = *argv;
260                 rploglen = strlen(rplog);
261                 if (rploglen < 7) {
262                         warnx("log must have at least seven characters");
263                 } else if (piped_pair(logpipe)) {
264                         warnx("can't create pipe for log");
265                 } else {
266                         close_on_exec_on(logpipe.rd);
267                         close_on_exec_on(logpipe.wr);
268                         ndelay_on(logpipe.rd);
269                         ndelay_on(logpipe.wr);
270                         if (dup2(logpipe.wr, 2) == -1) {
271                                 warnx("can't set filedescriptor for log");
272                         } else {
273                                 pfd[0].fd = logpipe.rd;
274                                 pfd[0].events = POLLIN;
275                                 stamplog = monotonic_sec();
276                                 goto run;
277                         }
278                 }
279                 rplog = NULL;
280                 warnx("log service disabled");
281         }
282  run:
283 #endif
284         curdir = open(".", O_RDONLY|O_NDELAY);
285         if (curdir == -1)
286                 fatal2_cannot("open current directory", "");
287         close_on_exec_on(curdir);
288
289         stampcheck = monotonic_sec();
290
291         for (;;) {
292                 /* collect children */
293                 for (;;) {
294                         pid = wait_any_nohang(&wstat);
295                         if (pid <= 0)
296                                 break;
297                         for (i = 0; i < svnum; i++) {
298                                 if (pid == sv[i].pid) {
299                                         /* runsv has died */
300                                         sv[i].pid = 0;
301                                         need_rescan = 1;
302                                 }
303                         }
304                 }
305
306                 now = monotonic_sec();
307                 if ((int)(now - stampcheck) >= 0) {
308                         /* wait at least a second */
309                         stampcheck = now + 1;
310
311                         if (stat(svdir, &s) != -1) {
312                                 if (need_rescan || s.st_mtime != last_mtime
313                                  || s.st_ino != last_ino || s.st_dev != last_dev
314                                 ) {
315                                         /* svdir modified */
316                                         if (chdir(svdir) != -1) {
317                                                 last_mtime = s.st_mtime;
318                                                 last_dev = s.st_dev;
319                                                 last_ino = s.st_ino;
320                                                 /* if the svdir changed this very second, wait until the
321                                                  * next second, because we won't be able to detect more
322                                                  * changes within this second */
323                                                 while (time(NULL) == last_mtime)
324                                                         usleep(100000);
325                                                 need_rescan = do_rescan();
326                                                 while (fchdir(curdir) == -1) {
327                                                         warn2_cannot("change directory, pausing", "");
328                                                         sleep(5);
329                                                 }
330                                         } else {
331                                                 warn2_cannot("change directory to ", svdir);
332                                         }
333                                 }
334                         } else {
335                                 warn2_cannot("stat ", svdir);
336                         }
337                 }
338
339 #if ENABLE_FEATURE_RUNSVDIR_LOG
340                 if (rplog) {
341                         if ((int)(now - stamplog) >= 0) {
342                                 write(logpipe.wr, ".", 1);
343                                 stamplog = now + 900;
344                         }
345                 }
346                 pfd[0].revents = 0;
347 #endif
348                 deadline = (need_rescan ? 1 : 5);
349                 sig_block(SIGCHLD);
350 #if ENABLE_FEATURE_RUNSVDIR_LOG
351                 if (rplog)
352                         poll(pfd, 1, deadline*1000);
353                 else
354 #endif
355                         sleep(deadline);
356                 sig_unblock(SIGCHLD);
357
358 #if ENABLE_FEATURE_RUNSVDIR_LOG
359                 if (pfd[0].revents & POLLIN) {
360                         char ch;
361                         while (read(logpipe.rd, &ch, 1) > 0) {
362                                 if (ch < ' ')
363                                         ch = ' ';
364                                 for (i = 6; i < rploglen; i++)
365                                         rplog[i-1] = rplog[i];
366                                 rplog[rploglen-1] = ch;
367                         }
368                 }
369 #endif
370                 if (!bb_got_signal)
371                         continue;
372
373                 /* -s SCRIPT: useful if we are init.
374                  * In this case typically script never returns,
375                  * it halts/powers off/reboots the system. */
376                 if (opt_s_argv[0]) {
377                         /* Single parameter: signal# */
378                         opt_s_argv[1] = utoa(bb_got_signal);
379                         pid = spawn(opt_s_argv);
380                         if (pid > 0) {
381                                 /* Remembering to wait for _any_ children,
382                                  * not just pid */
383                                 while (wait(NULL) != pid)
384                                         continue;
385                         }
386                 }
387
388                 if (bb_got_signal == SIGHUP) {
389                         for (i = 0; i < svnum; i++)
390                                 if (sv[i].pid)
391                                         kill(sv[i].pid, SIGTERM);
392                 }
393                 /* SIGHUP or SIGTERM (or SIGUSRn if we are init) */
394                 /* Exit unless we are init */
395                 if (getpid() != 1)
396                         return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS;
397
398                 /* init continues to monitor services forever */
399                 bb_got_signal = 0;
400         } /* for (;;) */
401 }