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