busybox/runit/runsvdir.c
<<
>>
Prefs
   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/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
  47struct 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
  56struct 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
  79static 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}
  84static 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}
  88static void warn2_cannot(const char *m1, const char *m2)
  89{
  90        warn3x("can't ", m1, m2);
  91}
  92#if ENABLE_FEATURE_RUNSVDIR_LOG
  93static void warnx(const char *m1)
  94{
  95        warn3x(m1, "", "");
  96}
  97#endif
  98
  99/* inlining + vfork -> bigger code */
 100static 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 */
 134static 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
 216int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 217int 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}
 402