diff options
Diffstat (limited to 'runit')
-rw-r--r-- | runit/Config.in | 66 | ||||
-rw-r--r-- | runit/Kbuild | 12 | ||||
-rw-r--r-- | runit/chpst.c | 373 | ||||
-rw-r--r-- | runit/runit_lib.c | 982 | ||||
-rw-r--r-- | runit/runit_lib.h | 403 | ||||
-rw-r--r-- | runit/runsv.c | 613 | ||||
-rw-r--r-- | runit/runsvdir.c | 306 | ||||
-rw-r--r-- | runit/sv.c | 360 | ||||
-rw-r--r-- | runit/svlogd.c | 878 |
9 files changed, 3993 insertions, 0 deletions
diff --git a/runit/Config.in b/runit/Config.in new file mode 100644 index 0000000..8a7deea --- /dev/null +++ b/runit/Config.in @@ -0,0 +1,66 @@ +# +# 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 SV + bool "sv" + default n + help + sv reports the current status and controls the state of services + monitored by the runsv supervisor. + +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..ad1706c --- /dev/null +++ b/runit/Kbuild @@ -0,0 +1,12 @@ +# Makefile for busybox +# +# Copyright (C) 1999-2005 by Erik Andersen <andersen@codepoet.org> +# +# 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 diff --git a/runit/chpst.c b/runit/chpst.c new file mode 100644 index 0000000..f8e6303 --- /dev/null +++ b/runit/chpst.c @@ -0,0 +1,373 @@ +/* +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 Denis Vlasenko <vda.linux@googlemail.com> */ +/* Dependencies on runit_lib.c removed */ + +#include "busybox.h" + +#include <dirent.h> + +// Must match constants in chpst_main! +#define OPT_verbose (option_mask32 & 0x2000) +#define OPT_pgrp (option_mask32 & 0x4000) +#define OPT_nostdin (option_mask32 & 0x8000) +#define OPT_nostdout (option_mask32 & 0x10000) +#define OPT_nostderr (option_mask32 & 0x20000) + +static char *set_user; +static char *env_user; +static const char *env_dir; +static long limitd = -2; +static long limits = -2; +static long limitl = -2; +static long limita = -2; +static long limito = -2; +static long limitp = -2; +static long limitf = -2; +static long limitc = -2; +static long limitr = -2; +static long limitt = -2; +static int nicelvl; +static const char *root; + +static void suidgid(char *user) +{ + struct bb_uidgid_t ugid; + + if (!uidgid_get(&ugid, user)) { + bb_error_msg_and_die("unknown user/group: %s", user); + } + if (setgroups(1, &ugid.gid) == -1) + bb_perror_msg_and_die("setgroups"); + xsetgid(ugid.gid); + xsetuid(ugid.uid); +} + +static void euidgid(char *user) +{ + struct bb_uidgid_t ugid; + + if (!uidgid_get(&ugid, user)) { + bb_error_msg_and_die("unknown user/group: %s", user); + } + xsetenv("GID", utoa(ugid.gid)); + xsetenv("UID", utoa(ugid.uid)); +} + +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 (;;) { + 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) && env_dir) { + if (OPT_verbose) + 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); + } + if (fd >= 0) { + char buf[256]; + char *tail; + int size; + + size = safe_read(fd, buf, sizeof(buf)-1); + 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 = memchr(buf, '\n', sizeof(buf)); + /* skip trailing whitespace */; + while (1) { + if (tail[0]==' ') tail[0] = '\0'; + if (tail[0]=='\t') tail[0] = '\0'; + if (tail[0]=='\n') tail[0] = '\0'; + if (tail == buf) break; + tail--; + } + 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; + + if (getrlimit(what, &r) == -1) bb_perror_msg_and_die("getrlimit"); + 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"); +} + +static void slimit(void) +{ + if (limitd >= -1) { +#ifdef RLIMIT_DATA + limit(RLIMIT_DATA, limitd); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_DATA"); +#endif + } + if (limits >= -1) { +#ifdef RLIMIT_STACK + limit(RLIMIT_STACK, limits); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_STACK"); +#endif + } + if (limitl >= -1) { +#ifdef RLIMIT_MEMLOCK + limit(RLIMIT_MEMLOCK, limitl); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_MEMLOCK"); +#endif + } + if (limita >= -1) { +#ifdef RLIMIT_VMEM + limit(RLIMIT_VMEM, limita); +#else +#ifdef RLIMIT_AS + limit(RLIMIT_AS, limita); +#else + if (OPT_verbose) + bb_error_msg("system does not support %s", + "RLIMIT_VMEM"); +#endif +#endif + } + if (limito >= -1) { +#ifdef RLIMIT_NOFILE + limit(RLIMIT_NOFILE, limito); +#else +#ifdef RLIMIT_OFILE + limit(RLIMIT_OFILE, limito); +#else + if (OPT_verbose) + bb_error_msg("system does not support %s", + "RLIMIT_NOFILE"); +#endif +#endif + } + if (limitp >= -1) { +#ifdef RLIMIT_NPROC + limit(RLIMIT_NPROC, limitp); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_NPROC"); +#endif + } + if (limitf >= -1) { +#ifdef RLIMIT_FSIZE + limit(RLIMIT_FSIZE, limitf); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_FSIZE"); +#endif + } + if (limitc >= -1) { +#ifdef RLIMIT_CORE + limit(RLIMIT_CORE, limitc); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_CORE"); +#endif + } + if (limitr >= -1) { +#ifdef RLIMIT_RSS + limit(RLIMIT_RSS, limitr); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_RSS"); +#endif + } + if (limitt >= -1) { +#ifdef RLIMIT_CPU + limit(RLIMIT_CPU, limitt); +#else + if (OPT_verbose) bb_error_msg("system does not support %s", + "RLIMIT_CPU"); +#endif + } +} + +/* argv[0] */ +static void setuidgid(int, char **); +static void envuidgid(int, char **); +static void envdir(int, char **); +static void softlimit(int, char **); + +int chpst_main(int argc, char **argv) +{ + if (applet_name[3] == 'd') envdir(argc, argv); + if (applet_name[1] == 'o') softlimit(argc, argv); + if (applet_name[0] == 's') setuidgid(argc, argv); + if (applet_name[0] == 'e') envuidgid(argc, argv); + // otherwise we are chpst + + { + char *m,*d,*o,*p,*f,*c,*r,*t,*n; + getopt32(argc, argv, "+u:U:e:m:d:o:p:f:c:r:t:/:n:vP012", + &set_user,&env_user,&env_dir, + &m,&d,&o,&p,&f,&c,&r,&t,&root,&n); + // if (option_mask32 & 0x1) // -u + // if (option_mask32 & 0x2) // -U + // if (option_mask32 & 0x4) // -e + if (option_mask32 & 0x8) limits = limitl = limita = limitd = xatoul(m); // -m + if (option_mask32 & 0x10) limitd = xatoul(d); // -d + if (option_mask32 & 0x20) limito = xatoul(o); // -o + if (option_mask32 & 0x40) limitp = xatoul(p); // -p + if (option_mask32 & 0x80) limitf = xatoul(f); // -f + if (option_mask32 & 0x100) limitc = xatoul(c); // -c + if (option_mask32 & 0x200) limitr = xatoul(r); // -r + if (option_mask32 & 0x400) limitt = xatoul(t); // -t + // if (option_mask32 & 0x800) // -/ + if (option_mask32 & 0x1000) nicelvl = xatoi(n); // -n + // The below consts should match #defines at top! + //if (option_mask32 & 0x2000) OPT_verbose = 1; // -v + //if (option_mask32 & 0x4000) OPT_pgrp = 1; // -P + //if (option_mask32 & 0x8000) OPT_nostdin = 1; // -0 + //if (option_mask32 & 0x10000) OPT_nostdout = 1; // -1 + //if (option_mask32 & 0x20000) OPT_nostderr = 1; // -2 + } + argv += optind; + if (!argv || !*argv) bb_show_usage(); + + if (OPT_pgrp) setsid(); + if (env_dir) edir(env_dir); + if (root) { + xchdir(root); + if (chroot(".") == -1) + bb_perror_msg_and_die("chroot"); + } + slimit(); + if (nicelvl) { + errno = 0; + if (nice(nicelvl) == -1) + bb_perror_msg_and_die("nice"); + } + if (env_user) euidgid(env_user); + if (set_user) suidgid(set_user); + if (OPT_nostdin) close(0); + if (OPT_nostdout) close(1); + if (OPT_nostderr) close(2); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void setuidgid(int argc, char **argv) +{ + const char *account; + + account = *++argv; + if (!account) bb_show_usage(); + if (!*++argv) bb_show_usage(); + suidgid((char*)account); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void envuidgid(int argc, char **argv) +{ + const char *account; + + account = *++argv; + if (!account) bb_show_usage(); + if (!*++argv) bb_show_usage(); + euidgid((char*)account); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void envdir(int argc, char **argv) +{ + const char *dir; + + dir = *++argv; + if (!dir) bb_show_usage(); + if (!*++argv) bb_show_usage(); + edir(dir); + execvp(argv[0], argv); + bb_perror_msg_and_die("exec %s", argv[0]); +} + +static void softlimit(int argc, char **argv) +{ + char *a,*c,*d,*f,*l,*m,*o,*p,*r,*s,*t; + getopt32(argc, argv, "+a:c:d:f:l:m:o:p:r:s:t:", + &a,&c,&d,&f,&l,&m,&o,&p,&r,&s,&t); + if (option_mask32 & 0x001) limita = xatoul(a); // -a + if (option_mask32 & 0x002) limitc = xatoul(c); // -c + if (option_mask32 & 0x004) limitd = xatoul(d); // -d + if (option_mask32 & 0x008) limitf = xatoul(f); // -f + if (option_mask32 & 0x010) limitl = xatoul(l); // -l + if (option_mask32 & 0x020) limits = limitl = limita = limitd = xatoul(m); // -m + if (option_mask32 & 0x040) limito = xatoul(o); // -o + if (option_mask32 & 0x080) limitp = xatoul(p); // -p + if (option_mask32 & 0x100) limitr = xatoul(r); // -r + if (option_mask32 & 0x200) limits = xatoul(s); // -s + if (option_mask32 & 0x400) limitt = xatoul(t); // -t + argv += optind; + if (!argv[0]) bb_show_usage(); + slimit(); + 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..5ebbc58 --- /dev/null +++ b/runit/runit_lib.c @@ -0,0 +1,982 @@ +/* +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 Denis Vlasenko <vda.linux@googlemail.com> */ +/* Collected into one file from runit's many tiny files */ +/* TODO: review, eliminate unneeded stuff, move good stuff to libbb */ + +#include <sys/poll.h> +#include <sys/file.h> +#include "libbb.h" +#include "runit_lib.h" + +/*** buffer.c ***/ + +void buffer_init(buffer *s,int (*op)(int fd,char *buf,unsigned len),int fd,char *buf,unsigned len) +{ + s->x = buf; + s->fd = fd; + s->op = op; + s->p = 0; + s->n = len; +} + + +/*** buffer_get.c ***/ + +static int oneread(int (*op)(int fd,char *buf,unsigned len),int fd,char *buf,unsigned len) +{ + int r; + + for (;;) { + r = op(fd,buf,len); + if (r == -1) if (errno == EINTR) continue; + return r; + } +} + +static int getthis(buffer *s,char *buf,unsigned len) +{ + if (len > s->p) len = s->p; + s->p -= len; + memcpy(buf,s->x + s->n,len); + s->n += len; + return len; +} + +int buffer_feed(buffer *s) +{ + int r; + + if (s->p) return s->p; + r = oneread(s->op,s->fd,s->x,s->n); + if (r <= 0) return r; + s->p = r; + s->n -= r; + if (s->n > 0) memmove(s->x + s->n,s->x,r); + return r; +} + +int buffer_bget(buffer *s,char *buf,unsigned len) +{ + int r; + + if (s->p > 0) return getthis(s,buf,len); + if (s->n <= len) return oneread(s->op,s->fd,buf,s->n); + r = buffer_feed(s); if (r <= 0) return r; + return getthis(s,buf,len); +} + +int buffer_get(buffer *s,char *buf,unsigned len) +{ + int r; + + if (s->p > 0) return getthis(s,buf,len); + if (s->n <= len) return oneread(s->op,s->fd,buf,len); + r = buffer_feed(s); if (r <= 0) return r; + return getthis(s,buf,len); +} + +char *buffer_peek(buffer *s) +{ + return s->x + s->n; +} + +void buffer_seek(buffer *s,unsigned len) +{ + s->n += len; + s->p -= len; +} + + +/*** buffer_put.c ***/ + +static int allwrite(int (*op)(int fd,char *buf,unsigned len),int fd,const char *buf,unsigned len) +{ + int w; + + while (len) { + w = op(fd,(char*)buf,len); + if (w == -1) { + if (errno == EINTR) continue; + return -1; /* note that some data may have been written */ + } + if (w == 0) ; /* luser's fault */ + buf += w; + len -= w; + } + return 0; +} + +int buffer_flush(buffer *s) +{ + int p; + + p = s->p; + if (!p) return 0; + s->p = 0; + return allwrite(s->op,s->fd,s->x,p); +} + +int buffer_putalign(buffer *s,const char *buf,unsigned len) +{ + unsigned n; + + while (len > (n = s->n - s->p)) { + memcpy(s->x + s->p,buf,n); + s->p += n; + buf += n; + len -= n; + if (buffer_flush(s) == -1) return -1; + } + /* now len <= s->n - s->p */ + memcpy(s->x + s->p,buf,len); + s->p += len; + return 0; +} + +int buffer_put(buffer *s,const char *buf,unsigned len) +{ + unsigned n; + + n = s->n; + if (len > n - s->p) { + if (buffer_flush(s) == -1) return -1; + /* now s->p == 0 */ + if (n < BUFFER_OUTSIZE) n = BUFFER_OUTSIZE; + while (len > s->n) { + if (n > len) n = len; + if (allwrite(s->op,s->fd,buf,n) == -1) return -1; + buf += n; + len -= n; + } + } + /* now len <= s->n - s->p */ + memcpy(s->x + s->p,buf,len); + s->p += len; + return 0; +} + +int buffer_putflush(buffer *s,const char *buf,unsigned len) +{ + if (buffer_flush(s) == -1) return -1; + return allwrite(s->op,s->fd,buf,len); +} + +int buffer_putsalign(buffer *s,const char *buf) +{ + return buffer_putalign(s,buf,strlen(buf)); +} + +int buffer_puts(buffer *s,const char *buf) +{ + return buffer_put(s,buf,strlen(buf)); +} + +int buffer_putsflush(buffer *s,const char *buf) +{ + return buffer_putflush(s,buf,strlen(buf)); +} + + +/*** buffer_read.c ***/ + +int buffer_unixread(int fd,char *buf,unsigned len) +{ + return read(fd,buf,len); +} + + +/*** buffer_write.c ***/ + +int buffer_unixwrite(int fd,char *buf,unsigned len) +{ + return write(fd,buf,len); +} + + +/*** byte_chr.c ***/ + +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; + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + if (!n) break; if (*t == ch) break; ++t; --n; + } + return t - s; +} + + +/*** coe.c ***/ + +int coe(int fd) +{ + return fcntl(fd,F_SETFD,FD_CLOEXEC); +} + + +/*** fd_copy.c ***/ + +int fd_copy(int to,int from) +{ + if (to == from) return 0; + if (fcntl(from,F_GETFL,0) == -1) return -1; + close(to); + if (fcntl(from,F_DUPFD,to) == -1) return -1; + return 0; +} + + +/*** fd_move.c ***/ + +int fd_move(int to,int from) +{ + if (to == from) return 0; + if (fd_copy(to,from) == -1) return -1; + close(from); + return 0; +} + + +/*** fifo.c ***/ + +int fifo_make(const char *fn,int mode) { return mkfifo(fn,mode); } + + +/*** fmt_ptime.c ***/ + +unsigned fmt_ptime(char *s, struct taia *ta) { + struct tm *t; + unsigned long u; + + if (ta->sec.x < 4611686018427387914ULL) return 0; /* impossible? */ + u = ta->sec.x -4611686018427387914ULL; + if (!(t = gmtime((time_t*)&u))) return 0; + fmt_ulong(s, 1900 + t->tm_year); + s[4] = '-'; fmt_uint0(&s[5], t->tm_mon+1, 2); + s[7] = '-'; fmt_uint0(&s[8], t->tm_mday, 2); + s[10] = '_'; fmt_uint0(&s[11], t->tm_hour, 2); + s[13] = ':'; fmt_uint0(&s[14], t->tm_min, 2); + s[16] = ':'; fmt_uint0(&s[17], t->tm_sec, 2); + s[19] = '.'; fmt_uint0(&s[20], ta->nano, 9); + return 25; +} + +unsigned fmt_taia(char *s, struct taia *t) { + static char hex[16] = "0123456789abcdef"; + static char pack[TAIA_PACK]; + int i; + + taia_pack(pack, t); + s[0] = '@'; + for (i = 0; i < 12; ++i) { + s[i*2+1] = hex[(pack[i] >> 4) &15]; + s[i*2+2] = hex[pack[i] &15]; + } + return 25; +} + + +/*** fmt_uint.c ***/ + +unsigned fmt_uint(char *s,unsigned u) +{ + return fmt_ulong(s,u); +} + + +/*** fmt_uint0.c ***/ + +unsigned fmt_uint0(char *s,unsigned u,unsigned n) +{ + unsigned len; + len = fmt_uint(FMT_LEN,u); + while (len < n) { if (s) *s++ = '0'; ++len; } + if (s) fmt_uint(s,u); + return len; +} + + +/*** fmt_ulong.c ***/ + +unsigned fmt_ulong(char *s,unsigned long u) +{ + unsigned len; unsigned long q; + len = 1; q = u; + while (q > 9) { ++len; q /= 10; } + if (s) { + s += len; + do { *--s = '0' + (u % 10); u /= 10; } while(u); /* handles u == 0 */ + } + return len; +} + + +/*** tai_now.c ***/ + +void tai_now(struct tai *t) +{ + tai_unix(t,time((time_t *) 0)); +} + + +/*** tai_pack.c ***/ + +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; +} + + +/*** tai_sub.c ***/ + +void tai_sub(struct tai *t,const struct tai *u,const struct tai *v) +{ + t->x = u->x - v->x; +} + + +/*** tai_unpack.c ***/ + +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; +} + + +/*** taia_add.c ***/ + +/* XXX: breaks tai encapsulation */ + +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; + } +} + + +/*** taia_approx.c ***/ + +double taia_approx(const struct taia *t) +{ + return tai_approx(&t->sec) + taia_frac(t); +} + + +/*** taia_frac.c ***/ + +double taia_frac(const struct taia *t) +{ + return (t->atto * 0.000000001 + t->nano) * 0.000000001; +} + + +/*** taia_less.c ***/ + +/* XXX: breaks tai encapsulation */ + +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; +} + + +/*** taia_now.c ***/ + +void taia_now(struct taia *t) +{ + struct timeval now; + gettimeofday(&now,(struct timezone *) 0); + tai_unix(&t->sec,now.tv_sec); + t->nano = 1000 * now.tv_usec + 500; + t->atto = 0; +} + + +/*** taia_pack.c ***/ + +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; +} + + +/*** taia_sub.c ***/ + +/* XXX: breaks tai encapsulation */ + +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; + } +} + + +/*** taia_uint.c ***/ + +/* XXX: breaks tai encapsulation */ + +void taia_uint(struct taia *t,unsigned s) +{ + t->sec.x = s; + t->nano = 0; + t->atto = 0; +} + + +/*** stralloc_cat.c ***/ +#if 0 + +int stralloc_cat(stralloc *sato,const stralloc *safrom) +{ + return stralloc_catb(sato,safrom->s,safrom->len); +} + + +/*** stralloc_catb.c ***/ + +int stralloc_catb(stralloc *sa,const char *s,unsigned n) +{ + if (!sa->s) return stralloc_copyb(sa,s,n); + if (!stralloc_readyplus(sa,n + 1)) return 0; + memcpy(sa->s + sa->len,s,n); + sa->len += n; + sa->s[sa->len] = 'Z'; /* ``offensive programming'' */ + return 1; +} + + +/*** stralloc_cats.c ***/ + +int stralloc_cats(stralloc *sa,const char *s) +{ + return stralloc_catb(sa,s,strlen(s)); +} + + +/*** stralloc_eady.c ***/ + +GEN_ALLOC_ready(stralloc,char,s,len,a,i,n,x,30,stralloc_ready) +GEN_ALLOC_readyplus(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus) + + +/*** stralloc_opyb.c ***/ + +int stralloc_copyb(stralloc *sa,const char *s,unsigned n) +{ + if (!stralloc_ready(sa,n + 1)) return 0; + memcpy(sa->s,s,n); + sa->len = n; + sa->s[n] = 'Z'; /* ``offensive programming'' */ + return 1; +} + + +/*** stralloc_opys.c ***/ + +int stralloc_copys(stralloc *sa,const char *s) +{ + return stralloc_copyb(sa,s,strlen(s)); +} + + +/*** stralloc_pend.c ***/ + +GEN_ALLOC_append(stralloc,char,s,len,a,i,n,x,30,stralloc_readyplus,stralloc_append) + +#endif /* stralloc */ + +/*** iopause.c ***/ + +void iopause(iopause_fd *x,unsigned len,struct taia *deadline,struct taia *stamp) +{ + struct taia t; + int millisecs; + double d; + int i; + + if (taia_less(deadline,stamp)) + millisecs = 0; + else { + t = *stamp; + taia_sub(&t,deadline,&t); + d = taia_approx(&t); + if (d > 1000.0) d = 1000.0; + millisecs = d * 1000.0 + 20.0; + } + + 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? */ +} + + +/*** lock_ex.c ***/ + +int lock_ex(int fd) +{ + return flock(fd,LOCK_EX); +} + + +/*** lock_exnb.c ***/ + +int lock_exnb(int fd) +{ + return flock(fd,LOCK_EX | LOCK_NB); +} + + +/*** open_append.c ***/ + +int open_append(const char *fn) +{ + return open(fn, O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600); +} + + +/*** open_read.c ***/ + +int open_read(const char *fn) +{ + return open(fn, O_RDONLY|O_NDELAY); +} + + +/*** open_trunc.c ***/ + +int open_trunc(const char *fn) +{ + return open(fn,O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT,0644); +} + + +/*** open_write.c ***/ + +int open_write(const char *fn) +{ + return open(fn, O_WRONLY|O_NDELAY); +} + + +/*** openreadclose.c ***/ +#if 0 +int openreadclose(const char *fn,stralloc *sa,unsigned bufsize) +{ + int fd; + fd = open_read(fn); + if (fd == -1) { + if (errno == ENOENT) return 0; + return -1; + } + if (readclose(fd,sa,bufsize) == -1) return -1; + return 1; +} +#endif + + +/*** pathexec_env.c ***/ +#if 0 +static stralloc plus; +static stralloc tmp; + +int pathexec_env(const char *s,const char *t) +{ + if (!s) return 1; + if (!stralloc_copys(&tmp,s)) return 0; + if (t) { + if (!stralloc_cats(&tmp,"=")) return 0; + if (!stralloc_cats(&tmp,t)) return 0; + } + if (!stralloc_0(&tmp)) return 0; + return stralloc_cat(&plus,&tmp); +} + +void pathexec(char **argv) +{ + char **e; + unsigned elen; + unsigned i; + unsigned j; + unsigned split; + unsigned t; + + if (!stralloc_cats(&plus,"")) return; + + elen = 0; + for (i = 0;environ[i];++i) + ++elen; + for (i = 0;i < plus.len;++i) + if (!plus.s[i]) + ++elen; + + e = malloc((elen + 1) * sizeof(char *)); + if (!e) return; + + elen = 0; + for (i = 0;environ[i];++i) + e[elen++] = environ[i]; + + j = 0; + for (i = 0;i < plus.len;++i) + if (!plus.s[i]) { + split = str_chr(plus.s + j,'='); + for (t = 0;t < elen;++t) + if (memcmp(plus.s + j,e[t],split) == 0) + if (e[t][split] == '=') { + --elen; + e[t] = e[elen]; + break; + } + if (plus.s[j + split]) + e[elen++] = plus.s + j; + j = i + 1; + } + e[elen] = 0; + + pathexec_run(*argv,argv,e); + free(e); +} +#endif + +/*** pathexec_run.c ***/ +#if 0 +static stralloc tmp; + +void pathexec_run(const char *file,char *const *argv,char *const *envp) +{ + const char *path; + unsigned split; + int savederrno; + + if (file[str_chr(file,'/')]) { + execve(file,argv,envp); + return; + } + + path = getenv("PATH"); + if (!path) path = "/bin:/usr/bin"; + + savederrno = 0; + for (;;) { + split = str_chr(path,':'); + if (!stralloc_copyb(&tmp,path,split)) return; + if (!split) + if (!stralloc_cats(&tmp,".")) return; + if (!stralloc_cats(&tmp,"/")) return; + if (!stralloc_cats(&tmp,file)) return; + if (!stralloc_0(&tmp)) return; + + execve(tmp.s,argv,envp); + if (errno != ENOENT) { + savederrno = errno; + if ((errno != EACCES) && (errno != EPERM) && (errno != EISDIR)) return; + } + + if (!path[split]) { + if (savederrno) errno = savederrno; + return; + } + path += split; + path += 1; + } +} +#endif + +/*** pmatch.c ***/ + +unsigned pmatch(const char *p, const char *s, unsigned len) { + for (;;) { + char c = *p++; + if (!c) return !len; + switch (c) { + case '*': + if (!(c = *p)) return 1; + for (;;) { + if (!len) return 0; + if (*s == c) break; + ++s; --len; + } + continue; + case '+': + if ((c = *p++) != *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; +} + + +/*** prot.c ***/ + +int prot_gid(int gid) +{ + gid_t x = gid; + if (setgroups(1,&x) == -1) return -1; + return setgid(gid); /* _should_ be redundant, but on some systems it isn't */ +} + +int prot_uid(int uid) +{ + return setuid(uid); +} + + +/*** readclose.c ***/ +#if 0 +int readclose_append(int fd,stralloc *sa,unsigned bufsize) +{ + int r; + for (;;) { + if (!stralloc_readyplus(sa,bufsize)) { close(fd); return -1; } + r = read(fd,sa->s + sa->len,bufsize); + if (r == -1) if (errno == EINTR) continue; + if (r <= 0) { close(fd); return r; } + sa->len += r; + } +} + +int readclose(int fd,stralloc *sa,unsigned bufsize) +{ + if (!stralloc_copys(sa,"")) { close(fd); return -1; } + return readclose_append(fd,sa,bufsize); +} +#endif + +/*** scan_ulong.c ***/ + +unsigned scan_ulong(const char *s,unsigned long *u) +{ + unsigned pos = 0; + unsigned long result = 0; + unsigned long c; + while ((c = (unsigned long) (unsigned char) (s[pos] - '0')) < 10) { + result = result * 10 + c; + ++pos; + } + *u = result; + return pos; +} + + +/*** seek_set.c ***/ + +int seek_set(int fd,seek_pos pos) +{ + if (lseek(fd,(off_t) pos,SEEK_SET) == -1) return -1; return 0; +} + + +/*** sig.c ***/ + +int sig_alarm = SIGALRM; +int sig_child = SIGCHLD; +int sig_cont = SIGCONT; +int sig_hangup = SIGHUP; +int sig_int = SIGINT; +int sig_pipe = SIGPIPE; +int sig_term = SIGTERM; + +void (*sig_defaulthandler)(int) = SIG_DFL; +void (*sig_ignorehandler)(int) = SIG_IGN; + + +/*** sig_block.c ***/ + +void sig_block(int sig) +{ + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss,sig); + sigprocmask(SIG_BLOCK,&ss,(sigset_t *) 0); +} + +void sig_unblock(int sig) +{ + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss,sig); + sigprocmask(SIG_UNBLOCK,&ss,(sigset_t *) 0); +} + +void sig_blocknone(void) +{ + sigset_t ss; + sigemptyset(&ss); + sigprocmask(SIG_SETMASK,&ss,(sigset_t *) 0); +} + + +/*** sig_catch.c ***/ + +void sig_catch(int sig,void (*f)(int)) +{ + struct sigaction sa; + sa.sa_handler = f; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(sig,&sa,(struct sigaction *) 0); +} + + +/*** sig_pause.c ***/ + +void sig_pause(void) +{ + sigset_t ss; + sigemptyset(&ss); + sigsuspend(&ss); +} + + +/*** str_chr.c ***/ + +unsigned str_chr(const char *s,int c) +{ + char ch; + const char *t; + + ch = c; + t = s; + for (;;) { + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + if (!*t) break; if (*t == ch) break; ++t; + } + return t - s; +} + + +/*** wait_nohang.c ***/ + +int wait_nohang(int *wstat) +{ + return waitpid(-1,wstat,WNOHANG); +} + + +/*** wait_pid.c ***/ + +int wait_pid(int *wstat, int pid) +{ + int r; + + do + r = waitpid(pid,wstat,0); + while ((r == -1) && (errno == EINTR)); + return r; +} diff --git a/runit/runit_lib.h b/runit/runit_lib.h new file mode 100644 index 0000000..1f91191 --- /dev/null +++ b/runit/runit_lib.h @@ -0,0 +1,403 @@ +/* +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. +*/ + +/*** buffer.h ***/ + +typedef struct buffer { + char *x; + unsigned p; + unsigned n; + int fd; + int (*op)(int fd,char *buf,unsigned len); +} buffer; + +#define BUFFER_INIT(op,fd,buf,len) { (buf), 0, (len), (fd), (op) } +#define BUFFER_INSIZE 8192 +#define BUFFER_OUTSIZE 8192 + +extern void buffer_init(buffer *,int (*)(int fd,char *buf,unsigned len),int,char *,unsigned); + +extern int buffer_flush(buffer *); +extern int buffer_put(buffer *,const char *,unsigned); +extern int buffer_putalign(buffer *,const char *,unsigned); +extern int buffer_putflush(buffer *,const char *,unsigned); +extern int buffer_puts(buffer *,const char *); +extern int buffer_putsalign(buffer *,const char *); +extern int buffer_putsflush(buffer *,const char *); + +#define buffer_PUTC(s,c) \ + ( ((s)->n != (s)->p) \ + ? ( (s)->x[(s)->p++] = (c), 0 ) \ + : buffer_put((s),&(c),1) \ + ) + +extern int buffer_get(buffer *,char *,unsigned); +extern int buffer_bget(buffer *,char *,unsigned); +extern int buffer_feed(buffer *); + +extern char *buffer_peek(buffer *); +extern void buffer_seek(buffer *,unsigned); + +#define buffer_PEEK(s) ( (s)->x + (s)->n ) +#define buffer_SEEK(s,len) ( ( (s)->p -= (len) ) , ( (s)->n += (len) ) ) + +#define buffer_GETC(s,c) \ + ( ((s)->p > 0) \ + ? ( *(c) = (s)->x[(s)->n], buffer_SEEK((s),1), 1 ) \ + : buffer_get((s),(c),1) \ + ) + +extern int buffer_copy(buffer *,buffer *); + +extern int buffer_unixread(int,char *,unsigned); +/* Actually, int buffer_unixwrite(int,const char *,unsigned), + but that 'const' will produce warnings... oh well */ +extern int buffer_unixwrite(int,char *,unsigned); + + +/*** byte.h ***/ + +extern unsigned byte_chr(char *s,unsigned n,int c); + + +/*** coe.h ***/ + +extern int coe(int); + + +/*** direntry.h ***/ + +#define direntry struct dirent + + +/*** fd.h ***/ + +extern int fd_copy(int,int); +extern int fd_move(int,int); + + +/*** fifo.h ***/ + +extern int fifo_make(const char *,int); + + +/*** fmt.h ***/ + +#define FMT_ULONG 40 /* enough space to hold 2^128 - 1 in decimal, plus \0 */ +#define FMT_LEN ((char *) 0) /* convenient abbreviation */ + +extern unsigned fmt_uint(char *,unsigned); +extern unsigned fmt_uint0(char *,unsigned,unsigned); +extern unsigned fmt_xint(char *,unsigned); +extern unsigned fmt_nbbint(char *,unsigned,unsigned,unsigned,unsigned); +extern unsigned fmt_ushort(char *,unsigned short); +extern unsigned fmt_xshort(char *,unsigned short); +extern unsigned fmt_nbbshort(char *,unsigned,unsigned,unsigned,unsigned short); +extern unsigned fmt_ulong(char *,unsigned long); +extern unsigned fmt_xlong(char *,unsigned long); +extern unsigned fmt_nbblong(char *,unsigned,unsigned,unsigned,unsigned long); + +extern unsigned fmt_plusminus(char *,int); +extern unsigned fmt_minus(char *,int); +extern unsigned fmt_0x(char *,int); + +extern unsigned fmt_str(char *,const char *); +extern unsigned fmt_strn(char *,const char *,unsigned); + + +/*** tai.h ***/ + +struct tai { + uint64_t x; +} ; + +#define tai_unix(t,u) ((void) ((t)->x = 4611686018427387914ULL + (uint64_t) (u))) + +extern void tai_now(struct tai *); + +#define tai_approx(t) ((double) ((t)->x)) + +extern void tai_add(struct tai *,const struct tai *,const struct tai *); +extern void tai_sub(struct tai *,const struct tai *,const struct tai *); +#define tai_less(t,u) ((t)->x < (u)->x) + +#define TAI_PACK 8 +extern void tai_pack(char *,const struct tai *); +extern void tai_unpack(const char *,struct tai *); + +extern void tai_uint(struct tai *,unsigned); + + +/*** taia.h ***/ + +struct taia { + struct tai sec; + unsigned long nano; /* 0...999999999 */ + unsigned long atto; /* 0...999999999 */ +} ; + +extern void taia_tai(const struct taia *,struct tai *); + +extern void taia_now(struct taia *); + +extern double taia_approx(const struct taia *); +extern double taia_frac(const 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_unpack(const char *,struct taia *); + +#define TAIA_FMTFRAC 19 +extern unsigned taia_fmtfrac(char *,const struct taia *); + +extern void taia_uint(struct taia *,unsigned); + + +/*** fmt_ptime.h ***/ + +#define FMT_PTIME 30 + +extern unsigned fmt_ptime(char *, struct taia *); +extern unsigned fmt_taia(char *, struct taia *); + + +/*** gen_alloc.h ***/ + +#define GEN_ALLOC_typedef(ta,type,field,len,a) \ + typedef struct ta { type *field; unsigned len; unsigned a; } ta; + + +/*** gen_allocdefs.h ***/ + +#define GEN_ALLOC_ready(ta,type,field,len,a,i,n,x,base,ta_ready) \ +int ta_ready(ta *x,unsigned n) \ +{ unsigned i; \ + if (x->field) { \ + i = x->a; \ + if (n > i) { \ + x->a = base + n + (n >> 3); \ + x->field = realloc(x->field,x->a * sizeof(type)); \ + if (x->field) return 1; \ + x->a = i; return 0; } \ + return 1; } \ + x->len = 0; \ + return !!(x->field = malloc((x->a = n) * sizeof(type))); } + +#define GEN_ALLOC_readyplus(ta,type,field,len,a,i,n,x,base,ta_rplus) \ +int ta_rplus(ta *x,unsigned n) \ +{ unsigned i; \ + if (x->field) { \ + i = x->a; n += x->len; \ + if (n > i) { \ + x->a = base + n + (n >> 3); \ + x->field = realloc(x->field,x->a * sizeof(type)); \ + if (x->field) return 1; \ + x->a = i; return 0; } \ + return 1; } \ + x->len = 0; \ + return !!(x->field = malloc((x->a = n) * sizeof(type))); } + +#define GEN_ALLOC_append(ta,type,field,len,a,i,n,x,base,ta_rplus,ta_append) \ +int ta_append(ta *x,const type *i) \ +{ if (!ta_rplus(x,1)) return 0; x->field[x->len++] = *i; return 1; } + + +/*** stralloc.h ***/ +#if 0 +GEN_ALLOC_typedef(stralloc,char,s,len,a) + +extern int stralloc_ready(stralloc *,unsigned); +extern int stralloc_readyplus(stralloc *,unsigned); +extern int stralloc_copy(stralloc *,const stralloc *); +extern int stralloc_cat(stralloc *,const stralloc *); +extern int stralloc_copys(stralloc *,const char *); +extern int stralloc_cats(stralloc *,const char *); +extern int stralloc_copyb(stralloc *,const char *,unsigned); +extern int stralloc_catb(stralloc *,const char *,unsigned); +extern int stralloc_append(stralloc *,const char *); /* beware: this takes a pointer to 1 char */ +extern int stralloc_starts(stralloc *,const char *); + +#define stralloc_0(sa) stralloc_append(sa,"") + +extern int stralloc_catulong0(stralloc *,unsigned long,unsigned); +extern int stralloc_catlong0(stralloc *,long,unsigned); + +#define stralloc_catlong(sa,l) (stralloc_catlong0((sa),(l),0)) +#define stralloc_catuint0(sa,i,n) (stralloc_catulong0((sa),(i),(n))) +#define stralloc_catint0(sa,i,n) (stralloc_catlong0((sa),(i),(n))) +#define stralloc_catint(sa,i) (stralloc_catlong0((sa),(i),0)) +#endif + +/*** iopause.h ***/ + +typedef struct pollfd iopause_fd; +#define IOPAUSE_READ POLLIN +#define IOPAUSE_WRITE POLLOUT + +extern void iopause(iopause_fd *,unsigned,struct taia *,struct taia *); + + +/*** lock.h ***/ + +extern int lock_ex(int); +extern int lock_un(int); +extern int lock_exnb(int); + + +/*** ndelay.h ***/ + +extern int ndelay_on(int); +extern int ndelay_off(int); + + +/*** open.h ***/ + +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 *); + + +/*** openreadclose.h ***/ +#if 0 +extern int openreadclose(const char *,stralloc *,unsigned); +#endif + +/*** pathexec.h ***/ + +extern void pathexec_run(const char *,char *const *,char *const *); +extern int pathexec_env(const char *,const char *); +extern void pathexec(char **); + + +/*** pmatch.h ***/ + +extern unsigned pmatch(const char *, const char *, unsigned); + + +/*** prot.h ***/ + +extern int prot_gid(int); +extern int prot_uid(int); + + +/*** readclose.h ***/ +#if 0 +extern int readclose_append(int,stralloc *,unsigned); +extern int readclose(int,stralloc *,unsigned); +#endif + +/*** scan.h ***/ + +extern unsigned scan_uint(const char *,unsigned *); +extern unsigned scan_xint(const char *,unsigned *); +extern unsigned scan_nbbint(const char *,unsigned,unsigned,unsigned,unsigned *); +extern unsigned scan_ushort(const char *,unsigned short *); +extern unsigned scan_xshort(const char *,unsigned short *); +extern unsigned scan_nbbshort(const char *,unsigned,unsigned,unsigned,unsigned short *); +extern unsigned scan_ulong(const char *,unsigned long *); +extern unsigned scan_xlong(const char *,unsigned long *); +extern unsigned scan_nbblong(const char *,unsigned,unsigned,unsigned,unsigned long *); + +extern unsigned scan_plusminus(const char *,int *); +extern unsigned scan_0x(const char *,unsigned *); + +extern unsigned scan_whitenskip(const char *,unsigned); +extern unsigned scan_nonwhitenskip(const char *,unsigned); +extern unsigned scan_charsetnskip(const char *,const char *,unsigned); +extern unsigned scan_noncharsetnskip(const char *,const char *,unsigned); + +extern unsigned scan_strncmp(const char *,const char *,unsigned); +extern unsigned scan_memcmp(const char *,const char *,unsigned); + +extern unsigned scan_long(const char *,long *); +extern unsigned scan_8long(const char *,unsigned long *); + + +/*** seek.h ***/ + +typedef unsigned long seek_pos; + +extern seek_pos seek_cur(int); + +extern int seek_set(int,seek_pos); +extern int seek_end(int); + +extern int seek_trunc(int,seek_pos); + +#define seek_begin(fd) (seek_set((fd),(seek_pos) 0)) + + +/*** sig.h ***/ + +extern int sig_alarm; +extern int sig_child; +extern int sig_cont; +extern int sig_hangup; +extern int sig_int; +extern int sig_pipe; +extern int sig_term; + +extern void (*sig_defaulthandler)(int); +extern void (*sig_ignorehandler)(int); + +extern void sig_catch(int,void (*)(int)); +#define sig_ignore(s) (sig_catch((s),sig_ignorehandler)) +#define sig_uncatch(s) (sig_catch((s),sig_defaulthandler)) + +extern void sig_block(int); +extern void sig_unblock(int); +extern void sig_blocknone(void); +extern void sig_pause(void); + +extern void sig_dfl(int); + + +/*** str.h ***/ + +extern unsigned str_chr(const char *,int); /* never returns NULL */ + +#define str_diff(s,t) strcmp((s),(t)) +#define str_equal(s,t) (!strcmp((s),(t))) + + +/*** wait.h ***/ + +extern int wait_pid(int *wstat, int pid); +extern int wait_nohang(int *wstat); + +#define wait_crashed(w) ((w) & 127) +#define wait_exitcode(w) ((w) >> 8) +#define wait_stopsig(w) ((w) >> 8) +#define wait_stopped(w) (((w) & 127) == 127) diff --git a/runit/runsv.c b/runit/runsv.c new file mode 100644 index 0000000..e1b5459 --- /dev/null +++ b/runit/runsv.c @@ -0,0 +1,613 @@ +/* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include <sys/poll.h> +#include <sys/file.h> +#include "busybox.h" +#include "runit_lib.h" + +static int selfpipe[2]; + +/* 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; + int state; + int ctrl; + int want; + struct taia start; + int fdlock; + int fdcontrol; + int fdcontrolwrite; + int islog; +}; +static struct svdir svd[2]; + +static int sigterm = 0; +static int haslog = 0; +static int pidchanged = 1; +static int logpipe[2]; +static char *dir; + +#define usage() bb_show_usage() + +static void fatal2_cannot(char *m1, char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void fatal_cannot(char *m) +{ + fatal2_cannot(m, ""); + /* was exiting 111 */ +} +static void fatal2x_cannot(char *m1, char *m2) +{ + bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2); + /* was exiting 111 */ +} +static void warn_cannot(char *m) +{ + bb_perror_msg("%s: warning: cannot %s", dir, m); +} +static void warnx_cannot(char *m) +{ + bb_error_msg("%s: warning: cannot %s", dir, m); +} + +static void stopservice(struct svdir *); + +static void s_child(int sig_no) +{ + write(selfpipe[1], "", 1); +} + +static void s_term(int sig_no) +{ + sigterm = 1; + write(selfpipe[1], "", 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 int rename_or_warn(const char *old, const char *new) +{ + if (rename(old, new) == -1) { + bb_perror_msg("%s: warning: cannot rename %s to %s", + dir, old, new); + return -1; + } + return 0; +} + +static void update_status(struct svdir *s) +{ + unsigned long l; + int fd; + char status[20]; + + /* pid */ + if (pidchanged) { + fd = open_trunc_or_warn("supervise/pid.new"); + if (fd < 0) + return; + if (s->pid) { + char spid[sizeof(s->pid)*3 + 2]; + int size = sprintf(spid, "%d\n", s->pid); + write(fd, spid, size); + } + close(fd); + if (s->islog) { + if (rename_or_warn("supervise/pid.new", "log/supervise/pid")) + return; + } else if (rename_or_warn("supervise/pid.new", "supervise/pid")) { + 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); + } + + if (s->islog) { + rename_or_warn("supervise/stat.new", "log/supervise/stat"); + } else { + rename_or_warn("supervise/stat.new", "log/supervise/stat"+4); + } + + /* supervise compatibility */ + taia_pack(status, &s->start); + l = (unsigned long)s->pid; + status[12] = l; l >>=8; + status[13] = l; l >>=8; + status[14] = l; l >>=8; + status[15] = l; + if (s->ctrl & C_PAUSE) + status[16] = 1; + else + status[16] = 0; + if (s->want == W_UP) + status[17] = 'u'; + else + status[17] = 'd'; + if (s->ctrl & C_TERM) + status[18] = 1; + else + status[18] = 0; + status[19] = s->state; + fd = open_trunc_or_warn("supervise/status.new"); + if (fd < 0) + return; + l = write(fd, status, sizeof status); + if (l < 0) { + warn_cannot("write supervise/status.new"); + close(fd); + unlink("supervise/status.new"); + return; + } + close(fd); + if (l < sizeof status) { + warnx_cannot("write supervise/status.new: partial write"); + return; + } + if (s->islog) { + rename_or_warn("supervise/status.new", "log/supervise/status"); + } else { + rename_or_warn("supervise/status.new", "log/supervise/status"+4); + } +} + +static unsigned custom(struct svdir *s, char c) +{ + int pid; + int w; + char a[10]; + struct stat st; + char *prog[2]; + + if (s->islog) return 0; + memcpy(a, "control/?", 10); + a[8] = c; + if (stat(a, &st) == 0) { + if (st.st_mode & S_IXUSR) { + pid = fork(); + if (pid == -1) { + warn_cannot("fork for control/?"); + return 0; + } + if (!pid) { + if (haslog && fd_copy(1, logpipe[1]) == -1) + warn_cannot("setup stdout for control/?"); + prog[0] = a; + prog[1] = 0; + execve(a, prog, environ); + fatal_cannot("run control/?"); + } + while (wait_pid(&w, pid) == -1) { + if (errno == EINTR) continue; + warn_cannot("wait for child control/?"); + return 0; + } + return !wait_exitcode(w); + } + } + else { + if (errno == ENOENT) return 0; + 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] = "./finish"; + else { + run[0] = "./run"; + custom(s, 'u'); + } + run[1] = 0; + + if (s->pid != 0) stopservice(s); /* should never happen */ + while ((p = fork()) == -1) { + warn_cannot("fork, sleeping"); + sleep(5); + } + if (p == 0) { + /* child */ + if (haslog) { + if (s->islog) { + if (fd_copy(0, logpipe[0]) == -1) + fatal_cannot("setup filedescriptor for ./log/run"); + close(logpipe[1]); + if (chdir("./log") == -1) + fatal_cannot("change directory to ./log"); + } else { + if (fd_copy(1, logpipe[1]) == -1) + fatal_cannot("setup filedescriptor for ./run"); + close(logpipe[0]); + } + } + sig_uncatch(sig_child); + sig_unblock(sig_child); + sig_uncatch(sig_term); + sig_unblock(sig_term); + execve(*run, run, environ); + if (s->islog) + fatal2_cannot("start log/", *run); + else + fatal2_cannot("start ", *run); + } + if (s->state != S_FINISH) { + taia_now(&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) +{ + 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); + if (s->pid && s->state != S_FINISH) stopservice(s); + break; + 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 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGALRM); + break; + case 'h': /* sig hup */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGHUP); + break; + case 'i': /* sig int */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGINT); + break; + case 'q': /* sig quit */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGQUIT); + break; + case '1': /* sig usr1 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGUSR1); + break; + case '2': /* sig usr2 */ + if (s->pid && ! custom(s, c)) kill(s->pid, SIGUSR2); + break; + } + return 1; +} + +int runsv_main(int argc, char **argv) +{ + struct stat s; + int fd; + int r; + char buf[256]; + + if (!argv[1] || argv[2]) usage(); + dir = argv[1]; + + if (pipe(selfpipe) == -1) fatal_cannot("create selfpipe"); + coe(selfpipe[0]); + coe(selfpipe[1]); + ndelay_on(selfpipe[0]); + ndelay_on(selfpipe[1]); + + sig_block(sig_child); + sig_catch(sig_child, s_child); + sig_block(sig_term); + sig_catch(sig_term, s_term); + + xchdir(dir); + svd[0].pid = 0; + svd[0].state = S_DOWN; + svd[0].ctrl = C_NOOP; + svd[0].want = W_UP; + svd[0].islog = 0; + svd[1].pid = 0; + taia_now(&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)) + warnx_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; + taia_now(&svd[1].start); + if (stat("log/down", &s) != -1) + svd[1].want = W_DOWN; + if (pipe(logpipe) == -1) + fatal_cannot("create log pipe"); + coe(logpipe[0]); + coe(logpipe[1]); + } + } + + if (mkdir("supervise", 0700) == -1) { + r = readlink("supervise", buf, 256); + if (r != -1) { + if (r == 256) + 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"); + coe(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"); + coe(svd[1].fdlock); + } + + fifo_make("log/supervise/control"+4, 0600); + svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY); + coe(svd[0].fdcontrol); + svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY); + coe(svd[0].fdcontrolwrite); + update_status(&svd[0]); + if (haslog) { + fifo_make("log/supervise/control", 0600); + svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY); + coe(svd[1].fdcontrol); + svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY); + coe(svd[1].fdcontrolwrite); + update_status(&svd[1]); + } + fifo_make("log/supervise/ok"+4, 0600); + fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY); + coe(fd); + if (haslog) { + fifo_make("log/supervise/ok", 0600); + fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY); + coe(fd); + } + for (;;) { + iopause_fd x[3]; + struct taia deadline; + struct taia now; + 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[0]; + x[0].events = IOPAUSE_READ; + x[1].fd = svd[0].fdcontrol; + x[1].events = IOPAUSE_READ; + if (haslog) { + x[2].fd = svd[1].fdcontrol; + x[2].events = IOPAUSE_READ; + } + taia_now(&now); + taia_uint(&deadline, 3600); + taia_add(&deadline, &now, &deadline); + + sig_unblock(sig_term); + sig_unblock(sig_child); + iopause(x, 2+haslog, &deadline, &now); + sig_block(sig_term); + sig_block(sig_child); + + while (read(selfpipe[0], &ch, 1) == 1) + ; + for (;;) { + int child; + int wstat; + + child = wait_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; + taia_uint(&deadline, 1); + taia_add(&deadline, &svd[0].start, &deadline); + taia_now(&svd[0].start); + update_status(&svd[0]); + if (taia_less(&svd[0].start, &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; + taia_uint(&deadline, 1); + taia_add(&deadline, &svd[1].start, &deadline); + taia_now(&svd[1].start); + update_status(&svd[1]); + if (taia_less(&svd[1].start, &deadline)) sleep(1); + } + } + } + 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(0); + if (svd[1].want != W_EXIT) { + svd[1].want = W_EXIT; + /* stopservice(&svd[1]); */ + update_status(&svd[1]); + close(logpipe[1]); + close(logpipe[0]); + //if (close(logpipe[1]) == -1) + // warn_cannot("close logpipe[1]"); + //if (close(logpipe[0]) == -1) + // warn_cannot("close logpipe[0]"); + } + } + } + /* not reached */ + return 0; +} diff --git a/runit/runsvdir.c b/runit/runsvdir.c new file mode 100644 index 0000000..9238eec --- /dev/null +++ b/runit/runsvdir.c @@ -0,0 +1,306 @@ +/* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include <sys/poll.h> +#include <sys/file.h> +#include "busybox.h" +#include "runit_lib.h" + +#define MAXSERVICES 1000 + +static char *svdir; +static unsigned long dev; +static unsigned long ino; +static struct service { + unsigned long dev; + unsigned long ino; + int pid; + int isgone; +} *sv; +static int svnum; +static int check = 1; +static char *rplog; +static int rploglen; +static int logpipe[2]; +static iopause_fd io[1]; +static struct taia stamplog; +static int exitsoon; +static int pgrp; + +#define usage() bb_show_usage() +static void fatal2_cannot(char *m1, char *m2) +{ + bb_perror_msg_and_die("%s: fatal: cannot %s%s", svdir, m1, m2); + /* was exiting 100 */ +} +static void warn3x(char *m1, char *m2, char *m3) +{ + bb_error_msg("%s: warning: %s%s%s", svdir, m1, m2, m3); +} +static void warn2_cannot(char *m1, char *m2) +{ + warn3x("cannot ", m1, m2); +} +static void warnx(char *m1) +{ + warn3x(m1, "", ""); +} + +static void s_term(int sig_no) +{ + exitsoon = 1; +} +static void s_hangup(int sig_no) +{ + exitsoon = 2; +} + +static void runsv(int no, char *name) +{ + int pid = fork(); + + if (pid == -1) { + warn2_cannot("fork for ", name); + return; + } + if (pid == 0) { + /* child */ + char *prog[3]; + + prog[0] = "runsv"; + prog[1] = name; + prog[2] = 0; + sig_uncatch(sig_hangup); + sig_uncatch(sig_term); + if (pgrp) setsid(); + execvp(prog[0], prog); + //pathexec_run(*prog, prog, (char* const*)environ); + fatal2_cannot("start runsv ", name); + } + sv[no].pid = pid; +} + +static void runsvdir(void) +{ + DIR *dir; + direntry *d; + int i; + struct stat s; + + dir = opendir("."); + if (!dir) { + warn2_cannot("open directory ", svdir); + return; + } + for (i = 0; i < svnum; i++) + sv[i].isgone = 1; + errno = 0; + while ((d = readdir(dir))) { + if (d->d_name[0] == '.') continue; + if (stat(d->d_name, &s) == -1) { + warn2_cannot("stat ", d->d_name); + errno = 0; + continue; + } + if (!S_ISDIR(s.st_mode)) continue; + for (i = 0; i < svnum; i++) { + if ((sv[i].ino == s.st_ino) && (sv[i].dev == s.st_dev)) { + sv[i].isgone = 0; + if (!sv[i].pid) + runsv(i, d->d_name); + break; + } + } + if (i == svnum) { + /* new service */ + struct service *svnew = realloc(sv, (i+1) * sizeof(*sv)); + if (!svnew) { + warn3x("cannot start runsv ", d->d_name, + " too many services"); + continue; + } + sv = svnew; + svnum++; + memset(&sv[i], 0, sizeof(sv[i])); + sv[i].ino = s.st_ino; + sv[i].dev = s.st_dev; + //sv[i].pid = 0; + //sv[i].isgone = 0; + runsv(i, d->d_name); + check = 1; + } + } + if (errno) { + warn2_cannot("read directory ", svdir); + closedir(dir); + check = 1; + return; + } + closedir(dir); + + /* SIGTERM removed runsv's */ + for (i = 0; i < svnum; i++) { + if (!sv[i].isgone) + continue; + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + sv[i] = sv[--svnum]; + check = 1; + } +} + +static int setup_log(void) +{ + rploglen = strlen(rplog); + if (rploglen < 7) { + warnx("log must have at least seven characters"); + return 0; + } + if (pipe(logpipe) == -1) { + warnx("cannot create pipe for log"); + return -1; + } + coe(logpipe[1]); + coe(logpipe[0]); + ndelay_on(logpipe[0]); + ndelay_on(logpipe[1]); + if (fd_copy(2, logpipe[1]) == -1) { + warnx("cannot set filedescriptor for log"); + return -1; + } + io[0].fd = logpipe[0]; + io[0].events = IOPAUSE_READ; + taia_now(&stamplog); + return 1; +} + +int runsvdir_main(int argc, char **argv) +{ + struct stat s; + time_t mtime = 0; + int wstat; + int curdir; + int pid; + struct taia deadline; + struct taia now; + struct taia stampcheck; + char ch; + int i; + + argv++; + if (!argv || !*argv) usage(); + if (**argv == '-') { + switch (*(*argv + 1)) { + case 'P': pgrp = 1; + case '-': ++argv; + } + if (!argv || !*argv) usage(); + } + + sig_catch(sig_term, s_term); + sig_catch(sig_hangup, s_hangup); + svdir = *argv++; + if (argv && *argv) { + rplog = *argv; + if (setup_log() != 1) { + rplog = 0; + warnx("log service disabled"); + } + } + curdir = open_read("."); + if (curdir == -1) + fatal2_cannot("open current directory", ""); + coe(curdir); + + taia_now(&stampcheck); + + for (;;) { + /* collect children */ + for (;;) { + pid = wait_nohang(&wstat); + if (pid <= 0) break; + for (i = 0; i < svnum; i++) { + if (pid == sv[i].pid) { + /* runsv has gone */ + sv[i].pid = 0; + check = 1; + break; + } + } + } + + taia_now(&now); + if (now.sec.x < (stampcheck.sec.x - 3)) { + /* time warp */ + warnx("time warp: resetting time stamp"); + taia_now(&stampcheck); + taia_now(&now); + if (rplog) taia_now(&stamplog); + } + if (taia_less(&now, &stampcheck) == 0) { + /* wait at least a second */ + taia_uint(&deadline, 1); + taia_add(&stampcheck, &now, &deadline); + + if (stat(svdir, &s) != -1) { + if (check || s.st_mtime != mtime + || s.st_ino != ino || s.st_dev != dev + ) { + /* svdir modified */ + if (chdir(svdir) != -1) { + mtime = s.st_mtime; + dev = s.st_dev; + ino = s.st_ino; + check = 0; + if (now.sec.x <= (4611686018427387914ULL + (uint64_t)mtime)) + sleep(1); + runsvdir(); + while (fchdir(curdir) == -1) { + warn2_cannot("change directory, pausing", ""); + sleep(5); + } + } else + warn2_cannot("change directory to ", svdir); + } + } else + warn2_cannot("stat ", svdir); + } + + if (rplog) { + if (taia_less(&now, &stamplog) == 0) { + write(logpipe[1], ".", 1); + taia_uint(&deadline, 900); + taia_add(&stamplog, &now, &deadline); + } + } + taia_uint(&deadline, check ? 1 : 5); + taia_add(&deadline, &now, &deadline); + + sig_block(sig_child); + if (rplog) + iopause(io, 1, &deadline, &now); + else + iopause(0, 0, &deadline, &now); + sig_unblock(sig_child); + + if (rplog && (io[0].revents | IOPAUSE_READ)) + while (read(logpipe[0], &ch, 1) > 0) + if (ch) { + for (i = 6; i < rploglen; i++) + rplog[i-1] = rplog[i]; + rplog[rploglen-1] = ch; + } + + switch (exitsoon) { + case 1: + _exit(0); + case 2: + for (i = 0; i < svnum; i++) + if (sv[i].pid) + kill(sv[i].pid, SIGTERM); + _exit(111); + } + } + /* not reached */ + return 0; +} diff --git a/runit/sv.c b/runit/sv.c new file mode 100644 index 0000000..819f314 --- /dev/null +++ b/runit/sv.c @@ -0,0 +1,360 @@ +/* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include <sys/poll.h> +#include <sys/file.h> +#include "busybox.h" +#include "runit_lib.h" + +static char *action; +static char *acts; +static char *varservice = "/var/service/"; +static char **service; +static char **servicex; +static unsigned services; +static unsigned rc = 0; +static unsigned verbose = 0; +static unsigned long waitsec = 7; +static unsigned kll = 0; +static struct taia tstart, tnow, tdiff; +static struct tai tstatus; + +static int (*act)(char*) = 0; +static int (*cbk)(char*) = 0; + +static int curdir, fd, r; +static char svstatus[20]; + +#define usage() bb_show_usage() + +static void fatal_cannot(char *m1) +{ + bb_perror_msg("fatal: cannot %s", m1); + _exit(151); +} + +static void out(char *p, char *m1) +{ + printf("%s%s: %s", p, *service, m1); + if (errno) { + printf(": %s", strerror(errno)); + } + puts(""); /* will also flush the output */ +} + +#define FAIL "fail: " +#define WARN "warning: " +#define OK "ok: " +#define RUN "run: " +#define FINISH "finish: " +#define DOWN "down: " +#define TIMEOUT "timeout: " +#define KILL "kill: " + +static void fail(char *m1) { ++rc; out(FAIL, m1); } +static void failx(char *m1) { errno = 0; fail(m1); } +static void warn_cannot(char *m1) { ++rc; out("warning: cannot ", m1); } +static void warnx_cannot(char *m1) { errno = 0; warn_cannot(m1); } +static void ok(char *m1) { errno = 0; out(OK, m1); } + +static int svstatus_get(void) +{ + if ((fd = open_write("supervise/ok")) == -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); + if ((fd = open_read("supervise/status")) == -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: warnx_cannot("read supervise/status: bad format"); return -1; + } + return 1; +} + +static unsigned svstatus_print(char *m) +{ + int pid; + int normallyup = 0; + struct stat s; + + if (stat("down", &s) == -1) { + if (errno != ENOENT) { + bb_perror_msg(WARN"cannot stat %s/down", *service); + return 0; + } + normallyup = 1; + } + pid = (unsigned char) svstatus[15]; + pid <<= 8; pid += (unsigned char)svstatus[14]; + pid <<= 8; pid += (unsigned char)svstatus[13]; + pid <<= 8; pid += (unsigned char)svstatus[12]; + tai_unpack(svstatus, &tstatus); + if (pid) { + switch (svstatus[19]) { + case 1: printf(RUN); break; + case 2: printf(FINISH); break; + } + printf("%s: (pid %d) ", m, pid); + } + else { + printf(DOWN"%s: ", m); + } + printf("%lus", (unsigned long)(tnow.sec.x < tstatus.x ? 0 : tnow.sec.x-tstatus.x)); + if (pid && !normallyup) printf(", normally down"); + if (!pid && normallyup) printf(", normally up"); + if (pid && svstatus[16]) printf(", paused"); + if (!pid && (svstatus[17] == 'u')) printf(", want up"); + if (pid && (svstatus[17] == 'd')) printf(", want down"); + if (pid && svstatus[18]) printf(", got TERM"); + return pid ? 1 : 2; +} + +static int status(char *unused) +{ + 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"); + } + puts(""); /* 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; */ + if ((pid = fork()) == -1) { + bb_perror_msg(WARN"cannot fork for %s/check", *service); + return 0; + } + if (!pid) { + prog[0] = "./check"; + prog[1] = 0; + close(1); + execve("check", prog, environ); + bb_perror_msg(WARN"cannot run %s/check", *service); + _exit(0); + } + while (wait_pid(&w, pid) == -1) { + if (errno == EINTR) continue; + bb_perror_msg(WARN"cannot wait for child %s/check", *service); + return 0; + } + return !wait_exitcode(w); +} + +static int check(char *a) +{ + unsigned pid; + + if ((r = svstatus_get()) == -1) return -1; + if (r == 0) { if (*a == 'x') return 1; return -1; } + pid = (unsigned char)svstatus[15]; + pid <<= 8; pid += (unsigned char)svstatus[14]; + pid <<= 8; pid += (unsigned char)svstatus[13]; + pid <<= 8; pid += (unsigned char)svstatus[12]; + switch (*a) { + case 'x': return 0; + case 'u': + if (!pid || svstatus[19] != 1) return 0; + if (!checkscript()) return 0; + break; + case 'd': if (pid) return 0; break; + case 'c': if (pid) if (!checkscript()) return 0; break; + case 't': + if (!pid && svstatus[17] == 'd') break; + tai_unpack(svstatus, &tstatus); + if ((tstart.sec.x > tstatus.x) || !pid || svstatus[18] || !checkscript()) + return 0; + break; + case 'o': + tai_unpack(svstatus, &tstatus); + if ((!pid && tstart.sec.x > tstatus.x) || (pid && svstatus[17] != 'd')) + return 0; + } + printf(OK); svstatus_print(*service); puts(""); /* will also flush the output */ + return 1; +} + +static int control(char *a) +{ + if (svstatus_get() <= 0) return -1; + if (svstatus[17] == *a) return 0; + if ((fd = open_write("supervise/control")) == -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) +{ + unsigned opt; + unsigned i, want_exit; + char *x; + + for (i = strlen(*argv); i; --i) + if ((*argv)[i-1] == '/') + break; + *argv += i; + service = argv; + services = 1; + if ((x = getenv("SVDIR"))) varservice = x; + if ((x = getenv("SVWAIT"))) waitsec = xatoul(x); + /* TODO: V can be handled internally by getopt_ulflags */ + opt = getopt32(argc, argv, "w:vV", &x); + if (opt & 1) waitsec = xatoul(x); + if (opt & 2) verbose = 1; + if (opt & 4) usage(); + if (!(action = *argv++)) usage(); + --argc; + service = argv; services = argc; + if (!*service) usage(); + + taia_now(&tnow); tstart = tnow; + if ((curdir = open_read(".")) == -1) + fatal_cannot("open current directory"); + + act = &control; acts = "s"; + if (verbose) cbk = ✓ + switch (*action) { + case 'x': case 'e': + acts = "x"; break; + case 'X': case 'E': + acts = "x"; kll = 1; cbk = ✓ break; + case 'D': + acts = "d"; kll = 1; cbk = ✓ break; + case 'T': + acts = "tc"; kll = 1; cbk = ✓ break; + case 'c': + if (!str_diff(action, "check")) { + act = 0; + acts = "c"; + cbk = ✓ + 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; break; + case 's': + if (!str_diff(action, "shutdown")) { + acts = "x"; + cbk = ✓ + break; + } + if (!str_diff(action, "start")) { + acts = "u"; + cbk = ✓ + break; + } + if (!str_diff(action, "stop")) { + acts = "d"; + cbk = ✓ + break; + } + act = &status; cbk = 0; break; + case 'r': + if (!str_diff(action, "restart")) { + acts = "tcu"; + cbk = ✓ + break; + } + usage(); + case 'f': + if (!str_diff(action, "force-reload")) + { acts = "tc"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-restart")) + { acts = "tcu"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-shutdown")) + { acts = "x"; kll = 1; cbk = ✓ break; } + if (!str_diff(action, "force-stop")) + { acts = "d"; kll = 1; cbk = ✓ break; } + default: + usage(); + } + + servicex = service; + for (i = 0; i < services; ++i) { + if ((**service != '/') && (**service != '.')) { + if ((chdir(varservice) == -1) || (chdir(*service) == -1)) { + fail("cannot change to service directory"); + *service = 0; + } + } else if (chdir(*service) == -1) { + fail("cannot change to service directory"); + *service = 0; + } + if (*service) if (act && (act(acts) == -1)) *service = 0; + if (fchdir(curdir) == -1) fatal_cannot("change to original directory"); + service++; + } + + if (*cbk) + for (;;) { + taia_sub(&tdiff, &tnow, &tstart); + service = servicex; want_exit = 1; + for (i = 0; i < services; ++i, ++service) { + if (!*service) continue; + if ((**service != '/') && (**service != '.')) { + if ((chdir(varservice) == -1) || (chdir(*service) == -1)) { + fail("cannot change to service directory"); + *service = 0; + } + } else if (chdir(*service) == -1) { + fail("cannot change to service directory"); + *service = 0; + } + if (*service) { if (cbk(acts) != 0) *service = 0; else want_exit = 0; } + if (*service && taia_approx(&tdiff) > waitsec) { + kll ? printf(KILL) : printf(TIMEOUT); + if (svstatus_get() > 0) { svstatus_print(*service); ++rc; } + puts(""); /* will also flush the output */ + if (kll) control("k"); + *service = 0; + } + if (fchdir(curdir) == -1) + fatal_cannot("change to original directory"); + } + if (want_exit) break; + usleep(420000); + taia_now(&tnow); + } + return rc > 99 ? 99 : rc; +} diff --git a/runit/svlogd.c b/runit/svlogd.c new file mode 100644 index 0000000..7024c3d --- /dev/null +++ b/runit/svlogd.c @@ -0,0 +1,878 @@ +/* +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 Denis Vlasenko <vda.linux@googlemail.com> */ +/* TODO: depends on runit_lib.c - review and reduce/eliminate */ + +#include <sys/poll.h> +#include <sys/file.h> +#include "busybox.h" +#include "runit_lib.h" + +static unsigned verbose; +static int linemax = 1000; +static int buflen = 1024; +static int linelen; + +static char **fndir; +static int fdwdir; +static int wstat; +static struct taia trotate; + +static char *line; +static unsigned exitasap; +static unsigned rotateasap; +static unsigned reopenasap; +static unsigned linecomplete = 1; +static unsigned tmaxflag; +static iopause_fd in; + +static const char *replace = ""; +static char repl; + +static 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; + /* int (not long) because of taia_uint() usage: */ + unsigned tmax; + int ppid; + int fddir; + int fdcur; + int fdlock; + struct taia trotate; + char fnsave[FMT_PTIME]; + char match; + char matcherr; +} *dir; +static unsigned dirn = 0; + +#define FATAL "fatal: " +#define WARNING "warning: " +#define PAUSE "pausing: " +#define INFO "info: " + +#define usage() bb_show_usage() +static void fatalx(char *m0) +{ + bb_error_msg_and_die(FATAL"%s", m0); +} +static void warn(char *m0) { + bb_perror_msg(WARNING"%s", m0); +} +static void warn2(char *m0, char *m1) +{ + bb_perror_msg(WARNING"%s: %s", m0, m1); +} +static void warnx(char *m0, 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(char *m0) +{ + bb_perror_msg(PAUSE"cannot %s", m0); sleep(3); +} +static void pause2cannot(char *m0, char *m1) +{ + bb_perror_msg(PAUSE"cannot %s %s", m0, m1); + sleep(3); +} + +static char* wstrdup(const char *str) +{ + char *s; + while (!(s = strdup(str))) pause_nomem(); + return s; +} + +static unsigned processorstart(struct logdir *ld) +{ + int pid; + + if (!ld->processor) return 0; + if (ld->ppid) { + warnx("processor already running", ld->name); + return 0; + } + while ((pid = fork()) == -1) + pause2cannot("fork for processor", ld->name); + if (!pid) { + char *prog[4]; + int fd; + + /* child */ + sig_uncatch(sig_term); + sig_uncatch(sig_alarm); + sig_uncatch(sig_hangup); + sig_unblock(sig_term); + sig_unblock(sig_alarm); + sig_unblock(sig_hangup); + + if (verbose) + bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave); + fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY); + if (fd_move(0, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + ld->fnsave[26] = 't'; + fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT); + if (fd_move(1, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + fd = open_read("state"); + if (fd == -1) { + if (errno != ENOENT) + bb_perror_msg_and_die(FATAL"cannot %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); + } + if (fd_move(4, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT); + if (fd_move(5, fd) == -1) + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "move filedescriptor for", ld->name); + + prog[0] = "sh"; + prog[1] = "-c"; + prog[2] = ld->processor; + prog[3] = '\0'; + execve("/bin/sh", prog, environ); + bb_perror_msg_and_die(FATAL"cannot %s processor %s", "run", ld->name); + } + ld->ppid = pid; + return 1; +} + +static unsigned processorstop(struct logdir *ld) +{ + char f[28]; + + if (ld->ppid) { + sig_unblock(sig_hangup); + while (wait_pid(&wstat, ld->ppid) == -1) + pause2cannot("wait for processor", ld->name); + sig_block(sig_hangup); + 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"cannot 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("cannot 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("cannot 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("cannot unlink oldest logfile", ld->name); + } +} + +static unsigned rotate(struct logdir *ld) +{ + struct stat st; + struct taia now; + + if (ld->fddir == -1) { + ld->tmax = 0; + return 0; + } + if (ld->ppid) + while(!processorstop(ld)) + /* wait */; + + 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 { + taia_now(&now); + fmt_taia(ld->fnsave, &now); + errno = 0; + } while ((stat(ld->fnsave, &st) != -1) || (errno != ENOENT)); + + if (ld->tmax && taia_less(&ld->trotate, &now)) { + taia_uint(&ld->trotate, ld->tmax); + taia_add(&ld->trotate, &now, &ld->trotate); + if (taia_less(&ld->trotate, &trotate)) + trotate = ld->trotate; + } + + if (ld->size > 0) { + while (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); + 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); + coe(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 ((i = write(ld->fdcur, s, len)) == -1) { + 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("cannot 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("cannot 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 (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); + 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]; + struct taia now; + char *new, *s, *np; + int i; + struct stat st; + + ld->fddir = open(fn, O_RDONLY|O_NDELAY); + if (ld->fddir == -1) { + warn2("cannot open log directory", (char*)fn); + return 0; + } + coe(ld->fddir); + if (fchdir(ld->fddir) == -1) { + logdir_close(ld); + warn2("cannot 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("cannot lock directory", (char*)fn); + while (fchdir(fdwdir) == -1) + pause1cannot("change to initial working directory"); + return 0; + } + coe(ld->fdlock); + + ld->size = 0; + ld->sizemax = 1000000; + ld->nmax = ld->nmin = 10; + ld->tmax = 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) + warn2("cannot read 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': + 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 }, + { NULL, 0 } + }; + 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 },*/ + { NULL, 0 } + }; + ld->tmax = xatou_sfx(&s[1], mh_suffixes); + if (ld->tmax) { + taia_uint(&ld->trotate, ld->tmax); + taia_add(&ld->trotate, &now, &ld->trotate); + if (!tmaxflag || taia_less(&ld->trotate, &trotate)) + trotate = ld->trotate; + 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 { + taia_now(&now); + fmt_taia(ld->fnsave, &now); + errno = 0; + } while ((stat(ld->fnsave, &st) != -1) || (errno != ENOENT)); + while (rename("current", ld->fnsave) == -1) + pause2cannot("rename current", ld->name); + rmoldest(ld); + i = -1; + } else { + /* Be paranoid: st.st_size can be not just bigger, but WIDER! */ + /* (bug in original svlogd. remove this comment when fixed there) */ + ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size; + } + } else { + if (errno != ENOENT) { + logdir_close(ld); + warn2("cannot 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); + coe(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) +{ + struct taia now; + int l; + int ok = 0; + + tmaxflag = 0; + taia_now(&now); + 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"); +} + +/* Used for reading stdin */ +static int buffer_pread(int fd, char *s, unsigned len) +{ + struct taia now; + int i; + + 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; + } + taia_now(&now); + taia_uint(&trotate, 2744); + taia_add(&trotate, &now, &trotate); + for (i = 0; i < dirn; ++i) + if (dir[i].tmax) { + if (taia_less(&dir[i].trotate, &now)) + rotate(dir+i); + if (taia_less(&dir[i].trotate, &trotate)) + trotate = dir[i].trotate; + } + + while (1) { + /* Comment? */ + sig_unblock(sig_term); + sig_unblock(sig_child); + sig_unblock(sig_alarm); + sig_unblock(sig_hangup); + iopause(&in, 1, &trotate, &now); + sig_block(sig_term); + sig_block(sig_child); + sig_block(sig_alarm); + sig_block(sig_hangup); + i = safe_read(fd, s, len); + if (i >= 0) break; + if (errno != EAGAIN) { + warn("cannot read standard input"); + break; + } + /* else: EAGAIN - normal, repeat silently */ + } + + 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) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "term"); + exitasap = 1; +} + +static void sig_child_handler(int sig_no) +{ + int pid, l; + + if (verbose) + bb_error_msg(INFO"sig%s received", "child"); + while ((pid = wait_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) +{ + if (verbose) + bb_error_msg(INFO"sig%s received", "alarm"); + rotateasap = 1; +} + +static void sig_hangup_handler(int sig_no) +{ + 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) +{ + struct taia now; + char *r,*l,*b; + ssize_t stdin_cnt = 0; + int i; + unsigned opt; + unsigned timestamp = 0; + + opt_complementary = "tt:vv"; + opt = getopt32(argc, 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, 1000); + if (linemax == 0) linemax = 1000; + 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); + coe(fdwdir); + dir = xmalloc(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; + in.fd = 0; + in.events = IOPAUSE_READ; + ndelay_on(in.fd); + + sig_block(sig_term); + sig_block(sig_child); + sig_block(sig_alarm); + sig_block(sig_hangup); + sig_catch(sig_term, sig_term_handler); + sig_catch(sig_child, sig_child_handler); + sig_catch(sig_alarm, sig_alarm_handler); + sig_catch(sig_hangup, sig_hangup_handler); + + logdirs_reopen(); + + /* Each iteration processes one line */ + while (1) { + int printlen; + char *lineptr = line; + char *np; + char ch; + + /* Prepare timestamp if needed */ + if (timestamp) { + char stamp[FMT_PTIME]; + taia_now(&now); + switch (timestamp) { + case 1: + fmt_taia(stamp, &now); + break; + default: /* case 2: */ + fmt_ptime(stamp, &now); + break; + } + memcpy(line, stamp, 25); + line[25] = ' '; + lineptr += 26; + } + + /* lineptr[0..linemax-1] - buffer for stdin */ + /* (possibly has some unprocessed data from prev loop) */ + + /* Refill the buffer if needed */ + np = memchr(lineptr, '\n', stdin_cnt); + i = linemax - stdin_cnt; /* avail. bytes at tail */ + if (i >= 128 && !exitasap && !np) { + int sz = buffer_pread(0, lineptr + stdin_cnt, i); + if (sz <= 0) /* EOF or error on stdin */ + exitasap = 1; + else { + stdin_cnt += sz; + np = memchr(lineptr, '\n', stdin_cnt); + } + } + if (stdin_cnt <= 0 && exitasap) + break; + + /* Search for '\n' (in fact, np already holds the result) */ + linelen = stdin_cnt; + if (np) linelen = np - lineptr + 1; + /* linelen == no of chars incl. '\n' (or == stdin_cnt) */ + ch = lineptr[linelen-1]; + + printlen = linelen + (timestamp ? 26 : 0); + /* write out line[0..printlen-1] to each log destination */ + 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') + full_write(2, line, printlen); + if (ld->match != '+') continue; + buffer_pwrite(i, line, 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 */ + lineptr[0] = ch = '\n'; + linelen = 1; + exitasap = 1; + stdin_cnt = 1; + } else { + linelen = stdin_cnt; + np = memchr(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(2, lineptr, linelen); + if (dir[i].match != '+') continue; + buffer_pwrite(i, lineptr, linelen); + } + } + + /* Move unprocessed data to the front of line */ + stdin_cnt -= linelen; + if (stdin_cnt > 0) /* TODO: slow if buffer is big */ + memmove(lineptr, &lineptr[linelen], stdin_cnt); + } + + for (i = 0; i < dirn; ++i) { + if (dir[i].ppid) + while (!processorstop(&dir[i])) + /* repeat */; + logdir_close(&dir[i]); + } + _exit(0); +} |