From 73b16af8feec390afbabd9356d6e5e83c0390838 Mon Sep 17 00:00:00 2001 From: Bjørn Mork Date: Fri, 15 May 2015 10:20:47 +0200 Subject: busybox: imported from http://www.busybox.net/downloads/busybox-1.13.3.tar.bz2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Bjørn Mork --- runit/Config.in | 83 +++++ runit/Kbuild | 17 + runit/chpst.c | 388 ++++++++++++++++++++ runit/runit_lib.c | 273 ++++++++++++++ runit/runit_lib.h | 105 ++++++ runit/runsv.c | 655 +++++++++++++++++++++++++++++++++ runit/runsvdir.c | 395 ++++++++++++++++++++ runit/sv.c | 598 ++++++++++++++++++++++++++++++ runit/svlogd.c | 1056 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 9 files changed, 3570 insertions(+) create mode 100644 runit/Config.in create mode 100644 runit/Kbuild create mode 100644 runit/chpst.c create mode 100644 runit/runit_lib.c create mode 100644 runit/runit_lib.h create mode 100644 runit/runsv.c create mode 100644 runit/runsvdir.c create mode 100644 runit/sv.c create mode 100644 runit/svlogd.c (limited to 'runit') diff --git a/runit/Config.in b/runit/Config.in new file mode 100644 index 0000000..422ca75 --- /dev/null +++ b/runit/Config.in @@ -0,0 +1,83 @@ +# +# For a description of the syntax of this configuration file, +# see scripts/kbuild/config-language.txt. +# + +menu "Runit Utilities" + +config RUNSV + bool "runsv" + default n + help + runsv starts and monitors a service and optionally an appendant log + service. + +config RUNSVDIR + bool "runsvdir" + default n + help + runsvdir starts a runsv process for each subdirectory, or symlink to + a directory, in the services directory dir, up to a limit of 1000 + subdirectories, and restarts a runsv process if it terminates. + +config FEATURE_RUNSVDIR_LOG + bool "Enable scrolling argument log" + depends on RUNSVDIR + default n + help + Enable feature where second parameter of runsvdir holds last error + message (viewable via top/ps). Otherwise (feature is off + or no parameter), error messages go to stderr only. + +config SV + bool "sv" + default n + help + sv reports the current status and controls the state of services + monitored by the runsv supervisor. + +config SV_DEFAULT_SERVICE_DIR + string "Default directory for services" + default "/var/service" + depends on SV + help + Default directory for services. + Defaults to "/var/service" + +config SVLOGD + bool "svlogd" + default n + help + svlogd continuously reads log data from its standard input, optionally + filters log messages, and writes the data to one or more automatically + rotated logs. + +config CHPST + bool "chpst" + default n + help + chpst changes the process state according to the given options, and + execs specified program. + +config SETUIDGID + bool "setuidgid" + help + Sets soft resource limits as specified by options + +config ENVUIDGID + bool "envuidgid" + help + Sets $UID to account's uid and $GID to account's gid + +config ENVDIR + bool "envdir" + help + Sets various environment variables as specified by files + in the given directory + +config SOFTLIMIT + bool "softlimit" + help + Sets soft resource limits as specified by options + +endmenu diff --git a/runit/Kbuild b/runit/Kbuild new file mode 100644 index 0000000..ab9eef6 --- /dev/null +++ b/runit/Kbuild @@ -0,0 +1,17 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen +# +# Licensed under the GPL v2, see the file LICENSE in this tarball. + +lib-y:= +lib-$(CONFIG_RUNSV) += runsv.o runit_lib.o +lib-$(CONFIG_RUNSVDIR) += runsvdir.o runit_lib.o +lib-$(CONFIG_SV) += sv.o runit_lib.o +lib-$(CONFIG_SVLOGD) += svlogd.o runit_lib.o +lib-$(CONFIG_CHPST) += chpst.o + +lib-$(CONFIG_ENVDIR) += chpst.o +lib-$(CONFIG_ENVUIDGID) += chpst.o +lib-$(CONFIG_SETUIDGID) += chpst.o +lib-$(CONFIG_SOFTLIMIT) += chpst.o diff --git a/runit/chpst.c b/runit/chpst.c new file mode 100644 index 0000000..82a81f5 --- /dev/null +++ b/runit/chpst.c @@ -0,0 +1,388 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denys Vlasenko */ +/* Dependencies on runit_lib.c removed */ + +#include "libbb.h" +#include + +/* +Five applets here: chpst, envdir, envuidgid, setuidgid, softlimit. + +Only softlimit and chpst are taking options: + +# common +-o N Limit number of open files per process +-p N Limit number of processes per uid +-m BYTES Same as -d BYTES -s BYTES -l BYTES [-a BYTES] +-d BYTES Limit data segment +-f BYTES Limit output file sizes +-c BYTES Limit core file size +# softlimit +-a BYTES Limit total size of all segments +-s BYTES Limit stack segment +-l BYTES Limit locked memory size +-r BYTES Limit resident set size +-t N Limit CPU time +# chpst +-u USER[:GRP] Set uid and gid +-U USER[:GRP] Set $UID and $GID in environment +-e DIR Set environment variables as specified by files in DIR +-/ DIR Chroot to DIR +-n NICE Add NICE to nice value +-v Verbose +-P Create new process group +-0 -1 -2 Close fd 0,1,2 + +Even though we accept all these options for both softlimit and chpst, +they are not to be advertised on their help texts. +We have enough problems with feature creep in other people's +software, don't want to add our own. + +envdir, envuidgid, setuidgid take no options, but they reuse code which +handles -e, -U and -u. +*/ + +enum { + OPT_a = (1 << 0) * ENABLE_SOFTLIMIT, + OPT_c = (1 << 1) * (ENABLE_SOFTLIMIT || ENABLE_CHPST), + OPT_d = (1 << 2) * (ENABLE_SOFTLIMIT || ENABLE_CHPST), + OPT_f = (1 << 3) * (ENABLE_SOFTLIMIT || ENABLE_CHPST), + OPT_l = (1 << 4) * ENABLE_SOFTLIMIT, + OPT_m = (1 << 5) * (ENABLE_SOFTLIMIT || ENABLE_CHPST), + OPT_o = (1 << 6) * (ENABLE_SOFTLIMIT || ENABLE_CHPST), + OPT_p = (1 << 7) * (ENABLE_SOFTLIMIT || ENABLE_CHPST), + OPT_r = (1 << 8) * ENABLE_SOFTLIMIT, + OPT_s = (1 << 9) * ENABLE_SOFTLIMIT, + OPT_t = (1 << 10) * ENABLE_SOFTLIMIT, + OPT_u = (1 << 11) * (ENABLE_CHPST || ENABLE_SETUIDGID), + OPT_U = (1 << 12) * (ENABLE_CHPST || ENABLE_ENVUIDGID), + OPT_e = (1 << 13) * (ENABLE_CHPST || ENABLE_ENVDIR), + OPT_root = (1 << 14) * ENABLE_CHPST, + OPT_n = (1 << 15) * ENABLE_CHPST, + OPT_v = (1 << 16) * ENABLE_CHPST, + OPT_P = (1 << 17) * ENABLE_CHPST, + OPT_0 = (1 << 18) * ENABLE_CHPST, + OPT_1 = (1 << 19) * ENABLE_CHPST, + OPT_2 = (1 << 20) * ENABLE_CHPST, +}; + +static void edir(const char *directory_name) +{ + int wdir; + DIR *dir; + struct dirent *d; + int fd; + + wdir = xopen(".", O_RDONLY | O_NDELAY); + xchdir(directory_name); + dir = opendir("."); + if (!dir) + bb_perror_msg_and_die("opendir %s", directory_name); + for (;;) { + char buf[256]; + char *tail; + int size; + + errno = 0; + d = readdir(dir); + if (!d) { + if (errno) + bb_perror_msg_and_die("readdir %s", + directory_name); + break; + } + if (d->d_name[0] == '.') + continue; + fd = open(d->d_name, O_RDONLY | O_NDELAY); + if (fd < 0) { + if ((errno == EISDIR) && directory_name) { + if (option_mask32 & OPT_v) + bb_perror_msg("warning: %s/%s is a directory", + directory_name, d->d_name); + continue; + } else + bb_perror_msg_and_die("open %s/%s", + directory_name, d->d_name); + } + size = full_read(fd, buf, sizeof(buf)-1); + close(fd); + if (size < 0) + bb_perror_msg_and_die("read %s/%s", + directory_name, d->d_name); + if (size == 0) { + unsetenv(d->d_name); + continue; + } + buf[size] = '\n'; + tail = strchr(buf, '\n'); + /* skip trailing whitespace */ + while (1) { + *tail = '\0'; + tail--; + if (tail < buf || !isspace(*tail)) + break; + } + xsetenv(d->d_name, buf); + } + closedir(dir); + if (fchdir(wdir) == -1) + bb_perror_msg_and_die("fchdir"); + close(wdir); +} + +static void limit(int what, long l) +{ + struct rlimit r; + + /* Never fails under Linux (except if you pass it bad arguments) */ + getrlimit(what, &r); + if ((l < 0) || (l > r.rlim_max)) + r.rlim_cur = r.rlim_max; + else + r.rlim_cur = l; + if (setrlimit(what, &r) == -1) + bb_perror_msg_and_die("setrlimit"); +} + +int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int chpst_main(int argc UNUSED_PARAM, char **argv) +{ + struct bb_uidgid_t ugid; + char *set_user = set_user; /* for compiler */ + char *env_user = env_user; + char *env_dir = env_dir; + char *root; + char *nicestr; + unsigned limita; + unsigned limitc; + unsigned limitd; + unsigned limitf; + unsigned limitl; + unsigned limitm; + unsigned limito; + unsigned limitp; + unsigned limitr; + unsigned limits; + unsigned limitt; + unsigned opt; + + if ((ENABLE_CHPST && applet_name[0] == 'c') + || (ENABLE_SOFTLIMIT && applet_name[1] == 'o') + ) { + // FIXME: can we live with int-sized limits? + // can we live with 40000 days? + // if yes -> getopt converts strings to numbers for us + opt_complementary = "-1:a+:c+:d+:f+:l+:m+:o+:p+:r+:s+:t+"; + opt = getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:u:U:e:" + USE_CHPST("/:n:vP012"), + &limita, &limitc, &limitd, &limitf, &limitl, + &limitm, &limito, &limitp, &limitr, &limits, &limitt, + &set_user, &env_user, &env_dir + USE_CHPST(, &root, &nicestr)); + argv += optind; + if (opt & OPT_m) { // -m means -asld + limita = limits = limitl = limitd = limitm; + opt |= (OPT_s | OPT_l | OPT_a | OPT_d); + } + } else { + option_mask32 = opt = 0; + argv++; + if (!*argv) + bb_show_usage(); + } + + // envdir? + if (ENABLE_ENVDIR && applet_name[3] == 'd') { + env_dir = *argv++; + opt |= OPT_e; + } + + // setuidgid? + if (ENABLE_SETUIDGID && applet_name[1] == 'e') { + set_user = *argv++; + opt |= OPT_u; + } + + // envuidgid? + if (ENABLE_ENVUIDGID && applet_name[0] == 'e' && applet_name[3] == 'u') { + env_user = *argv++; + opt |= OPT_U; + } + + // we must have PROG [ARGS] + if (!*argv) + bb_show_usage(); + + // set limits + if (opt & OPT_d) { +#ifdef RLIMIT_DATA + limit(RLIMIT_DATA, limitd); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "DATA"); +#endif + } + if (opt & OPT_s) { +#ifdef RLIMIT_STACK + limit(RLIMIT_STACK, limits); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "STACK"); +#endif + } + if (opt & OPT_l) { +#ifdef RLIMIT_MEMLOCK + limit(RLIMIT_MEMLOCK, limitl); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "MEMLOCK"); +#endif + } + if (opt & OPT_a) { +#ifdef RLIMIT_VMEM + limit(RLIMIT_VMEM, limita); +#else +#ifdef RLIMIT_AS + limit(RLIMIT_AS, limita); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "VMEM"); +#endif +#endif + } + if (opt & OPT_o) { +#ifdef RLIMIT_NOFILE + limit(RLIMIT_NOFILE, limito); +#else +#ifdef RLIMIT_OFILE + limit(RLIMIT_OFILE, limito); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "NOFILE"); +#endif +#endif + } + if (opt & OPT_p) { +#ifdef RLIMIT_NPROC + limit(RLIMIT_NPROC, limitp); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "NPROC"); +#endif + } + if (opt & OPT_f) { +#ifdef RLIMIT_FSIZE + limit(RLIMIT_FSIZE, limitf); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "FSIZE"); +#endif + } + if (opt & OPT_c) { +#ifdef RLIMIT_CORE + limit(RLIMIT_CORE, limitc); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "CORE"); +#endif + } + if (opt & OPT_r) { +#ifdef RLIMIT_RSS + limit(RLIMIT_RSS, limitr); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "RSS"); +#endif + } + if (opt & OPT_t) { +#ifdef RLIMIT_CPU + limit(RLIMIT_CPU, limitt); +#else + if (opt & OPT_v) + bb_error_msg("system does not support RLIMIT_%s", + "CPU"); +#endif + } + + if (opt & OPT_P) + setsid(); + + if (opt & OPT_e) + edir(env_dir); + + // FIXME: chrooted jail must have /etc/passwd if we move this after chroot! + // OTOH chroot fails for non-roots! + // SOLUTION: cache uid/gid before chroot, apply uid/gid after + if (opt & OPT_U) { + xget_uidgid(&ugid, env_user); + xsetenv("GID", utoa(ugid.gid)); + xsetenv("UID", utoa(ugid.uid)); + } + + if (opt & OPT_u) { + xget_uidgid(&ugid, set_user); + } + + if (opt & OPT_root) { + xchdir(root); + xchroot("."); + } + + if (opt & OPT_u) { + if (setgroups(1, &ugid.gid) == -1) + bb_perror_msg_and_die("setgroups"); + xsetgid(ugid.gid); + xsetuid(ugid.uid); + } + + if (opt & OPT_n) { + errno = 0; + if (nice(xatoi(nicestr)) == -1) + bb_perror_msg_and_die("nice"); + } + + if (opt & OPT_0) + close(STDIN_FILENO); + if (opt & OPT_1) + close(STDOUT_FILENO); + if (opt & OPT_2) + close(STDERR_FILENO); + + BB_EXECVP(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} diff --git a/runit/runit_lib.c b/runit/runit_lib.c new file mode 100644 index 0000000..f33619d --- /dev/null +++ b/runit/runit_lib.c @@ -0,0 +1,273 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denys Vlasenko */ +/* Collected into one file from runit's many tiny files */ +/* TODO: review, eliminate unneeded stuff, move good stuff to libbb */ + +#include +#include +#include "libbb.h" +#include "runit_lib.h" + +unsigned byte_chr(char *s,unsigned n,int c) +{ + char ch; + char *t; + + ch = c; + t = s; + for (;;) { + if (!n) break; + if (*t == ch) break; + ++t; + --n; + } + return t - s; +} + +#ifdef UNUSED +static /* as it isn't used anywhere else */ +void tai_pack(char *s, const struct tai *t) +{ + uint64_t x; + + x = t->x; + s[7] = x & 255; x >>= 8; + s[6] = x & 255; x >>= 8; + s[5] = x & 255; x >>= 8; + s[4] = x & 255; x >>= 8; + s[3] = x & 255; x >>= 8; + s[2] = x & 255; x >>= 8; + s[1] = x & 255; x >>= 8; + s[0] = x; +} + +void tai_unpack(const char *s,struct tai *t) +{ + uint64_t x; + + x = (unsigned char) s[0]; + x <<= 8; x += (unsigned char) s[1]; + x <<= 8; x += (unsigned char) s[2]; + x <<= 8; x += (unsigned char) s[3]; + x <<= 8; x += (unsigned char) s[4]; + x <<= 8; x += (unsigned char) s[5]; + x <<= 8; x += (unsigned char) s[6]; + x <<= 8; x += (unsigned char) s[7]; + t->x = x; +} + + +void taia_add(struct taia *t,const struct taia *u,const struct taia *v) +{ + t->sec.x = u->sec.x + v->sec.x; + t->nano = u->nano + v->nano; + t->atto = u->atto + v->atto; + if (t->atto > 999999999UL) { + t->atto -= 1000000000UL; + ++t->nano; + } + if (t->nano > 999999999UL) { + t->nano -= 1000000000UL; + ++t->sec.x; + } +} + +int taia_less(const struct taia *t, const struct taia *u) +{ + if (t->sec.x < u->sec.x) return 1; + if (t->sec.x > u->sec.x) return 0; + if (t->nano < u->nano) return 1; + if (t->nano > u->nano) return 0; + return t->atto < u->atto; +} + +void taia_now(struct taia *t) +{ + struct timeval now; + gettimeofday(&now, NULL); + tai_unix(&t->sec, now.tv_sec); + t->nano = 1000 * now.tv_usec + 500; + t->atto = 0; +} + +/* UNUSED +void taia_pack(char *s, const struct taia *t) +{ + unsigned long x; + + tai_pack(s, &t->sec); + s += 8; + + x = t->atto; + s[7] = x & 255; x >>= 8; + s[6] = x & 255; x >>= 8; + s[5] = x & 255; x >>= 8; + s[4] = x; + x = t->nano; + s[3] = x & 255; x >>= 8; + s[2] = x & 255; x >>= 8; + s[1] = x & 255; x >>= 8; + s[0] = x; +} +*/ + +void taia_sub(struct taia *t, const struct taia *u, const struct taia *v) +{ + unsigned long unano = u->nano; + unsigned long uatto = u->atto; + + t->sec.x = u->sec.x - v->sec.x; + t->nano = unano - v->nano; + t->atto = uatto - v->atto; + if (t->atto > uatto) { + t->atto += 1000000000UL; + --t->nano; + } + if (t->nano > unano) { + t->nano += 1000000000UL; + --t->sec.x; + } +} + +/* XXX: breaks tai encapsulation */ +void taia_uint(struct taia *t, unsigned s) +{ + t->sec.x = s; + t->nano = 0; + t->atto = 0; +} + +static +uint64_t taia2millisec(const struct taia *t) +{ + return (t->sec.x * 1000) + (t->nano / 1000000); +} + +void iopause(iopause_fd *x, unsigned len, struct taia *deadline, struct taia *stamp) +{ + int millisecs; + int i; + + if (taia_less(deadline, stamp)) + millisecs = 0; + else { + uint64_t m; + struct taia t; + t = *stamp; + taia_sub(&t, deadline, &t); + millisecs = m = taia2millisec(&t); + if (m > 1000) millisecs = 1000; + millisecs += 20; + } + + for (i = 0; i < len; ++i) + x[i].revents = 0; + + poll(x, len, millisecs); + /* XXX: some kernels apparently need x[0] even if len is 0 */ + /* XXX: how to handle EAGAIN? are kernels really this dumb? */ + /* XXX: how to handle EINVAL? when exactly can this happen? */ +} +#endif + +int lock_ex(int fd) +{ + return flock(fd,LOCK_EX); +} + +int lock_exnb(int fd) +{ + return flock(fd,LOCK_EX | LOCK_NB); +} + +int open_append(const char *fn) +{ + return open(fn, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); +} + +int open_read(const char *fn) +{ + return open(fn, O_RDONLY|O_NDELAY); +} + +int open_trunc(const char *fn) +{ + return open(fn,O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT,0644); +} + +int open_write(const char *fn) +{ + return open(fn, O_WRONLY|O_NDELAY); +} + +unsigned pmatch(const char *p, const char *s, unsigned len) +{ + for (;;) { + char c = *p++; + if (!c) return !len; + switch (c) { + case '*': + c = *p; + if (!c) return 1; + for (;;) { + if (!len) return 0; + if (*s == c) break; + ++s; + --len; + } + continue; + case '+': + c = *p++; + if (c != *s) return 0; + for (;;) { + if (!len) return 1; + if (*s != c) break; + ++s; + --len; + } + continue; + /* + case '?': + if (*p == '?') { + if (*s != '?') return 0; + ++p; + } + ++s; --len; + continue; + */ + default: + if (!len) return 0; + if (*s != c) return 0; + ++s; + --len; + continue; + } + } + return 0; +} diff --git a/runit/runit_lib.h b/runit/runit_lib.h new file mode 100644 index 0000000..b0b6dc2 --- /dev/null +++ b/runit/runit_lib.h @@ -0,0 +1,105 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility push(hidden) +#endif + +extern unsigned byte_chr(char *s,unsigned n,int c); + +#define direntry struct dirent + +//struct tai { +// uint64_t x; +//}; +// +//#define tai_unix(t,u) ((void) ((t)->x = 0x400000000000000aULL + (uint64_t) (u))) +// +//#define TAI_PACK 8 +//extern void tai_unpack(const char *,struct tai *); +// +//extern void tai_uint(struct tai *,unsigned); +// +//struct taia { +// struct tai sec; +// unsigned long nano; /* 0...999999999 */ +// unsigned long atto; /* 0...999999999 */ +//}; +// +//extern void taia_now(struct taia *); +// +//extern void taia_add(struct taia *,const struct taia *,const struct taia *); +//extern void taia_addsec(struct taia *,const struct taia *,int); +//extern void taia_sub(struct taia *,const struct taia *,const struct taia *); +//extern void taia_half(struct taia *,const struct taia *); +//extern int taia_less(const struct taia *,const struct taia *); +// +//#define TAIA_PACK 16 +//extern void taia_pack(char *,const struct taia *); +// +//extern void taia_uint(struct taia *,unsigned); +// +//typedef struct pollfd iopause_fd; +//#define IOPAUSE_READ POLLIN +//#define IOPAUSE_WRITE POLLOUT +// +//extern void iopause(iopause_fd *,unsigned,struct taia *,struct taia *); + +extern int lock_ex(int); +extern int lock_un(int); +extern int lock_exnb(int); + +extern int open_read(const char *); +extern int open_excl(const char *); +extern int open_append(const char *); +extern int open_trunc(const char *); +extern int open_write(const char *); + +extern unsigned pmatch(const char *, const char *, unsigned); + +#define str_diff(s,t) strcmp((s), (t)) +#define str_equal(s,t) (!strcmp((s), (t))) + +/* + * runsv / supervise / sv stuff + */ +typedef struct svstatus_t { + uint64_t time_be64 PACKED; + uint32_t time_nsec_be32 PACKED; + uint32_t pid_le32 PACKED; + uint8_t paused; + uint8_t want; + uint8_t got_term; + uint8_t run_or_finish; +} svstatus_t; +struct ERR_svstatus_must_be_20_bytes { + char ERR_svstatus_must_be_20_bytes[sizeof(svstatus_t) == 20 ? 1 : -1]; +}; + +#if __GNUC_PREREQ(4,1) +# pragma GCC visibility pop +#endif diff --git a/runit/runsv.c b/runit/runsv.c new file mode 100644 index 0000000..1237208 --- /dev/null +++ b/runit/runsv.c @@ -0,0 +1,655 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denys Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "libbb.h" +#include "runit_lib.h" + +#if ENABLE_MONOTONIC_SYSCALL +#include + +/* libc has incredibly messy way of doing this, + * typically requiring -lrt. We just skip all this mess */ +static void gettimeofday_ns(struct timespec *ts) +{ + syscall(__NR_clock_gettime, CLOCK_REALTIME, ts); +} +#else +static void gettimeofday_ns(struct timespec *ts) +{ + if (sizeof(struct timeval) == sizeof(struct timespec) + && sizeof(((struct timeval*)ts)->tv_usec) == sizeof(ts->tv_nsec) + ) { + /* Cheat */ + gettimeofday((void*)ts, NULL); + ts->tv_nsec *= 1000; + } else { + extern void BUG_need_to_implement_gettimeofday_ns(void); + BUG_need_to_implement_gettimeofday_ns(); + } +} +#endif + +/* Compare possibly overflowing unsigned counters */ +#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0) + +/* state */ +#define S_DOWN 0 +#define S_RUN 1 +#define S_FINISH 2 +/* ctrl */ +#define C_NOOP 0 +#define C_TERM 1 +#define C_PAUSE 2 +/* want */ +#define W_UP 0 +#define W_DOWN 1 +#define W_EXIT 2 + +struct svdir { + int pid; + smallint state; + smallint ctrl; + smallint want; + smallint islog; + struct timespec start; + int fdlock; + int fdcontrol; + int fdcontrolwrite; +}; + +struct globals { + smallint haslog; + smallint sigterm; + smallint pidchanged; + struct fd_pair selfpipe; + struct fd_pair logpipe; + char *dir; + struct svdir svd[2]; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define haslog (G.haslog ) +#define sigterm (G.sigterm ) +#define pidchanged (G.pidchanged ) +#define selfpipe (G.selfpipe ) +#define logpipe (G.logpipe ) +#define dir (G.dir ) +#define svd (G.svd ) +#define INIT_G() do { \ + pidchanged = 1; \ +} while (0) + +static void fatal2_cannot(const char *m1, const char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void fatal_cannot(const char *m) +{ + fatal2_cannot(m, ""); + /* was exiting 111 */ +} +static void fatal2x_cannot(const char *m1, const char *m2) +{ + bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void warn_cannot(const char *m) +{ + bb_perror_msg("%s: warning: cannot %s", dir, m); +} + +static void s_child(int sig_no UNUSED_PARAM) +{ + write(selfpipe.wr, "", 1); +} + +static void s_term(int sig_no UNUSED_PARAM) +{ + sigterm = 1; + write(selfpipe.wr, "", 1); /* XXX */ +} + +static char *add_str(char *p, const char *to_add) +{ + while ((*p = *to_add) != '\0') { + p++; + to_add++; + } + return p; +} + +static int open_trunc_or_warn(const char *name) +{ + int fd = open_trunc(name); + if (fd < 0) + bb_perror_msg("%s: warning: cannot open %s", + dir, name); + return fd; +} + +static void update_status(struct svdir *s) +{ + ssize_t sz; + int fd; + svstatus_t status; + + /* pid */ + if (pidchanged) { + fd = open_trunc_or_warn("supervise/pid.new"); + if (fd < 0) + return; + if (s->pid) { + char spid[sizeof(int)*3 + 2]; + int size = sprintf(spid, "%u\n", (unsigned)s->pid); + write(fd, spid, size); + } + close(fd); + if (rename_or_warn("supervise/pid.new", + s->islog ? "log/supervise/pid" : "log/supervise/pid"+4)) + return; + pidchanged = 0; + } + + /* stat */ + fd = open_trunc_or_warn("supervise/stat.new"); + if (fd < -1) + return; + + { + char stat_buf[sizeof("finish, paused, got TERM, want down\n")]; + char *p = stat_buf; + switch (s->state) { + case S_DOWN: + p = add_str(p, "down"); + break; + case S_RUN: + p = add_str(p, "run"); + break; + case S_FINISH: + p = add_str(p, "finish"); + break; + } + if (s->ctrl & C_PAUSE) p = add_str(p, ", paused"); + if (s->ctrl & C_TERM) p = add_str(p, ", got TERM"); + if (s->state != S_DOWN) + switch (s->want) { + case W_DOWN: + p = add_str(p, ", want down"); + break; + case W_EXIT: + p = add_str(p, ", want exit"); + break; + } + *p++ = '\n'; + write(fd, stat_buf, p - stat_buf); + close(fd); + } + + rename_or_warn("supervise/stat.new", + s->islog ? "log/supervise/stat" : "log/supervise/stat"+4); + + /* supervise compatibility */ + memset(&status, 0, sizeof(status)); + status.time_be64 = SWAP_BE64(s->start.tv_sec + 0x400000000000000aULL); + status.time_nsec_be32 = SWAP_BE32(s->start.tv_nsec); + status.pid_le32 = SWAP_LE32(s->pid); + if (s->ctrl & C_PAUSE) + status.paused = 1; + if (s->want == W_UP) + status.want = 'u'; + else + status.want = 'd'; + if (s->ctrl & C_TERM) + status.got_term = 1; + status.run_or_finish = s->state; + fd = open_trunc_or_warn("supervise/status.new"); + if (fd < 0) + return; + sz = write(fd, &status, sizeof(status)); + close(fd); + if (sz != sizeof(status)) { + warn_cannot("write supervise/status.new"); + unlink("supervise/status.new"); + return; + } + rename_or_warn("supervise/status.new", + s->islog ? "log/supervise/status" : "log/supervise/status"+4); +} + +static unsigned custom(struct svdir *s, char c) +{ + pid_t pid; + int w; + char a[10]; + struct stat st; + char *prog[2]; + + if (s->islog) return 0; + strcpy(a, "control/?"); + a[8] = c; /* replace '?' */ + if (stat(a, &st) == 0) { + if (st.st_mode & S_IXUSR) { + pid = vfork(); + if (pid == -1) { + warn_cannot("vfork for control/?"); + return 0; + } + if (!pid) { + /* child */ + if (haslog && dup2(logpipe.wr, 1) == -1) + warn_cannot("setup stdout for control/?"); + prog[0] = a; + prog[1] = NULL; + execv(a, prog); + fatal_cannot("run control/?"); + } + /* parent */ + while (safe_waitpid(pid, &w, 0) == -1) { + warn_cannot("wait for child control/?"); + return 0; + } + return !wait_exitcode(w); + } + } else { + if (errno != ENOENT) + warn_cannot("stat control/?"); + } + return 0; +} + +static void stopservice(struct svdir *s) +{ + if (s->pid && !custom(s, 't')) { + kill(s->pid, SIGTERM); + s->ctrl |= C_TERM; + update_status(s); + } + if (s->want == W_DOWN) { + kill(s->pid, SIGCONT); + custom(s, 'd'); + return; + } + if (s->want == W_EXIT) { + kill(s->pid, SIGCONT); + custom(s, 'x'); + } +} + +static void startservice(struct svdir *s) +{ + int p; + char *run[2]; + + if (s->state == S_FINISH) + run[0] = (char*)"./finish"; + else { + run[0] = (char*)"./run"; + custom(s, 'u'); + } + run[1] = NULL; + + if (s->pid != 0) + stopservice(s); /* should never happen */ + while ((p = vfork()) == -1) { + warn_cannot("vfork, sleeping"); + sleep(5); + } + if (p == 0) { + /* child */ + if (haslog) { + /* NB: bug alert! right order is close, then dup2 */ + if (s->islog) { + xchdir("./log"); + close(logpipe.wr); + xdup2(logpipe.rd, 0); + } else { + close(logpipe.rd); + xdup2(logpipe.wr, 1); + } + } + /* Non-ignored signals revert to SIG_DFL on exec anyway */ + /*bb_signals(0 + + (1 << SIGCHLD) + + (1 << SIGTERM) + , SIG_DFL);*/ + sig_unblock(SIGCHLD); + sig_unblock(SIGTERM); + execvp(*run, run); + fatal2_cannot(s->islog ? "start log/" : "start ", *run); + } + /* parent */ + if (s->state != S_FINISH) { + gettimeofday_ns(&s->start); + s->state = S_RUN; + } + s->pid = p; + pidchanged = 1; + s->ctrl = C_NOOP; + update_status(s); +} + +static int ctrl(struct svdir *s, char c) +{ + int sig; + + switch (c) { + case 'd': /* down */ + s->want = W_DOWN; + update_status(s); + if (s->pid && s->state != S_FINISH) + stopservice(s); + break; + case 'u': /* up */ + s->want = W_UP; + update_status(s); + if (s->pid == 0) + startservice(s); + break; + case 'x': /* exit */ + if (s->islog) + break; + s->want = W_EXIT; + update_status(s); + /* FALLTHROUGH */ + case 't': /* sig term */ + if (s->pid && s->state != S_FINISH) + stopservice(s); + break; + case 'k': /* sig kill */ + if (s->pid && !custom(s, c)) + kill(s->pid, SIGKILL); + s->state = S_DOWN; + break; + case 'p': /* sig pause */ + if (s->pid && !custom(s, c)) + kill(s->pid, SIGSTOP); + s->ctrl |= C_PAUSE; + update_status(s); + break; + case 'c': /* sig cont */ + if (s->pid && !custom(s, c)) + kill(s->pid, SIGCONT); + if (s->ctrl & C_PAUSE) + s->ctrl &= ~C_PAUSE; + update_status(s); + break; + case 'o': /* once */ + s->want = W_DOWN; + update_status(s); + if (!s->pid) + startservice(s); + break; + case 'a': /* sig alarm */ + sig = SIGALRM; + goto sendsig; + case 'h': /* sig hup */ + sig = SIGHUP; + goto sendsig; + case 'i': /* sig int */ + sig = SIGINT; + goto sendsig; + case 'q': /* sig quit */ + sig = SIGQUIT; + goto sendsig; + case '1': /* sig usr1 */ + sig = SIGUSR1; + goto sendsig; + case '2': /* sig usr2 */ + sig = SIGUSR2; + goto sendsig; + } + return 1; + sendsig: + if (s->pid && !custom(s, c)) + kill(s->pid, sig); + return 1; +} + +int runsv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int runsv_main(int argc UNUSED_PARAM, char **argv) +{ + struct stat s; + int fd; + int r; + char buf[256]; + + INIT_G(); + + if (!argv[1] || argv[2]) + bb_show_usage(); + dir = argv[1]; + + xpiped_pair(selfpipe); + close_on_exec_on(selfpipe.rd); + close_on_exec_on(selfpipe.wr); + ndelay_on(selfpipe.rd); + ndelay_on(selfpipe.wr); + + sig_block(SIGCHLD); + bb_signals_recursive(1 << SIGCHLD, s_child); + sig_block(SIGTERM); + bb_signals_recursive(1 << SIGTERM, s_term); + + xchdir(dir); + /* bss: svd[0].pid = 0; */ + if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */ + if (C_NOOP) svd[0].ctrl = C_NOOP; + if (W_UP) svd[0].want = W_UP; + /* bss: svd[0].islog = 0; */ + /* bss: svd[1].pid = 0; */ + gettimeofday_ns(&svd[0].start); + if (stat("down", &s) != -1) svd[0].want = W_DOWN; + + if (stat("log", &s) == -1) { + if (errno != ENOENT) + warn_cannot("stat ./log"); + } else { + if (!S_ISDIR(s.st_mode)) { + errno = 0; + warn_cannot("stat log/down: log is not a directory"); + } else { + haslog = 1; + svd[1].state = S_DOWN; + svd[1].ctrl = C_NOOP; + svd[1].want = W_UP; + svd[1].islog = 1; + gettimeofday_ns(&svd[1].start); + if (stat("log/down", &s) != -1) + svd[1].want = W_DOWN; + xpiped_pair(logpipe); + close_on_exec_on(logpipe.rd); + close_on_exec_on(logpipe.wr); + } + } + + if (mkdir("supervise", 0700) == -1) { + r = readlink("supervise", buf, sizeof(buf)); + if (r != -1) { + if (r == sizeof(buf)) + fatal2x_cannot("readlink ./supervise", ": name too long"); + buf[r] = 0; + mkdir(buf, 0700); + } else { + if ((errno != ENOENT) && (errno != EINVAL)) + fatal_cannot("readlink ./supervise"); + } + } + svd[0].fdlock = xopen3("log/supervise/lock"+4, + O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if (lock_exnb(svd[0].fdlock) == -1) + fatal_cannot("lock supervise/lock"); + close_on_exec_on(svd[0].fdlock); + if (haslog) { + if (mkdir("log/supervise", 0700) == -1) { + r = readlink("log/supervise", buf, 256); + if (r != -1) { + if (r == 256) + fatal2x_cannot("readlink ./log/supervise", ": name too long"); + buf[r] = 0; + fd = xopen(".", O_RDONLY|O_NDELAY); + xchdir("./log"); + mkdir(buf, 0700); + if (fchdir(fd) == -1) + fatal_cannot("change back to service directory"); + close(fd); + } + else { + if ((errno != ENOENT) && (errno != EINVAL)) + fatal_cannot("readlink ./log/supervise"); + } + } + svd[1].fdlock = xopen3("log/supervise/lock", + O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if (lock_ex(svd[1].fdlock) == -1) + fatal_cannot("lock log/supervise/lock"); + close_on_exec_on(svd[1].fdlock); + } + + mkfifo("log/supervise/control"+4, 0600); + svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY); + close_on_exec_on(svd[0].fdcontrol); + svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY); + close_on_exec_on(svd[0].fdcontrolwrite); + update_status(&svd[0]); + if (haslog) { + mkfifo("log/supervise/control", 0600); + svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY); + close_on_exec_on(svd[1].fdcontrol); + svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY); + close_on_exec_on(svd[1].fdcontrolwrite); + update_status(&svd[1]); + } + mkfifo("log/supervise/ok"+4, 0600); + fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY); + close_on_exec_on(fd); + if (haslog) { + mkfifo("log/supervise/ok", 0600); + fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY); + close_on_exec_on(fd); + } + for (;;) { + struct pollfd x[3]; + unsigned deadline; + char ch; + + if (haslog) + if (!svd[1].pid && svd[1].want == W_UP) + startservice(&svd[1]); + if (!svd[0].pid) + if (svd[0].want == W_UP || svd[0].state == S_FINISH) + startservice(&svd[0]); + + x[0].fd = selfpipe.rd; + x[0].events = POLLIN; + x[1].fd = svd[0].fdcontrol; + x[1].events = POLLIN; + /* x[2] is used only if haslog == 1 */ + x[2].fd = svd[1].fdcontrol; + x[2].events = POLLIN; + sig_unblock(SIGTERM); + sig_unblock(SIGCHLD); + poll(x, 2 + haslog, 3600*1000); + sig_block(SIGTERM); + sig_block(SIGCHLD); + + while (read(selfpipe.rd, &ch, 1) == 1) + continue; + + for (;;) { + pid_t child; + int wstat; + + child = wait_any_nohang(&wstat); + if (!child) + break; + if ((child == -1) && (errno != EINTR)) + break; + if (child == svd[0].pid) { + svd[0].pid = 0; + pidchanged = 1; + svd[0].ctrl &=~ C_TERM; + if (svd[0].state != S_FINISH) { + fd = open_read("finish"); + if (fd != -1) { + close(fd); + svd[0].state = S_FINISH; + update_status(&svd[0]); + continue; + } + } + svd[0].state = S_DOWN; + deadline = svd[0].start.tv_sec + 1; + gettimeofday_ns(&svd[0].start); + update_status(&svd[0]); + if (LESS(svd[0].start.tv_sec, deadline)) + sleep(1); + } + if (haslog) { + if (child == svd[1].pid) { + svd[1].pid = 0; + pidchanged = 1; + svd[1].state = S_DOWN; + svd[1].ctrl &= ~C_TERM; + deadline = svd[1].start.tv_sec + 1; + gettimeofday_ns(&svd[1].start); + update_status(&svd[1]); + if (LESS(svd[1].start.tv_sec, deadline)) + sleep(1); + } + } + } /* for (;;) */ + if (read(svd[0].fdcontrol, &ch, 1) == 1) + ctrl(&svd[0], ch); + if (haslog) + if (read(svd[1].fdcontrol, &ch, 1) == 1) + ctrl(&svd[1], ch); + + if (sigterm) { + ctrl(&svd[0], 'x'); + sigterm = 0; + } + + if (svd[0].want == W_EXIT && svd[0].state == S_DOWN) { + if (svd[1].pid == 0) + _exit(EXIT_SUCCESS); + if (svd[1].want != W_EXIT) { + svd[1].want = W_EXIT; + /* stopservice(&svd[1]); */ + update_status(&svd[1]); + close(logpipe.wr); + close(logpipe.rd); + } + } + } /* for (;;) */ + /* not reached */ + return 0; +} diff --git a/runit/runsvdir.c b/runit/runsvdir.c new file mode 100644 index 0000000..9d560e0 --- /dev/null +++ b/runit/runsvdir.c @@ -0,0 +1,395 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denys Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "libbb.h" +#include "runit_lib.h" + +#define MAXSERVICES 1000 + +/* Should be not needed - all dirs are on same FS, right? */ +#define CHECK_DEVNO_TOO 0 + +struct service { +#if CHECK_DEVNO_TOO + dev_t dev; +#endif + ino_t ino; + pid_t pid; + smallint isgone; +}; + +struct globals { + struct service *sv; + char *svdir; + int svnum; +#if ENABLE_FEATURE_RUNSVDIR_LOG + char *rplog; + int rploglen; + struct fd_pair logpipe; + struct pollfd pfd[1]; + unsigned stamplog; +#endif +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define sv (G.sv ) +#define svdir (G.svdir ) +#define svnum (G.svnum ) +#define rplog (G.rplog ) +#define rploglen (G.rploglen ) +#define logpipe (G.logpipe ) +#define pfd (G.pfd ) +#define stamplog (G.stamplog ) +#define INIT_G() do { \ +} while (0) + +static void fatal2_cannot(const char *m1, const char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2); + /* was exiting 100 */ +} +static void warn3x(const char *m1, const char *m2, const char *m3) +{ + bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3); +} +static void warn2_cannot(const char *m1, const char *m2) +{ + warn3x("cannot ", m1, m2); +} +#if ENABLE_FEATURE_RUNSVDIR_LOG +static void warnx(const char *m1) +{ + warn3x(m1, "", ""); +} +#endif + +/* inlining + vfork -> bigger code */ +static NOINLINE pid_t runsv(const char *name) +{ + pid_t pid; + + /* If we got signaled, stop spawning children at once! */ + if (bb_got_signal) + return 0; + + pid = vfork(); + if (pid == -1) { + warn2_cannot("vfork", ""); + return 0; + } + if (pid == 0) { + /* child */ + if (option_mask32 & 1) /* -P option? */ + setsid(); +/* man execv: + * "Signals set to be caught by the calling process image + * shall be set to the default action in the new process image." + * Therefore, we do not need this: */ +#if 0 + bb_signals(0 + | (1 << SIGHUP) + | (1 << SIGTERM) + , SIG_DFL); +#endif + execlp("runsv", "runsv", name, NULL); + fatal2_cannot("start runsv ", name); + } + return pid; +} + +/* gcc 4.3.0 does better with NOINLINE */ +static NOINLINE int do_rescan(void) +{ + DIR *dir; + direntry *d; + int i; + struct stat s; + int need_rescan = 0; + + dir = opendir("."); + if (!dir) { + warn2_cannot("open directory ", svdir); + return 1; /* need to rescan again soon */ + } + for (i = 0; i < svnum; i++) + sv[i].isgone = 1; + + while (1) { + errno = 0; + d = readdir(dir); + if (!d) + break; + if (d->d_name[0] == '.') + continue; + if (stat(d->d_name, &s) == -1) { + warn2_cannot("stat ", d->d_name); + continue; + } + if (!S_ISDIR(s.st_mode)) + continue; + /* Do we have this service listed already? */ + for (i = 0; i < svnum; i++) { + if ((sv[i].ino == s.st_ino) +#if CHECK_DEVNO_TOO + && (sv[i].dev == s.st_dev) +#endif + ) { + if (sv[i].pid == 0) /* restart if it has died */ + goto run_ith_sv; + sv[i].isgone = 0; /* "we still see you" */ + goto next_dentry; + } + } + { /* Not found, make new service */ + struct service *svnew = realloc(sv, (i+1) * sizeof(*sv)); + if (!svnew) { + warn2_cannot("start runsv ", d->d_name); + need_rescan = 1; + continue; + } + sv = svnew; + svnum++; +#if CHECK_DEVNO_TOO + sv[i].dev = s.st_dev; +#endif + sv[i].ino = s.st_ino; + run_ith_sv: + sv[i].pid = runsv(d->d_name); + sv[i].isgone = 0; + } + next_dentry: ; + } + i = errno; + closedir(dir); + if (i) { /* readdir failed */ + warn2_cannot("read directory ", svdir); + return 1; /* need to rescan again soon */ + } + + /* Send SIGTERM to runsv whose directories + * were no longer found (-> must have been removed) */ + for (i = 0; i < svnum; i++) { + if (!sv[i].isgone) + continue; + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + svnum--; + sv[i] = sv[svnum]; + i--; /* so that we don't skip new sv[i] (bug was here!) */ + } + return need_rescan; +} + +int runsvdir_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int runsvdir_main(int argc UNUSED_PARAM, char **argv) +{ + struct stat s; + dev_t last_dev = last_dev; /* for gcc */ + ino_t last_ino = last_ino; /* for gcc */ + time_t last_mtime = 0; + int wstat; + int curdir; + pid_t pid; + unsigned deadline; + unsigned now; + unsigned stampcheck; + int i; + int need_rescan = 1; + char *opt_s_argv[3]; + + INIT_G(); + + opt_complementary = "-1"; + opt_s_argv[0] = NULL; + opt_s_argv[2] = NULL; + getopt32(argv, "Ps:", &opt_s_argv[0]); + argv += optind; + + bb_signals(0 + | (1 << SIGTERM) + | (1 << SIGHUP) + /* For busybox's init, SIGTERM == reboot, + * SIGUSR1 == halt + * SIGUSR2 == poweroff + * so we need to intercept SIGUSRn too. + * Note that we do not implement actual reboot + * (killall(TERM) + umount, etc), we just pause + * respawing and avoid exiting (-> making kernel oops). + * The user is responsible for the rest. */ + | (getpid() == 1 ? ((1 << SIGUSR1) | (1 << SIGUSR2)) : 0) + , record_signo); + svdir = *argv++; + +#if ENABLE_FEATURE_RUNSVDIR_LOG + /* setup log */ + if (*argv) { + rplog = *argv; + rploglen = strlen(rplog); + if (rploglen < 7) { + warnx("log must have at least seven characters"); + } else if (piped_pair(logpipe)) { + warnx("cannot create pipe for log"); + } else { + close_on_exec_on(logpipe.rd); + close_on_exec_on(logpipe.wr); + ndelay_on(logpipe.rd); + ndelay_on(logpipe.wr); + if (dup2(logpipe.wr, 2) == -1) { + warnx("cannot set filedescriptor for log"); + } else { + pfd[0].fd = logpipe.rd; + pfd[0].events = POLLIN; + stamplog = monotonic_sec(); + goto run; + } + } + rplog = NULL; + warnx("log service disabled"); + } + run: +#endif + curdir = open_read("."); + if (curdir == -1) + fatal2_cannot("open current directory", ""); + close_on_exec_on(curdir); + + stampcheck = monotonic_sec(); + + for (;;) { + /* collect children */ + for (;;) { + pid = wait_any_nohang(&wstat); + if (pid <= 0) + break; + for (i = 0; i < svnum; i++) { + if (pid == sv[i].pid) { + /* runsv has died */ + sv[i].pid = 0; + need_rescan = 1; + } + } + } + + now = monotonic_sec(); + if ((int)(now - stampcheck) >= 0) { + /* wait at least a second */ + stampcheck = now + 1; + + if (stat(svdir, &s) != -1) { + if (need_rescan || s.st_mtime != last_mtime + || s.st_ino != last_ino || s.st_dev != last_dev + ) { + /* svdir modified */ + if (chdir(svdir) != -1) { + last_mtime = s.st_mtime; + last_dev = s.st_dev; + last_ino = s.st_ino; + //if (now <= mtime) + // sleep(1); + need_rescan = do_rescan(); + while (fchdir(curdir) == -1) { + warn2_cannot("change directory, pausing", ""); + sleep(5); + } + } else { + warn2_cannot("change directory to ", svdir); + } + } + } else { + warn2_cannot("stat ", svdir); + } + } + +#if ENABLE_FEATURE_RUNSVDIR_LOG + if (rplog) { + if ((int)(now - stamplog) >= 0) { + write(logpipe.wr, ".", 1); + stamplog = now + 900; + } + } + pfd[0].revents = 0; +#endif + deadline = (need_rescan ? 1 : 5); + sig_block(SIGCHLD); +#if ENABLE_FEATURE_RUNSVDIR_LOG + if (rplog) + poll(pfd, 1, deadline*1000); + else +#endif + sleep(deadline); + sig_unblock(SIGCHLD); + +#if ENABLE_FEATURE_RUNSVDIR_LOG + if (pfd[0].revents & POLLIN) { + char ch; + while (read(logpipe.rd, &ch, 1) > 0) { + if (ch < ' ') + ch = ' '; + for (i = 6; i < rploglen; i++) + rplog[i-1] = rplog[i]; + rplog[rploglen-1] = ch; + } + } +#endif + if (!bb_got_signal) + continue; + + /* -s SCRIPT: useful if we are init. + * In this case typically script never returns, + * it halts/powers off/reboots the system. */ + if (opt_s_argv[0]) { + /* Single parameter: signal# */ + opt_s_argv[1] = utoa(bb_got_signal); + pid = spawn(opt_s_argv); + if (pid > 0) { + /* Remebering to wait for _any_ children, + * not just pid */ + while (wait(NULL) != pid) + continue; + } + } + + switch (bb_got_signal) { + case SIGHUP: + for (i = 0; i < svnum; i++) + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + /* Fall through */ + default: /* SIGTERM (or SIGUSRn if we are init) */ + /* Exit unless we are init */ + if (getpid() == 1) + break; + return (SIGHUP == bb_got_signal) ? 111 : EXIT_SUCCESS; + } + + bb_got_signal = 0; + } /* for (;;) */ +} diff --git a/runit/sv.c b/runit/sv.c new file mode 100644 index 0000000..7e5efde --- /dev/null +++ b/runit/sv.c @@ -0,0 +1,598 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Taken from http://smarden.sunsite.dk/runit/sv.8.html: + +sv - control and manage services monitored by runsv + +sv [-v] [-w sec] command services +/etc/init.d/service [-w sec] command + +The sv program reports the current status and controls the state of services +monitored by the runsv(8) supervisor. + +services consists of one or more arguments, each argument naming a directory +service used by runsv(8). If service doesn't start with a dot or slash, +it is searched in the default services directory /var/service/, otherwise +relative to the current directory. + +command is one of up, down, status, once, pause, cont, hup, alarm, interrupt, +1, 2, term, kill, or exit, or start, stop, restart, shutdown, force-stop, +force-reload, force-restart, force-shutdown. + +The sv program can be sym-linked to /etc/init.d/ to provide an LSB init +script interface. The service to be controlled then is specified by the +base name of the "init script". + +status + Report the current status of the service, and the appendant log service + if available, to standard output. +up + If the service is not running, start it. If the service stops, restart it. +down + If the service is running, send it the TERM signal, and the CONT signal. + If ./run exits, start ./finish if it exists. After it stops, do not + restart service. +once + If the service is not running, start it. Do not restart it if it stops. +pause cont hup alarm interrupt quit 1 2 term kill + If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT, + USR1, USR2, TERM, or KILL signal respectively. +exit + If the service is running, send it the TERM signal, and the CONT signal. + Do not restart the service. If the service is down, and no log service + exists, runsv(8) exits. If the service is down and a log service exists, + send the TERM signal to the log service. If the log service is down, + runsv(8) exits. This command is ignored if it is given to an appendant + log service. + +sv actually looks only at the first character of above commands. + +Commands compatible to LSB init script actions: + +status + Same as status. +start + Same as up, but wait up to 7 seconds for the command to take effect. + Then report the status or timeout. If the script ./check exists in + the service directory, sv runs this script to check whether the service + is up and available; it's considered to be available if ./check exits + with 0. +stop + Same as down, but wait up to 7 seconds for the service to become down. + Then report the status or timeout. +restart + Send the commands term, cont, and up to the service, and wait up to + 7 seconds for the service to restart. Then report the status or timeout. + If the script ./check exists in the service directory, sv runs this script + to check whether the service is up and available again; it's considered + to be available if ./check exits with 0. +shutdown + Same as exit, but wait up to 7 seconds for the runsv(8) process + to terminate. Then report the status or timeout. +force-stop + Same as down, but wait up to 7 seconds for the service to become down. + Then report the status, and on timeout send the service the kill command. +force-reload + Send the service the term and cont commands, and wait up to + 7 seconds for the service to restart. Then report the status, + and on timeout send the service the kill command. +force-restart + Send the service the term, cont and up commands, and wait up to + 7 seconds for the service to restart. Then report the status, and + on timeout send the service the kill command. If the script ./check + exists in the service directory, sv runs this script to check whether + the service is up and available again; it's considered to be available + if ./check exits with 0. +force-shutdown + Same as exit, but wait up to 7 seconds for the runsv(8) process to + terminate. Then report the status, and on timeout send the service + the kill command. + +Additional Commands + +check + Check for the service to be in the state that's been requested. Wait up to + 7 seconds for the service to reach the requested state, then report + the status or timeout. If the requested state of the service is up, + and the script ./check exists in the service directory, sv runs + this script to check whether the service is up and running; + it's considered to be up if ./check exits with 0. + +Options + +-v + wait up to 7 seconds for the command to take effect. + Then report the status or timeout. +-w sec + Override the default timeout of 7 seconds with sec seconds. Implies -v. + +Environment + +SVDIR + The environment variable $SVDIR overrides the default services directory + /var/service. +SVWAIT + The environment variable $SVWAIT overrides the default 7 seconds to wait + for a command to take effect. It is overridden by the -w option. + +Exit Codes + sv exits 0, if the command was successfully sent to all services, and, + if it was told to wait, the command has taken effect to all services. + + For each service that caused an error (e.g. the directory is not + controlled by a runsv(8) process, or sv timed out while waiting), + sv increases the exit code by one and exits non zero. The maximum + is 99. sv exits 100 on error. +*/ + +/* Busyboxed by Denys Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "libbb.h" +#include "runit_lib.h" + +struct globals { + const char *acts; + char **service; + unsigned rc; +/* "Bernstein" time format: unix + 0x400000000000000aULL */ + uint64_t tstart, tnow; + svstatus_t svstatus; +}; +#define G (*(struct globals*)&bb_common_bufsiz1) +#define acts (G.acts ) +#define service (G.service ) +#define rc (G.rc ) +#define tstart (G.tstart ) +#define tnow (G.tnow ) +#define svstatus (G.svstatus ) +#define INIT_G() do { } while (0) + + +static void fatal_cannot(const char *m1) NORETURN; +static void fatal_cannot(const char *m1) +{ + bb_perror_msg("fatal: can't %s", m1); + _exit(151); +} + +static void out(const char *p, const char *m1) +{ + printf("%s%s: %s", p, *service, m1); + if (errno) { + printf(": %s", strerror(errno)); + } + bb_putchar('\n'); /* will also flush the output */ +} + +#define WARN "warning: " +#define OK "ok: " + +static void fail(const char *m1) +{ + ++rc; + out("fail: ", m1); +} +static void failx(const char *m1) +{ + errno = 0; + fail(m1); +} +static void warn(const char *m1) +{ + ++rc; + /* "warning: : \n" */ + out("warning: ", m1); +} +static void ok(const char *m1) +{ + errno = 0; + out(OK, m1); +} + +static int svstatus_get(void) +{ + int fd, r; + + fd = open_write("supervise/ok"); + if (fd == -1) { + if (errno == ENODEV) { + *acts == 'x' ? ok("runsv not running") + : failx("runsv not running"); + return 0; + } + warn("cannot open supervise/ok"); + return -1; + } + close(fd); + fd = open_read("supervise/status"); + if (fd == -1) { + warn("cannot open supervise/status"); + return -1; + } + r = read(fd, &svstatus, 20); + close(fd); + switch (r) { + case 20: + break; + case -1: + warn("cannot read supervise/status"); + return -1; + default: + errno = 0; + warn("cannot read supervise/status: bad format"); + return -1; + } + return 1; +} + +static unsigned svstatus_print(const char *m) +{ + int diff; + int pid; + int normallyup = 0; + struct stat s; + uint64_t timestamp; + + if (stat("down", &s) == -1) { + if (errno != ENOENT) { + bb_perror_msg(WARN"cannot stat %s/down", *service); + return 0; + } + normallyup = 1; + } + pid = SWAP_LE32(svstatus.pid_le32); + timestamp = SWAP_BE64(svstatus.time_be64); + if (pid) { + switch (svstatus.run_or_finish) { + case 1: printf("run: "); break; + case 2: printf("finish: "); break; + } + printf("%s: (pid %d) ", m, pid); + } else { + printf("down: %s: ", m); + } + diff = tnow - timestamp; + printf("%us", (diff < 0 ? 0 : diff)); + if (pid) { + if (!normallyup) printf(", normally down"); + if (svstatus.paused) printf(", paused"); + if (svstatus.want == 'd') printf(", want down"); + if (svstatus.got_term) printf(", got TERM"); + } else { + if (normallyup) printf(", normally up"); + if (svstatus.want == 'u') printf(", want up"); + } + return pid ? 1 : 2; +} + +static int status(const char *unused UNUSED_PARAM) +{ + int r; + + r = svstatus_get(); + switch (r) { case -1: case 0: return 0; } + + r = svstatus_print(*service); + if (chdir("log") == -1) { + if (errno != ENOENT) { + printf("; log: "WARN"cannot change to log service directory: %s", + strerror(errno)); + } + } else if (svstatus_get()) { + printf("; "); + svstatus_print("log"); + } + bb_putchar('\n'); /* will also flush the output */ + return r; +} + +static int checkscript(void) +{ + char *prog[2]; + struct stat s; + int pid, w; + + if (stat("check", &s) == -1) { + if (errno == ENOENT) return 1; + bb_perror_msg(WARN"cannot stat %s/check", *service); + return 0; + } + /* if (!(s.st_mode & S_IXUSR)) return 1; */ + prog[0] = (char*)"./check"; + prog[1] = NULL; + pid = spawn(prog); + if (pid <= 0) { + bb_perror_msg(WARN"cannot %s child %s/check", "run", *service); + return 0; + } + while (safe_waitpid(pid, &w, 0) == -1) { + bb_perror_msg(WARN"cannot %s child %s/check", "wait for", *service); + return 0; + } + return !wait_exitcode(w); +} + +static int check(const char *a) +{ + int r; + unsigned pid; + uint64_t timestamp; + + r = svstatus_get(); + if (r == -1) + return -1; + if (r == 0) { + if (*a == 'x') + return 1; + return -1; + } + pid = SWAP_LE32(svstatus.pid_le32); + switch (*a) { + case 'x': + return 0; + case 'u': + if (!pid || svstatus.run_or_finish != 1) return 0; + if (!checkscript()) return 0; + break; + case 'd': + if (pid) return 0; + break; + case 'c': + if (pid && !checkscript()) return 0; + break; + case 't': + if (!pid && svstatus.want == 'd') break; + timestamp = SWAP_BE64(svstatus.time_be64); + if ((tstart > timestamp) || !pid || svstatus.got_term || !checkscript()) + return 0; + break; + case 'o': + timestamp = SWAP_BE64(svstatus.time_be64); + if ((!pid && tstart > timestamp) || (pid && svstatus.want != 'd')) + return 0; + } + printf(OK); + svstatus_print(*service); + bb_putchar('\n'); /* will also flush the output */ + return 1; +} + +static int control(const char *a) +{ + int fd, r; + + if (svstatus_get() <= 0) + return -1; + if (svstatus.want == *a) + return 0; + fd = open_write("supervise/control"); + if (fd == -1) { + if (errno != ENODEV) + warn("cannot open supervise/control"); + else + *a == 'x' ? ok("runsv not running") : failx("runsv not running"); + return -1; + } + r = write(fd, a, strlen(a)); + close(fd); + if (r != strlen(a)) { + warn("cannot write to supervise/control"); + return -1; + } + return 1; +} + +int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int sv_main(int argc, char **argv) +{ + unsigned opt; + unsigned i, want_exit; + char *x; + char *action; + const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR; + unsigned services; + char **servicex; + unsigned waitsec = 7; + smallint kll = 0; + int verbose = 0; + int (*act)(const char*); + int (*cbk)(const char*); + int curdir; + + INIT_G(); + + xfunc_error_retval = 100; + + x = getenv("SVDIR"); + if (x) varservice = x; + x = getenv("SVWAIT"); + if (x) waitsec = xatou(x); + + opt_complementary = "w+:vv"; /* -w N, -v is a counter */ + opt = getopt32(argv, "w:v", &waitsec, &verbose); + argc -= optind; + argv += optind; + action = *argv++; + if (!action || !*argv) bb_show_usage(); + service = argv; + services = argc - 1; + + tnow = time(0) + 0x400000000000000aULL; + tstart = tnow; + curdir = open_read("."); + if (curdir == -1) + fatal_cannot("open current directory"); + + act = &control; + acts = "s"; + cbk = ✓ + + switch (*action) { + case 'x': + case 'e': + acts = "x"; + if (!verbose) cbk = NULL; + break; + case 'X': + case 'E': + acts = "x"; + kll = 1; + break; + case 'D': + acts = "d"; + kll = 1; + break; + case 'T': + acts = "tc"; + kll = 1; + break; + case 'c': + if (str_equal(action, "check")) { + act = NULL; + acts = "c"; + break; + } + case 'u': case 'd': case 'o': case 't': case 'p': case 'h': + case 'a': case 'i': case 'k': case 'q': case '1': case '2': + action[1] = '\0'; + acts = action; + if (!verbose) cbk = NULL; + break; + case 's': + if (str_equal(action, "shutdown")) { + acts = "x"; + break; + } + if (str_equal(action, "start")) { + acts = "u"; + break; + } + if (str_equal(action, "stop")) { + acts = "d"; + break; + } + /* "status" */ + act = &status; + cbk = NULL; + break; + case 'r': + if (str_equal(action, "restart")) { + acts = "tcu"; + break; + } + bb_show_usage(); + case 'f': + if (str_equal(action, "force-reload")) { + acts = "tc"; + kll = 1; + break; + } + if (str_equal(action, "force-restart")) { + acts = "tcu"; + kll = 1; + break; + } + if (str_equal(action, "force-shutdown")) { + acts = "x"; + kll = 1; + break; + } + if (str_equal(action, "force-stop")) { + acts = "d"; + kll = 1; + break; + } + default: + bb_show_usage(); + } + + servicex = service; + for (i = 0; i < services; ++i) { + if ((**service != '/') && (**service != '.')) { + if (chdir(varservice) == -1) + goto chdir_failed_0; + } + if (chdir(*service) == -1) { + chdir_failed_0: + fail("cannot change to service directory"); + goto nullify_service_0; + } + if (act && (act(acts) == -1)) { + nullify_service_0: + *service = NULL; + } + if (fchdir(curdir) == -1) + fatal_cannot("change to original directory"); + service++; + } + + if (cbk) while (1) { + int diff; + + diff = tnow - tstart; + service = servicex; + want_exit = 1; + for (i = 0; i < services; ++i, ++service) { + if (!*service) + continue; + if ((**service != '/') && (**service != '.')) { + if (chdir(varservice) == -1) + goto chdir_failed; + } + if (chdir(*service) == -1) { + chdir_failed: + fail("cannot change to service directory"); + goto nullify_service; + } + if (cbk(acts) != 0) + goto nullify_service; + want_exit = 0; + if (diff >= waitsec) { + printf(kll ? "kill: " : "timeout: "); + if (svstatus_get() > 0) { + svstatus_print(*service); + ++rc; + } + bb_putchar('\n'); /* will also flush the output */ + if (kll) + control("k"); + nullify_service: + *service = NULL; + } + if (fchdir(curdir) == -1) + fatal_cannot("change to original directory"); + } + if (want_exit) break; + usleep(420000); + tnow = time(0) + 0x400000000000000aULL; + } + return rc > 99 ? 99 : rc; +} diff --git a/runit/svlogd.c b/runit/svlogd.c new file mode 100644 index 0000000..9beb9f5 --- /dev/null +++ b/runit/svlogd.c @@ -0,0 +1,1056 @@ +/* +Copyright (c) 2001-2006, Gerrit Pape +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + + 1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + 3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED +WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO +EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* Busyboxed by Denys Vlasenko */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include +#include +#include "libbb.h" +#include "runit_lib.h" + +#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0) + +#define FMT_PTIME 30 + +struct logdir { + ////char *btmp; + /* pattern list to match, in "aa\0bb\0\cc\0\0" form */ + char *inst; + char *processor; + char *name; + unsigned size; + unsigned sizemax; + unsigned nmax; + unsigned nmin; + unsigned rotate_period; + int ppid; + int fddir; + int fdcur; + FILE* filecur; //// + int fdlock; + unsigned next_rotate; + char fnsave[FMT_PTIME]; + char match; + char matcherr; +}; + + +struct globals { + struct logdir *dir; + unsigned verbose; + int linemax; + ////int buflen; + int linelen; + + int fdwdir; + char **fndir; + int wstat; + unsigned nearest_rotate; + + smallint exitasap; + smallint rotateasap; + smallint reopenasap; + smallint linecomplete; + smallint tmaxflag; + + char repl; + const char *replace; + int fl_flag_0; + unsigned dirn; + + sigset_t blocked_sigset; +}; +#define G (*(struct globals*)ptr_to_globals) +#define dir (G.dir ) +#define verbose (G.verbose ) +#define linemax (G.linemax ) +#define buflen (G.buflen ) +#define linelen (G.linelen ) +#define fndir (G.fndir ) +#define fdwdir (G.fdwdir ) +#define wstat (G.wstat ) +#define nearest_rotate (G.nearest_rotate) +#define exitasap (G.exitasap ) +#define rotateasap (G.rotateasap ) +#define reopenasap (G.reopenasap ) +#define linecomplete (G.linecomplete ) +#define tmaxflag (G.tmaxflag ) +#define repl (G.repl ) +#define replace (G.replace ) +#define blocked_sigset (G.blocked_sigset) +#define fl_flag_0 (G.fl_flag_0 ) +#define dirn (G.dirn ) +#define INIT_G() do { \ + SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \ + linemax = 1000; \ + /*buflen = 1024;*/ \ + linecomplete = 1; \ + replace = ""; \ +} while (0) + +#define line bb_common_bufsiz1 + + +#define FATAL "fatal: " +#define WARNING "warning: " +#define PAUSE "pausing: " +#define INFO "info: " + +#define usage() bb_show_usage() +static void fatalx(const char *m0) +{ + bb_error_msg_and_die(FATAL"%s", m0); +} +static void warn(const char *m0) +{ + bb_perror_msg(WARNING"%s", m0); +} +static void warn2(const char *m0, const char *m1) +{ + bb_perror_msg(WARNING"%s: %s", m0, m1); +} +static void warnx(const char *m0, const char *m1) +{ + bb_error_msg(WARNING"%s: %s", m0, m1); +} +static void pause_nomem(void) +{ + bb_error_msg(PAUSE"out of memory"); + sleep(3); +} +static void pause1cannot(const char *m0) +{ + bb_perror_msg(PAUSE"can't %s", m0); + sleep(3); +} +static void pause2cannot(const char *m0, const char *m1) +{ + bb_perror_msg(PAUSE"can't %s %s", m0, m1); + sleep(3); +} + +static char* wstrdup(const char *str) +{ + char *s; + while (!(s = strdup(str))) + pause_nomem(); + return s; +} + +/*** ex fmt_ptime.[ch] ***/ + +/* NUL terminated */ +static void fmt_time_human_30nul(char *s) +{ + struct tm *t; + struct timeval tv; + + gettimeofday(&tv, NULL); + t = gmtime(&(tv.tv_sec)); + sprintf(s, "%04u-%02u-%02u_%02u:%02u:%02u.%06u000", + (unsigned)(1900 + t->tm_year), + (unsigned)(t->tm_mon + 1), + (unsigned)(t->tm_mday), + (unsigned)(t->tm_hour), + (unsigned)(t->tm_min), + (unsigned)(t->tm_sec), + (unsigned)(tv.tv_usec) + ); + /* 4+1 + 2+1 + 2+1 + 2+1 + 2+1 + 2+1 + 9 = */ + /* 5 + 3 + 3 + 3 + 3 + 3 + 9 = */ + /* 20 (up to '.' inclusive) + 9 (not including '\0') */ +} + +/* NOT terminated! */ +static void fmt_time_bernstein_25(char *s) +{ + uint32_t pack[3]; + struct timeval tv; + unsigned sec_hi; + + gettimeofday(&tv, NULL); + sec_hi = (0x400000000000000aULL + tv.tv_sec) >> 32; + tv.tv_sec = (time_t)(0x400000000000000aULL) + tv.tv_sec; + tv.tv_usec *= 1000; + /* Network order is big-endian: most significant byte first. + * This is exactly what we want here */ + pack[0] = htonl(sec_hi); + pack[1] = htonl(tv.tv_sec); + pack[2] = htonl(tv.tv_usec); + *s++ = '@'; + bin2hex(s, (char*)pack, 12); +} + +static void processorstart(struct logdir *ld) +{ + char sv_ch; + int pid; + + if (!ld->processor) return; + if (ld->ppid) { + warnx("processor already running", ld->name); + return; + } + + /* vfork'ed child trashes this byte, save... */ + sv_ch = ld->fnsave[26]; + + while ((pid = vfork()) == -1) + pause2cannot("vfork for processor", ld->name); + if (!pid) { + char *prog[4]; + int fd; + + /* child */ + /* Non-ignored signals revert to SIG_DFL on exec anyway */ + /*bb_signals(0 + + (1 << SIGTERM) + + (1 << SIGALRM) + + (1 << SIGHUP) + , SIG_DFL);*/ + sig_unblock(SIGTERM); + sig_unblock(SIGALRM); + sig_unblock(SIGHUP); + + if (verbose) + bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave); + fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY); + xmove_fd(fd, 0); + ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */ + fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT); + xmove_fd(fd, 1); + fd = open_read("state"); + if (fd == -1) { + if (errno != ENOENT) + bb_perror_msg_and_die(FATAL"can't %s processor %s", "open state for", ld->name); + close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT)); + fd = xopen("state", O_RDONLY|O_NDELAY); + } + xmove_fd(fd, 4); + fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT); + xmove_fd(fd, 5); + +// getenv("SHELL")? + prog[0] = (char*)"sh"; + prog[1] = (char*)"-c"; + prog[2] = ld->processor; + prog[3] = NULL; + execv("/bin/sh", prog); + bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name); + } + ld->fnsave[26] = sv_ch; /* ...restore */ + ld->ppid = pid; +} + +static unsigned processorstop(struct logdir *ld) +{ + char f[28]; + + if (ld->ppid) { + sig_unblock(SIGHUP); + while (safe_waitpid(ld->ppid, &wstat, 0) == -1) + pause2cannot("wait for processor", ld->name); + sig_block(SIGHUP); + ld->ppid = 0; + } + if (ld->fddir == -1) return 1; + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want processor", ld->name); + if (wait_exitcode(wstat) != 0) { + warnx("processor failed, restart", ld->name); + ld->fnsave[26] = 't'; + unlink(ld->fnsave); + ld->fnsave[26] = 'u'; + processorstart(ld); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return ld->processor ? 0 : 1; + } + ld->fnsave[26] = 't'; + memcpy(f, ld->fnsave, 26); + f[26] = 's'; + f[27] = '\0'; + while (rename(ld->fnsave, f) == -1) + pause2cannot("rename processed", ld->name); + while (chmod(f, 0744) == -1) + pause2cannot("set mode of processed", ld->name); + ld->fnsave[26] = 'u'; + if (unlink(ld->fnsave) == -1) + bb_error_msg(WARNING"can't unlink: %s/%s", ld->name, ld->fnsave); + while (rename("newstate", "state") == -1) + pause2cannot("rename state", ld->name); + if (verbose) + bb_error_msg(INFO"processed: %s/%s", ld->name, f); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static void rmoldest(struct logdir *ld) +{ + DIR *d; + struct dirent *f; + char oldest[FMT_PTIME]; + int n = 0; + + oldest[0] = 'A'; oldest[1] = oldest[27] = 0; + while (!(d = opendir("."))) + pause2cannot("open directory, want rotate", ld->name); + errno = 0; + while ((f = readdir(d))) { + if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) { + if (f->d_name[26] == 't') { + if (unlink(f->d_name) == -1) + warn2("can't unlink processor leftover", f->d_name); + } else { + ++n; + if (strcmp(f->d_name, oldest) < 0) + memcpy(oldest, f->d_name, 27); + } + errno = 0; + } + } + if (errno) + warn2("can't read directory", ld->name); + closedir(d); + + if (ld->nmax && (n > ld->nmax)) { + if (verbose) + bb_error_msg(INFO"delete: %s/%s", ld->name, oldest); + if ((*oldest == '@') && (unlink(oldest) == -1)) + warn2("can't unlink oldest logfile", ld->name); + } +} + +static unsigned rotate(struct logdir *ld) +{ + struct stat st; + unsigned now; + + if (ld->fddir == -1) { + ld->rotate_period = 0; + return 0; + } + if (ld->ppid) + while (!processorstop(ld)) + continue; + + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want rotate", ld->name); + + /* create new filename */ + ld->fnsave[25] = '.'; + ld->fnsave[26] = 's'; + if (ld->processor) + ld->fnsave[26] = 'u'; + ld->fnsave[27] = '\0'; + do { + fmt_time_bernstein_25(ld->fnsave); + errno = 0; + stat(ld->fnsave, &st); + } while (errno != ENOENT); + + now = monotonic_sec(); + if (ld->rotate_period && LESS(ld->next_rotate, now)) { + ld->next_rotate = now + ld->rotate_period; + if (LESS(ld->next_rotate, nearest_rotate)) + nearest_rotate = ld->next_rotate; + } + + if (ld->size > 0) { + while (fflush(ld->filecur) || fsync(ld->fdcur) == -1) + pause2cannot("fsync current logfile", ld->name); + while (fchmod(ld->fdcur, 0744) == -1) + pause2cannot("set mode of current", ld->name); + ////close(ld->fdcur); + fclose(ld->filecur); + + if (verbose) { + bb_error_msg(INFO"rename: %s/current %s %u", ld->name, + ld->fnsave, ld->size); + } + while (rename("current", ld->fnsave) == -1) + pause2cannot("rename current", ld->name); + while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1) + pause2cannot("create new current", ld->name); + /* we presume this cannot fail */ + ld->filecur = fdopen(ld->fdcur, "a"); //// + setvbuf(ld->filecur, NULL, _IOFBF, linelen); //// + close_on_exec_on(ld->fdcur); + ld->size = 0; + while (fchmod(ld->fdcur, 0644) == -1) + pause2cannot("set mode of current", ld->name); + rmoldest(ld); + processorstart(ld); + } + + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static int buffer_pwrite(int n, char *s, unsigned len) +{ + int i; + struct logdir *ld = &dir[n]; + + if (ld->sizemax) { + if (ld->size >= ld->sizemax) + rotate(ld); + if (len > (ld->sizemax - ld->size)) + len = ld->sizemax - ld->size; + } + while (1) { + ////i = full_write(ld->fdcur, s, len); + ////if (i != -1) break; + i = fwrite(s, 1, len, ld->filecur); + if (i == len) break; + + if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) { + DIR *d; + struct dirent *f; + char oldest[FMT_PTIME]; + int j = 0; + + while (fchdir(ld->fddir) == -1) + pause2cannot("change directory, want remove old logfile", + ld->name); + oldest[0] = 'A'; + oldest[1] = oldest[27] = '\0'; + while (!(d = opendir("."))) + pause2cannot("open directory, want remove old logfile", + ld->name); + errno = 0; + while ((f = readdir(d))) + if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) { + ++j; + if (strcmp(f->d_name, oldest) < 0) + memcpy(oldest, f->d_name, 27); + } + if (errno) warn2("can't read directory, want remove old logfile", + ld->name); + closedir(d); + errno = ENOSPC; + if (j > ld->nmin) { + if (*oldest == '@') { + bb_error_msg(WARNING"out of disk space, delete: %s/%s", + ld->name, oldest); + errno = 0; + if (unlink(oldest) == -1) { + warn2("can't unlink oldest logfile", ld->name); + errno = ENOSPC; + } + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + } + } + } + if (errno) + pause2cannot("write to current", ld->name); + } + + ld->size += i; + if (ld->sizemax) + if (s[i-1] == '\n') + if (ld->size >= (ld->sizemax - linemax)) + rotate(ld); + return i; +} + +static void logdir_close(struct logdir *ld) +{ + if (ld->fddir == -1) + return; + if (verbose) + bb_error_msg(INFO"close: %s", ld->name); + close(ld->fddir); + ld->fddir = -1; + if (ld->fdcur == -1) + return; /* impossible */ + while (fflush(ld->filecur) || fsync(ld->fdcur) == -1) + pause2cannot("fsync current logfile", ld->name); + while (fchmod(ld->fdcur, 0744) == -1) + pause2cannot("set mode of current", ld->name); + ////close(ld->fdcur); + fclose(ld->filecur); + ld->fdcur = -1; + if (ld->fdlock == -1) + return; /* impossible */ + close(ld->fdlock); + ld->fdlock = -1; + free(ld->processor); + ld->processor = NULL; +} + +static unsigned logdir_open(struct logdir *ld, const char *fn) +{ + char buf[128]; + unsigned now; + char *new, *s, *np; + int i; + struct stat st; + + now = monotonic_sec(); + + ld->fddir = open(fn, O_RDONLY|O_NDELAY); + if (ld->fddir == -1) { + warn2("can't open log directory", (char*)fn); + return 0; + } + close_on_exec_on(ld->fddir); + if (fchdir(ld->fddir) == -1) { + logdir_close(ld); + warn2("can't change directory", (char*)fn); + return 0; + } + ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); + if ((ld->fdlock == -1) + || (lock_exnb(ld->fdlock) == -1) + ) { + logdir_close(ld); + warn2("can't lock directory", (char*)fn); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 0; + } + close_on_exec_on(ld->fdlock); + + ld->size = 0; + ld->sizemax = 1000000; + ld->nmax = ld->nmin = 10; + ld->rotate_period = 0; + ld->name = (char*)fn; + ld->ppid = 0; + ld->match = '+'; + free(ld->inst); ld->inst = NULL; + free(ld->processor); ld->processor = NULL; + + /* read config */ + i = open_read_close("config", buf, sizeof(buf)); + if (i < 0 && errno != ENOENT) + bb_perror_msg(WARNING"%s/config", ld->name); + if (i > 0) { + if (verbose) + bb_error_msg(INFO"read: %s/config", ld->name); + s = buf; + while (s) { + np = strchr(s, '\n'); + if (np) + *np++ = '\0'; + switch (s[0]) { + case '+': + case '-': + case 'e': + case 'E': + /* Add '\n'-terminated line to ld->inst */ + while (1) { + int l = asprintf(&new, "%s%s\n", ld->inst ? : "", s); + if (l >= 0 && new) + break; + pause_nomem(); + } + free(ld->inst); + ld->inst = new; + break; + case 's': { + static const struct suffix_mult km_suffixes[] = { + { "k", 1024 }, + { "m", 1024*1024 }, + { } + }; + ld->sizemax = xatou_sfx(&s[1], km_suffixes); + break; + } + case 'n': + ld->nmax = xatoi_u(&s[1]); + break; + case 'N': + ld->nmin = xatoi_u(&s[1]); + break; + case 't': { + static const struct suffix_mult mh_suffixes[] = { + { "m", 60 }, + { "h", 60*60 }, + /*{ "d", 24*60*60 },*/ + { } + }; + ld->rotate_period = xatou_sfx(&s[1], mh_suffixes); + if (ld->rotate_period) { + ld->next_rotate = now + ld->rotate_period; + if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate)) + nearest_rotate = ld->next_rotate; + tmaxflag = 1; + } + break; + } + case '!': + if (s[1]) { + free(ld->processor); + ld->processor = wstrdup(s); + } + break; + } + s = np; + } + /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */ + s = ld->inst; + while (s) { + np = strchr(s, '\n'); + if (np) + *np++ = '\0'; + s = np; + } + } + + /* open current */ + i = stat("current", &st); + if (i != -1) { + if (st.st_size && !(st.st_mode & S_IXUSR)) { + ld->fnsave[25] = '.'; + ld->fnsave[26] = 'u'; + ld->fnsave[27] = '\0'; + do { + fmt_time_bernstein_25(ld->fnsave); + errno = 0; + stat(ld->fnsave, &st); + } while (errno != ENOENT); + while (rename("current", ld->fnsave) == -1) + pause2cannot("rename current", ld->name); + rmoldest(ld); + i = -1; + } else { + /* st.st_size can be not just bigger, but WIDER! + * This code is safe: if st.st_size > 4GB, we select + * ld->sizemax (because it's "unsigned") */ + ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size; + } + } else { + if (errno != ENOENT) { + logdir_close(ld); + warn2("can't stat current", ld->name); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 0; + } + } + while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1) + pause2cannot("open current", ld->name); + /* we presume this cannot fail */ + ld->filecur = fdopen(ld->fdcur, "a"); //// + setvbuf(ld->filecur, NULL, _IOFBF, linelen); //// + + close_on_exec_on(ld->fdcur); + while (fchmod(ld->fdcur, 0644) == -1) + pause2cannot("set mode of current", ld->name); + + if (verbose) { + if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name); + else bb_error_msg(INFO"new: %s/current", ld->name); + } + + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 1; +} + +static void logdirs_reopen(void) +{ + int l; + int ok = 0; + + tmaxflag = 0; + for (l = 0; l < dirn; ++l) { + logdir_close(&dir[l]); + if (logdir_open(&dir[l], fndir[l])) + ok = 1; + } + if (!ok) + fatalx("no functional log directories"); +} + +/* Will look good in libbb one day */ +static ssize_t ndelay_read(int fd, void *buf, size_t count) +{ + if (!(fl_flag_0 & O_NONBLOCK)) + fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK); + count = safe_read(fd, buf, count); + if (!(fl_flag_0 & O_NONBLOCK)) + fcntl(fd, F_SETFL, fl_flag_0); + return count; +} + +/* Used for reading stdin */ +static int buffer_pread(/*int fd, */char *s, unsigned len) +{ + unsigned now; + struct pollfd input; + int i; + + input.fd = 0; + input.events = POLLIN; + + do { + if (rotateasap) { + for (i = 0; i < dirn; ++i) + rotate(dir + i); + rotateasap = 0; + } + if (exitasap) { + if (linecomplete) + return 0; + len = 1; + } + if (reopenasap) { + logdirs_reopen(); + reopenasap = 0; + } + now = monotonic_sec(); + nearest_rotate = now + (45 * 60 + 45); + for (i = 0; i < dirn; ++i) { + if (dir[i].rotate_period) { + if (LESS(dir[i].next_rotate, now)) + rotate(dir + i); + if (LESS(dir[i].next_rotate, nearest_rotate)) + nearest_rotate = dir[i].next_rotate; + } + } + + sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL); + i = nearest_rotate - now; + if (i > 1000000) + i = 1000000; + if (i <= 0) + i = 1; + poll(&input, 1, i * 1000); + sigprocmask(SIG_BLOCK, &blocked_sigset, NULL); + + i = ndelay_read(STDIN_FILENO, s, len); + if (i >= 0) + break; + if (errno == EINTR) + continue; + if (errno != EAGAIN) { + warn("can't read standard input"); + break; + } + /* else: EAGAIN - normal, repeat silently */ + } while (!exitasap); + + if (i > 0) { + int cnt; + linecomplete = (s[i-1] == '\n'); + if (!repl) + return i; + + cnt = i; + while (--cnt >= 0) { + char ch = *s; + if (ch != '\n') { + if (ch < 32 || ch > 126) + *s = repl; + else { + int j; + for (j = 0; replace[j]; ++j) { + if (ch == replace[j]) { + *s = repl; + break; + } + } + } + } + s++; + } + } + return i; +} + +static void sig_term_handler(int sig_no UNUSED_PARAM) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "term"); + exitasap = 1; +} + +static void sig_child_handler(int sig_no UNUSED_PARAM) +{ + pid_t pid; + int l; + + if (verbose) + bb_error_msg(INFO"sig%s received", "child"); + while ((pid = wait_any_nohang(&wstat)) > 0) { + for (l = 0; l < dirn; ++l) { + if (dir[l].ppid == pid) { + dir[l].ppid = 0; + processorstop(&dir[l]); + break; + } + } + } +} + +static void sig_alarm_handler(int sig_no UNUSED_PARAM) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "alarm"); + rotateasap = 1; +} + +static void sig_hangup_handler(int sig_no UNUSED_PARAM) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "hangup"); + reopenasap = 1; +} + +static void logmatch(struct logdir *ld) +{ + char *s; + + ld->match = '+'; + ld->matcherr = 'E'; + s = ld->inst; + while (s && s[0]) { + switch (s[0]) { + case '+': + case '-': + if (pmatch(s+1, line, linelen)) + ld->match = s[0]; + break; + case 'e': + case 'E': + if (pmatch(s+1, line, linelen)) + ld->matcherr = s[0]; + break; + } + s += strlen(s) + 1; + } +} + +int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int svlogd_main(int argc, char **argv) +{ + char *r,*l,*b; + ssize_t stdin_cnt = 0; + int i; + unsigned opt; + unsigned timestamp = 0; + void* (*memRchr)(const void *, int, size_t) = memchr; + + INIT_G(); + + opt_complementary = "tt:vv"; + opt = getopt32(argv, "r:R:l:b:tv", + &r, &replace, &l, &b, ×tamp, &verbose); + if (opt & 1) { // -r + repl = r[0]; + if (!repl || r[1]) usage(); + } + if (opt & 2) if (!repl) repl = '_'; // -R + if (opt & 4) { // -l + linemax = xatou_range(l, 0, BUFSIZ-26); + if (linemax == 0) linemax = BUFSIZ-26; + if (linemax < 256) linemax = 256; + } + ////if (opt & 8) { // -b + //// buflen = xatoi_u(b); + //// if (buflen == 0) buflen = 1024; + ////} + //if (opt & 0x10) timestamp++; // -t + //if (opt & 0x20) verbose++; // -v + //if (timestamp > 2) timestamp = 2; + argv += optind; + argc -= optind; + + dirn = argc; + if (dirn <= 0) usage(); + ////if (buflen <= linemax) usage(); + fdwdir = xopen(".", O_RDONLY|O_NDELAY); + close_on_exec_on(fdwdir); + dir = xzalloc(dirn * sizeof(struct logdir)); + for (i = 0; i < dirn; ++i) { + dir[i].fddir = -1; + dir[i].fdcur = -1; + ////dir[i].btmp = xmalloc(buflen); + /*dir[i].ppid = 0;*/ + } + /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */ + fndir = argv; + /* We cannot set NONBLOCK on fd #0 permanently - this setting + * _isn't_ per-process! It is shared among all other processes + * with the same stdin */ + fl_flag_0 = fcntl(0, F_GETFL); + + sigemptyset(&blocked_sigset); + sigaddset(&blocked_sigset, SIGTERM); + sigaddset(&blocked_sigset, SIGCHLD); + sigaddset(&blocked_sigset, SIGALRM); + sigaddset(&blocked_sigset, SIGHUP); + sigprocmask(SIG_BLOCK, &blocked_sigset, NULL); + bb_signals_recursive(1 << SIGTERM, sig_term_handler); + bb_signals_recursive(1 << SIGCHLD, sig_child_handler); + bb_signals_recursive(1 << SIGALRM, sig_alarm_handler); + bb_signals_recursive(1 << SIGHUP, sig_hangup_handler); + + logdirs_reopen(); + + /* Without timestamps, we don't have to print each line + * separately, so we can look for _last_ newline, not first, + * thus batching writes */ + if (!timestamp) + memRchr = memrchr; + + setvbuf(stderr, NULL, _IOFBF, linelen); + + /* Each iteration processes one or more lines */ + while (1) { + char stamp[FMT_PTIME]; + char *lineptr; + char *printptr; + char *np; + int printlen; + char ch; + + lineptr = line; + if (timestamp) + lineptr += 26; + + /* lineptr[0..linemax-1] - buffer for stdin */ + /* (possibly has some unprocessed data from prev loop) */ + + /* Refill the buffer if needed */ + np = memRchr(lineptr, '\n', stdin_cnt); + if (!np && !exitasap) { + i = linemax - stdin_cnt; /* avail. bytes at tail */ + if (i >= 128) { + i = buffer_pread(/*0, */lineptr + stdin_cnt, i); + if (i <= 0) /* EOF or error on stdin */ + exitasap = 1; + else { + np = memRchr(lineptr + stdin_cnt, '\n', i); + stdin_cnt += i; + } + } + } + if (stdin_cnt <= 0 && exitasap) + break; + + /* Search for '\n' (in fact, np already holds the result) */ + linelen = stdin_cnt; + if (np) { + print_to_nl: /* NB: starting from here lineptr may point + * farther out into line[] */ + linelen = np - lineptr + 1; + } + /* linelen == no of chars incl. '\n' (or == stdin_cnt) */ + ch = lineptr[linelen-1]; + + /* Biggest performance hit was coming from the fact + * that we did not buffer writes. We were reading many lines + * in one read() above, but wrote one line per write(). + * We are using stdio to fix that */ + + /* write out lineptr[0..linelen-1] to each log destination + * (or lineptr[-26..linelen-1] if timestamping) */ + printlen = linelen; + printptr = lineptr; + if (timestamp) { + if (timestamp == 1) + fmt_time_bernstein_25(stamp); + else /* 2: */ + fmt_time_human_30nul(stamp); + printlen += 26; + printptr -= 26; + memcpy(printptr, stamp, 25); + printptr[25] = ' '; + } + for (i = 0; i < dirn; ++i) { + struct logdir *ld = &dir[i]; + if (ld->fddir == -1) continue; + if (ld->inst) + logmatch(ld); + if (ld->matcherr == 'e') { + /* runit-1.8.0 compat: if timestamping, do it on stderr too */ + ////full_write(STDERR_FILENO, printptr, printlen); + fwrite(printptr, 1, printlen, stderr); + } + if (ld->match != '+') continue; + buffer_pwrite(i, printptr, printlen); + } + + /* If we didn't see '\n' (long input line), */ + /* read/write repeatedly until we see it */ + while (ch != '\n') { + /* lineptr is emptied now, safe to use as buffer */ + stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax); + if (stdin_cnt <= 0) { /* EOF or error on stdin */ + exitasap = 1; + lineptr[0] = ch = '\n'; + linelen = 1; + stdin_cnt = 1; + } else { + linelen = stdin_cnt; + np = memRchr(lineptr, '\n', stdin_cnt); + if (np) + linelen = np - lineptr + 1; + ch = lineptr[linelen-1]; + } + /* linelen == no of chars incl. '\n' (or == stdin_cnt) */ + for (i = 0; i < dirn; ++i) { + if (dir[i].fddir == -1) continue; + if (dir[i].matcherr == 'e') { + ////full_write(STDERR_FILENO, lineptr, linelen); + fwrite(lineptr, 1, linelen, stderr); + } + if (dir[i].match != '+') continue; + buffer_pwrite(i, lineptr, linelen); + } + } + + stdin_cnt -= linelen; + if (stdin_cnt > 0) { + lineptr += linelen; + /* If we see another '\n', we don't need to read + * next piece of input: can print what we have */ + np = memRchr(lineptr, '\n', stdin_cnt); + if (np) + goto print_to_nl; + /* Move unprocessed data to the front of line */ + memmove((timestamp ? line+26 : line), lineptr, stdin_cnt); + } + fflush(NULL);//// + } + + for (i = 0; i < dirn; ++i) { + if (dir[i].ppid) + while (!processorstop(&dir[i])) + /* repeat */; + logdir_close(&dir[i]); + } + return 0; +} -- cgit v1.2.3