source: branches/3.2/mindi-busybox/runit/runsvdir.c @ 3232

Last change on this file since 3232 was 3232, checked in by bruno, 5 years ago
  • Update mindi-busybox to 1.21.1
  • Property svn:eol-style set to native
File size: 10.0 KB
Line 
1/*
2Copyright (c) 2001-2006, Gerrit Pape
3All rights reserved.
4
5Redistribution and use in source and binary forms, with or without
6modification, 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
16THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25ADVISED 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
48struct 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
57struct 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
80static 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}
85static 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}
89static void warn2_cannot(const char *m1, const char *m2)
90{
91    warn3x("can't ", m1, m2);
92}
93#if ENABLE_FEATURE_RUNSVDIR_LOG
94static void warnx(const char *m1)
95{
96    warn3x(m1, "", "");
97}
98#endif
99
100/* inlining + vfork -> bigger code */
101static 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 */
135static 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
217int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
218int 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}
Note: See TracBrowser for help on using the repository browser.