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