From 90b12bd71bb9fc79a4640b9112c13ef529d0196a Mon Sep 17 00:00:00 2001 From: Laurent Bercot Date: Fri, 5 Dec 2014 22:26:11 +0000 Subject: Initial commit --- src/daemontools-extras/deps-exe/s6-envdir | 1 + src/daemontools-extras/deps-exe/s6-envuidgid | 1 + src/daemontools-extras/deps-exe/s6-fghack | 1 + src/daemontools-extras/deps-exe/s6-log | 2 + src/daemontools-extras/deps-exe/s6-notifywhenup | 3 + src/daemontools-extras/deps-exe/s6-setlock | 2 + src/daemontools-extras/deps-exe/s6-setsid | 1 + src/daemontools-extras/deps-exe/s6-setuidgid | 1 + src/daemontools-extras/deps-exe/s6-softlimit | 1 + src/daemontools-extras/deps-exe/s6-tai64n | 2 + src/daemontools-extras/deps-exe/s6-tai64nlocal | 1 + src/daemontools-extras/deps-exe/ucspilogd | 1 + src/daemontools-extras/s6-envdir.c | 40 + src/daemontools-extras/s6-envuidgid.c | 44 + src/daemontools-extras/s6-fghack.c | 68 ++ src/daemontools-extras/s6-log.c | 1265 +++++++++++++++++++++++ src/daemontools-extras/s6-notifywhenup.c | 86 ++ src/daemontools-extras/s6-setlock.c | 87 ++ src/daemontools-extras/s6-setsid.c | 35 + src/daemontools-extras/s6-setuidgid.c | 30 + src/daemontools-extras/s6-softlimit.c | 117 +++ src/daemontools-extras/s6-tai64n.c | 35 + src/daemontools-extras/s6-tai64nlocal.c | 47 + src/daemontools-extras/ucspilogd.c | 117 +++ 24 files changed, 1988 insertions(+) create mode 100644 src/daemontools-extras/deps-exe/s6-envdir create mode 100644 src/daemontools-extras/deps-exe/s6-envuidgid create mode 100644 src/daemontools-extras/deps-exe/s6-fghack create mode 100644 src/daemontools-extras/deps-exe/s6-log create mode 100644 src/daemontools-extras/deps-exe/s6-notifywhenup create mode 100644 src/daemontools-extras/deps-exe/s6-setlock create mode 100644 src/daemontools-extras/deps-exe/s6-setsid create mode 100644 src/daemontools-extras/deps-exe/s6-setuidgid create mode 100644 src/daemontools-extras/deps-exe/s6-softlimit create mode 100644 src/daemontools-extras/deps-exe/s6-tai64n create mode 100644 src/daemontools-extras/deps-exe/s6-tai64nlocal create mode 100644 src/daemontools-extras/deps-exe/ucspilogd create mode 100644 src/daemontools-extras/s6-envdir.c create mode 100644 src/daemontools-extras/s6-envuidgid.c create mode 100644 src/daemontools-extras/s6-fghack.c create mode 100644 src/daemontools-extras/s6-log.c create mode 100644 src/daemontools-extras/s6-notifywhenup.c create mode 100644 src/daemontools-extras/s6-setlock.c create mode 100644 src/daemontools-extras/s6-setsid.c create mode 100644 src/daemontools-extras/s6-setuidgid.c create mode 100644 src/daemontools-extras/s6-softlimit.c create mode 100644 src/daemontools-extras/s6-tai64n.c create mode 100644 src/daemontools-extras/s6-tai64nlocal.c create mode 100644 src/daemontools-extras/ucspilogd.c (limited to 'src/daemontools-extras') diff --git a/src/daemontools-extras/deps-exe/s6-envdir b/src/daemontools-extras/deps-exe/s6-envdir new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-envdir @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-envuidgid b/src/daemontools-extras/deps-exe/s6-envuidgid new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-envuidgid @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-fghack b/src/daemontools-extras/deps-exe/s6-fghack new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-fghack @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-log b/src/daemontools-extras/deps-exe/s6-log new file mode 100644 index 0000000..1840bc1 --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-log @@ -0,0 +1,2 @@ +-lskarnet +${TAINNOW_LIB} diff --git a/src/daemontools-extras/deps-exe/s6-notifywhenup b/src/daemontools-extras/deps-exe/s6-notifywhenup new file mode 100644 index 0000000..58a34e0 --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-notifywhenup @@ -0,0 +1,3 @@ +-ls6 +-lskarnet +${TAINNOW_LIB} diff --git a/src/daemontools-extras/deps-exe/s6-setlock b/src/daemontools-extras/deps-exe/s6-setlock new file mode 100644 index 0000000..1840bc1 --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-setlock @@ -0,0 +1,2 @@ +-lskarnet +${TAINNOW_LIB} diff --git a/src/daemontools-extras/deps-exe/s6-setsid b/src/daemontools-extras/deps-exe/s6-setsid new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-setsid @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-setuidgid b/src/daemontools-extras/deps-exe/s6-setuidgid new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-setuidgid @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-softlimit b/src/daemontools-extras/deps-exe/s6-softlimit new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-softlimit @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/s6-tai64n b/src/daemontools-extras/deps-exe/s6-tai64n new file mode 100644 index 0000000..a11a5f4 --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-tai64n @@ -0,0 +1,2 @@ +-lskarnet +${SYSCLOCK_LIB} diff --git a/src/daemontools-extras/deps-exe/s6-tai64nlocal b/src/daemontools-extras/deps-exe/s6-tai64nlocal new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/s6-tai64nlocal @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/deps-exe/ucspilogd b/src/daemontools-extras/deps-exe/ucspilogd new file mode 100644 index 0000000..e7187fe --- /dev/null +++ b/src/daemontools-extras/deps-exe/ucspilogd @@ -0,0 +1 @@ +-lskarnet diff --git a/src/daemontools-extras/s6-envdir.c b/src/daemontools-extras/s6-envdir.c new file mode 100644 index 0000000..394253e --- /dev/null +++ b/src/daemontools-extras/s6-envdir.c @@ -0,0 +1,40 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include + +#define USAGE "s6-envdir [ -I | -i ] [ -n ] [ -f ] [ -c nullchar ] dir prog..." + +int main (int argc, char const *const *argv, char const *const *envp) +{ + stralloc modifs = STRALLOC_ZERO ; + subgetopt_t l = SUBGETOPT_ZERO ; + int insist = 1 ; + unsigned int options = 0 ; + char nullis = '\n' ; + PROG = "s6-envdir" ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "Iinfc:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'I' : insist = 0 ; break ; + case 'i' : insist = 1 ; break ; + case 'n' : options |= SKALIBS_ENVDIR_NOCHOMP ; break ; + case 'f' : options |= SKALIBS_ENVDIR_VERBATIM ; break ; + case 'c' : nullis = *l.arg ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + if ((envdir_internal(*argv++, &modifs, options, nullis) < 0) && (insist || (errno != ENOENT))) + strerr_diefu1sys(111, "envdir") ; + pathexec_r(argv, envp, env_len(envp), modifs.s, modifs.len) ; + strerr_dieexec(111, argv[0]) ; +} diff --git a/src/daemontools-extras/s6-envuidgid.c b/src/daemontools-extras/s6-envuidgid.c new file mode 100644 index 0000000..64521b0 --- /dev/null +++ b/src/daemontools-extras/s6-envuidgid.c @@ -0,0 +1,44 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define USAGE "s6-envuidgid username prog..." + +int main (int argc, char const *const *argv, char const *const *envp) +{ + PROG = "s6-envuidgid" ; + if (argc < 3) strerr_dieusage(100, USAGE) ; + { + char fmt[UINT64_FMT] ; + struct passwd *pw = getpwnam(argv[1]) ; + if (!pw) strerr_dief2x(1, "unknown user: ", argv[1]) ; + fmt[gid_fmt(fmt, pw->pw_gid)] = 0 ; + if (!pathexec_env("GID", fmt)) + strerr_diefu1sys(111, "update environment") ; + fmt[uint64_fmt(fmt, pw->pw_uid)] = 0 ; + if (!pathexec_env("UID", fmt)) + strerr_diefu1sys(111, "update environment") ; + } + + { + gid_t tab[NGROUPS_MAX] ; + int n = prot_readgroups(argv[1], tab, NGROUPS_MAX) ; + if (n < 0) + strerr_diefu2sys(111, "get supplementary groups for ", argv[1]) ; + { + char fmt[GID_FMT * n] ; + fmt[gid_fmtlist(fmt, tab, n)] = 0 ; + if (!pathexec_env("GIDLIST", fmt)) + strerr_diefu1sys(111, "update environment") ; + } + } + pathexec_fromenv(argv+2, envp, env_len(envp)) ; + strerr_dieexec(111, argv[2]) ; +} diff --git a/src/daemontools-extras/s6-fghack.c b/src/daemontools-extras/s6-fghack.c new file mode 100644 index 0000000..1415706 --- /dev/null +++ b/src/daemontools-extras/s6-fghack.c @@ -0,0 +1,68 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include + +#define USAGE "s6-fghack prog..." + +int main (int argc, char const *const *argv, char const *const *envp) +{ + int p[2] ; + int pcoe[2] ; + pid_t pid ; + char dummy ; + PROG = "s6-fghack" ; + if (argc < 2) strerr_dieusage(100, USAGE) ; + if (pipe(p) < 0) strerr_diefu1sys(111, "create hackpipe") ; + if (pipe(pcoe) < 0) strerr_diefu1sys(111, "create coepipe") ; + + switch (pid = fork()) + { + case -1 : strerr_diefu1sys(111, "fork") ; + case 0 : + { + int i = 0 ; + fd_close(p[0]) ; + fd_close(pcoe[0]) ; + if (coe(pcoe[1]) < 0) _exit(111) ; + for (; i < 30 ; i++) dup(p[1]) ; /* hack. gcc's warning is justified. */ + pathexec_run(argv[1], argv+1, envp) ; + i = errno ; + if (fd_write(pcoe[1], "", 1) < 1) _exit(111) ; + _exit(i) ; + } + } + + fd_close(p[1]) ; + fd_close(pcoe[1]) ; + + switch (fd_read(pcoe[0], &dummy, 1)) + { + case -1 : strerr_diefu1sys(111, "read on coepipe") ; + case 1 : + { + int wstat ; + if (wait_pid(pid, &wstat) < 0) strerr_diefu1sys(111, "wait_pid") ; + errno = WEXITSTATUS(wstat) ; + strerr_dieexec(111, argv[1]) ; + } + } + + fd_close(pcoe[0]) ; + + p[1] = fd_read(p[0], &dummy, 1) ; + if (p[1] < 0) strerr_diefu1sys(111, "read on hackpipe") ; + if (p[1]) strerr_dief2x(102, argv[1], " wrote on hackpipe") ; + + { + int wstat ; + if (wait_pid(pid, &wstat) < 0) strerr_diefu1sys(111, "wait_pid") ; + if (WIFSIGNALED(wstat)) strerr_dief2x(111, argv[2], " crashed") ; + return WEXITSTATUS(wstat) ; + } +} diff --git a/src/daemontools-extras/s6-log.c b/src/daemontools-extras/s6-log.c new file mode 100644 index 0000000..e788016 --- /dev/null +++ b/src/daemontools-extras/s6-log.c @@ -0,0 +1,1265 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include /* for rename() */ +#include /* for qsort() */ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USAGE "s6-log [ -q | -v ] [ -b ] [ -p ] [ -t ] [ -e ] logging_script" +#define dienomem() strerr_diefu1sys(111, "stralloc_catb") + +static int flagstampalert = 0 ; +static int flagstamp = 0 ; +static int flagprotect = 0 ; +static int flagexiting = 0 ; +static unsigned int verbosity = 1 ; + +static stralloc indata = STRALLOC_ZERO ; + + +/* Begin datatypes. Get ready for some lulz. */ + +typedef int qcmpfunc_t (void const *, void const *) ; +typedef qcmpfunc_t *qcmpfunc_t_ref ; + +typedef enum rotstate_e rotstate_t, *rotstate_t_ref ; +enum rotstate_e +{ + ROTSTATE_WRITABLE, + ROTSTATE_START, + ROTSTATE_RENAME, + ROTSTATE_NEWCURRENT, + ROTSTATE_CHMODPREVIOUS, + ROTSTATE_FINISHPREVIOUS, + ROTSTATE_RUNPROCESSOR, + ROTSTATE_WAITPROCESSOR, + ROTSTATE_SYNCPROCESSED, + ROTSTATE_SYNCNEWSTATE, + ROTSTATE_UNLINKPREVIOUS, + ROTSTATE_RENAMESTATE, + ROTSTATE_FINISHPROCESSED, + ROTSTATE_ENDFCHMOD, + ROTSTATE_END +} ; + +typedef enum seltype_e seltype_t, *seltype_t_ref ; +enum seltype_e +{ + SELTYPE_DEFAULT, + SELTYPE_PLUS, + SELTYPE_MINUS, + SELTYPE_PHAIL +} ; + +typedef struct sel_s sel_t, *sel_t_ref ; +struct sel_s +{ + seltype_t type ; + regex_t re ; +} ; + +#define SEL_ZERO { .type = SELTYPE_PHAIL } + +static void sel_free (sel_t_ref s) +{ + if (s->type != SELTYPE_DEFAULT) regfree(&s->re) ; + s->type = SELTYPE_PHAIL ; +} + +typedef enum acttype_e acttype_t, *acttype_t_ref ; +enum acttype_e +{ + ACTTYPE_FD2, + ACTTYPE_STATUS, + ACTTYPE_DIR, + ACTTYPE_PHAIL +} ; + +typedef struct as_fd2_s as_fd2_t, *as_fd2_t_ref ; +struct as_fd2_s +{ + unsigned int size ; +} ; + +typedef struct as_status_s as_status_t, *as_status_t_ref ; +struct as_status_s +{ + stralloc content ; + char const *file ; +} ; + +static void as_status_free (as_status_t_ref ap) +{ + stralloc_free(&ap->content) ; + ap->file = 0 ; +} + +typedef struct as_dir_s as_dir_t, *as_dir_t_ref ; +struct as_dir_s +{ + unsigned int lindex ; +} ; + +typedef union actstuff_u actstuff_t, *actstuff_t_ref ; +union actstuff_u +{ + as_fd2_t fd2 ; + as_status_t status ; + as_dir_t dir ; +} ; + +typedef struct act_s act_t, *act_t_ref ; +struct act_s +{ + acttype_t type ; + actstuff_t data ; +} ; + +static void act_free (act_t_ref ap) +{ + switch (ap->type) + { + case ACTTYPE_FD2 : + break ; + case ACTTYPE_STATUS : + as_status_free(&ap->data.status) ; + break ; + case ACTTYPE_DIR : + break ; + default : break ; + } + ap->type = ACTTYPE_PHAIL ; +} + +typedef struct scriptelem_s scriptelem_t, *scriptelem_t_ref ; +struct scriptelem_s +{ + genalloc selections ; /* array of sel_t */ + genalloc actions ; /* array of act_t */ +} ; + +#define SCRIPTELEM_ZERO { .selections = GENALLOC_ZERO, .actions = GENALLOC_ZERO } + +static void scriptelem_free (scriptelem_t_ref se) +{ + scriptelem_t zero = SCRIPTELEM_ZERO ; + genalloc_deepfree(sel_t, &se->selections, &sel_free) ; + genalloc_deepfree(act_t, &se->actions, &act_free) ; + *se = zero ; +} + +typedef void inputprocfunc_t (scriptelem_t const *, unsigned int) ; +typedef inputprocfunc_t *inputprocfunc_t_ref ; + +typedef struct logdir_s logdir_t, *logdir_t_ref ; +struct logdir_s +{ + bufalloc out ; + tain_t retrytto ; + tain_t deadline ; + uint64 maxdirsize ; + uint32 b ; + uint32 n ; + uint32 s ; + uint32 tolerance ; + unsigned int pid ; + char const *dir ; + char const *processor ; + int fd ; + int fdlock ; + rotstate_t rstate ; +} ; + +#define LOGDIR_ZERO { \ + .out = BUFALLOC_ZERO, \ + .retrytto = TAIN_ZERO, \ + .deadline = TAIN_ZERO, \ + .maxdirsize = 0, \ + .b = 0, \ + .n = 0, \ + .s = 0, \ + .tolerance = 0, \ + .pid = 0, \ + .dir = 0, \ + .processor = 0, \ + .fd = -1, \ + .fdlock = -1, \ + .rstate = ROTSTATE_WRITABLE \ +} + + /* If freeing a logdir before exiting is ever needed: +static void logdir_free (logdir_t_ref ldp) +{ + bufalloc_free(&ldp->out) ; + fd_close(ldp->fd) ; ldp->fd = -1 ; + fd_close(ldp->fdlock) ; ldp->fdlock = -1 ; +} + */ + +/* End datatypes. All of this was just to optimize the script interpretation. :-) */ + +static genalloc logdirs = GENALLOC_ZERO ; /* array of logdir_t */ + +typedef struct filesize_s filesize_t, *filesize_t_ref ; +struct filesize_s +{ + uint64 size ; + char name[28] ; +} ; + +static int filesize_cmp (filesize_t const *a, filesize_t const *b) +{ + return byte_diff(a->name+1, 26, b->name+1) ; +} + +static int name_is_relevant (char const *name) +{ + if (name[0] != '@') return 0 ; + if (str_len(name) != 27) return 0 ; + { + char tmp[12] ; + if (!ucharn_scan(name+1, tmp, 12)) return 0 ; + } + if (name[25] != '.') return 0 ; + if ((name[26] != 's') && (name[26] != 'u')) return 0 ; + return 1 ; +} + +static inline int logdir_trim (logdir_t_ref ldp) +{ + unsigned int n = 0 ; + DIR *dir = opendir(ldp->dir) ; + if (!dir) return -1 ; + for (;;) + { + direntry *d ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + if (name_is_relevant(d->d_name)) n++ ; + } + if (errno) + { + register int e = errno ; + dir_close(dir) ; + errno = e ; + return -1 ; + } + rewinddir(dir) ; + { + filesize_t blurgh[n] ; + uint64 totalsize = 0 ; + unsigned int dirlen = str_len(ldp->dir) ; + unsigned int i = 0 ; + char fullname[dirlen + 29] ; + byte_copy(fullname, dirlen, ldp->dir) ; + fullname[dirlen] = '/' ; + for (;;) + { + struct stat st ; + direntry *d ; + errno = 0 ; + d = readdir(dir) ; + if (!d) break ; + if (!name_is_relevant(d->d_name)) continue ; + if (i >= n) { errno = EBUSY ; break ; } + byte_copy(fullname + dirlen + 1, 28, d->d_name) ; + if (stat(fullname, &st) < 0) + { + if (verbosity) strerr_warnwu2sys("stat ", fullname) ; + continue ; + } + byte_copy(blurgh[i].name, 28, d->d_name) ; + blurgh[i].size = st.st_size ; + totalsize += st.st_size ; + i++ ; + } + if (errno) + { + register int e = errno ; + dir_close(dir) ; + errno = e ; + return -1 ; + } + dir_close(dir) ; + if ((i <= ldp->n) && (!ldp->maxdirsize || (totalsize <= ldp->maxdirsize))) + return 0 ; + qsort(blurgh, i, sizeof(filesize_t), (qcmpfunc_t_ref)&filesize_cmp) ; + n = 0 ; + while ((i > ldp->n + n) || (ldp->maxdirsize && (totalsize > ldp->maxdirsize))) + { + byte_copy(fullname + dirlen + 1, 28, blurgh[n].name) ; + if (unlink(fullname) < 0) + { + if (errno == ENOENT) totalsize -= blurgh[n].size ; + if (verbosity) strerr_warnwu2sys("unlink ", fullname) ; + } + else totalsize -= blurgh[n].size ; + n++ ; + } + } + return n ; +} + +static int finish (logdir_t *ldp, char const *name, char suffix) +{ + struct stat st ; + unsigned int dirlen = str_len(ldp->dir) ; + unsigned int namelen = str_len(name) ; + char x[dirlen + namelen + 2] ; + byte_copy(x, dirlen, ldp->dir) ; + x[dirlen] = '/' ; + byte_copy(x + dirlen + 1, namelen + 1, name) ; + if (stat(x, &st) < 0) return (errno == ENOENT) ; + if (st.st_nlink == 1) + { + char y[dirlen + 29] ; + byte_copy(y, dirlen, ldp->dir) ; + y[dirlen] = '/' ; + timestamp_g(y + dirlen + 1) ; + y[dirlen + 26] = '.' ; + y[dirlen + 27] = suffix ; + y[dirlen + 28] = 0 ; + if (link(x, y) < 0) return 0 ; + } + if (unlink(x) < 0) return 0 ; + return logdir_trim(ldp) ; +} + +static inline void exec_processor (logdir_t_ref ldp) +{ + char const *cargv[4] = { "execlineb", "-Pc", ldp->processor, 0 } ; + unsigned int dirlen = str_len(ldp->dir) ; + int fd ; + char x[dirlen + 10] ; + PROG = "s6-log (processor child)" ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 10, "/previous") ; + fd = open_readb(x) ; + if (fd < 0) strerr_diefu2sys(111, "open_readb ", x) ; + if (fd_move(0, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ; + byte_copy(x + dirlen + 1, 10, "processed") ; + fd = open_trunc(x) ; + if (fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ; + if (fd_move(1, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ; + byte_copy(x + dirlen + 1, 6, "state") ; + fd = open_readb(x) ; + if (fd < 0) strerr_diefu2sys(111, "open_readb ", x) ; + if (fd_move(4, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ; + byte_copy(x + dirlen + 1, 9, "newstate") ; + fd = open_trunc(x) ; + if (fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ; + if (fd_move(5, fd) < 0) strerr_diefu2sys(111, "fd_move ", x) ; + selfpipe_finish() ; + sig_restore(SIGPIPE) ; + pathexec_run(cargv[0], cargv, (char const *const *)environ) ; + strerr_dieexec(111, cargv[0]) ; +} + +static int rotator (logdir_t_ref ldp) +{ + unsigned int dirlen = str_len(ldp->dir) ; + switch (ldp->rstate) + { + case ROTSTATE_START : + { + if (fd_sync(ldp->fd) < 0) + { + if (verbosity) strerr_warnwu3sys("fd_sync ", ldp->dir, "/current") ; + goto fail ; + } + tain_now_g() ; + ldp->rstate = ROTSTATE_RENAME ; + } + case ROTSTATE_RENAME : + { + char current[dirlen + 9] ; + char previous[dirlen + 10] ; + byte_copy(current, dirlen, ldp->dir) ; + byte_copy(current + dirlen, 9, "/current") ; + byte_copy(previous, dirlen, ldp->dir) ; + byte_copy(previous + dirlen, 10, "/previous") ; + if (rename(current, previous) < 0) + { + if (verbosity) strerr_warnwu4sys("rename ", current, " to ", previous) ; + goto fail ; + } + ldp->rstate = ROTSTATE_NEWCURRENT ; + } + case ROTSTATE_NEWCURRENT : + { + int fd ; + char x[dirlen + 9] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 9, "/current") ; + fd = open_append(x) ; + if (fd < 0) + { + if (verbosity) strerr_warnwu2sys("open_append ", x) ; + goto fail ; + } + if (coe(fd) < 0) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + if (verbosity) strerr_warnwu2sys("coe ", x) ; + goto fail ; + } + if (fd_chmod(fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + if (verbosity) strerr_warnwu3sys("fchmod ", x, " to 0644") ; + goto fail ; + } + fd_close(ldp->fd) ; + ldp->fd = fd ; + ldp->b = 0 ; + ldp->rstate = ROTSTATE_CHMODPREVIOUS ; + } + case ROTSTATE_CHMODPREVIOUS : + { + char x[dirlen + 10] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 10, "/previous") ; + if (chmod(x, S_IRWXU | S_IRGRP | S_IROTH) < 0) + { + if (verbosity) strerr_warnwu3sys("chmod ", x, " to 0744") ; + goto fail ; + } + if (ldp->processor) goto runprocessor ; + ldp->rstate = ROTSTATE_FINISHPREVIOUS ; + } + case ROTSTATE_FINISHPREVIOUS : + { + if (finish(ldp, "previous", 's') < 0) + { + if (verbosity) strerr_warnwu2sys("finish previous .s to logdir ", ldp->dir) ; + goto fail ; + } + tain_copynow(&ldp->deadline) ; + ldp->rstate = ROTSTATE_WRITABLE ; + break ; + } + runprocessor : + ldp->rstate = ROTSTATE_RUNPROCESSOR ; + case ROTSTATE_RUNPROCESSOR : + { + int pid = fork() ; + if (pid < 0) + { + if (verbosity) strerr_warnwu2sys("fork processor for logdir ", ldp->dir) ; + goto fail ; + } + else if (!pid) exec_processor(ldp) ; + ldp->pid = (unsigned int)pid ; + tain_add_g(&ldp->deadline, &tain_infinite_relative) ; + ldp->rstate = ROTSTATE_WAITPROCESSOR ; + } + case ROTSTATE_WAITPROCESSOR : + { + return (errno = EAGAIN, 0) ; + } + case ROTSTATE_SYNCPROCESSED : + { + int fd ; + char x[dirlen + 11] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 11, "/processed") ; + fd = open_append(x) ; + if (fd < 0) + { + if (verbosity) strerr_warnwu2sys("open_append ", x) ; + goto fail ; + } + if (fd_sync(fd) < 0) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + if (verbosity) strerr_warnwu2sys("fd_sync ", x) ; + goto fail ; + } + tain_now_g() ; + if (fd_chmod(fd, S_IRWXU | S_IRGRP | S_IROTH) < 0) + { + register int e = errno ; + fd_close(fd) ; + errno = e ; + if (verbosity) strerr_warnwu3sys("fd_chmod ", x, " to 0744") ; + goto fail ; + } + fd_close(fd) ; + ldp->rstate = ROTSTATE_SYNCNEWSTATE ; + } + case ROTSTATE_SYNCNEWSTATE : + { + int fd ; + char x[dirlen + 10] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 10, "/newstate") ; + fd = open_append(x) ; + if (ldp->fd < 0) + { + if (verbosity) strerr_warnwu2sys("open_append ", x) ; + goto fail ; + } + if (fd_sync(fd) < 0) + { + if (verbosity) strerr_warnwu2sys("fd_sync ", x) ; + goto fail ; + } + tain_now_g() ; + fd_close(fd) ; + ldp->rstate = ROTSTATE_UNLINKPREVIOUS ; + } + case ROTSTATE_UNLINKPREVIOUS : + { + char x[dirlen + 10] ; + byte_copy(x, dirlen, ldp->dir) ; + byte_copy(x + dirlen, 10, "/previous") ; + if ((unlink(x) < 0) && (errno != ENOENT)) + { + if (verbosity) strerr_warnwu2sys("open_append ", x) ; + goto fail ; + } + ldp->rstate = ROTSTATE_RENAMESTATE ; + } + case ROTSTATE_RENAMESTATE : + { + char newstate[dirlen + 10] ; + char state[dirlen + 7] ; + byte_copy(newstate, dirlen, ldp->dir) ; + byte_copy(state, dirlen, ldp->dir) ; + byte_copy(newstate + dirlen, 10, "/newstate") ; + byte_copy(state + dirlen, 7, "/state") ; + if (rename(newstate, state) < 0) + { + if (verbosity) strerr_warnwu4sys("rename ", newstate, " to ", state) ; + goto fail ; + } + ldp->rstate = ROTSTATE_FINISHPROCESSED ; + } + case ROTSTATE_FINISHPROCESSED : + { + if (finish(ldp, "processed", 's') < 0) + { + if (verbosity) strerr_warnwu2sys("finish processed .s to logdir ", ldp->dir) ; + goto fail ; + } + tain_copynow(&ldp->deadline) ; + ldp->rstate = ROTSTATE_WRITABLE ; + break ; + } + default : strerr_dief1x(101, "inconsistent state in rotator()") ; + } + return 1 ; + fail: + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + return 0 ; +} + +static int logdir_write (int i, char const *s, unsigned int len) +{ + logdir_t_ref ldp = genalloc_s(logdir_t, &logdirs) + (unsigned int)i ; + int r ; + unsigned int n = len ; + { + unsigned int m = byte_rchr(s, n, '\n') ; + if (m < n) n = m+1 ; + } + r = fd_write(ldp->fd, s, n) ; + if (r < 0) + { + if (!error_isagain(errno)) + { + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + if (verbosity) strerr_warnwu3sys("write to ", ldp->dir, "/current") ; + } + return r ; + } + ldp->b += r ; + if ((ldp->b + ldp->tolerance >= ldp->s) && (s[r-1] == '\n')) + { + ldp->rstate = ROTSTATE_START ; + rotator(ldp) ; + } + return r ; +} + +static inline void rotate_or_flush (logdir_t *ldp) +{ + if ((ldp->rstate != ROTSTATE_WRITABLE) && !rotator(ldp)) return ; + if (ldp->b >= ldp->s) + { + ldp->rstate = ROTSTATE_START ; + if (!rotator(ldp)) return ; + } + bufalloc_flush(&ldp->out) ; +} + +static inline void logdir_init (logdir_t *ap, uint32 s, uint32 n, uint32 tolerance, uint64 maxdirsize, tain_t const *retrytto, char const *processor, char const *name, unsigned int index) +{ + struct stat st ; + unsigned int dirlen = str_len(name) ; + int r ; + char x[dirlen + 11] ; + ap->s = s ; + ap->n = n ; + ap->pid = 0 ; + ap->tolerance = tolerance ; + ap->maxdirsize = maxdirsize ; + ap->retrytto = *retrytto ; + ap->processor = processor ; + ap->dir = name ; + ap->fd = -1 ; + ap->rstate = ROTSTATE_WRITABLE ; + r = mkdir(ap->dir, S_IRWXU | S_ISGID) ; + if ((r < 0) && (errno != EEXIST)) strerr_diefu2sys(111, "mkdir ", name) ; + byte_copy(x, dirlen, name) ; + byte_copy(x + dirlen, 6, "/lock") ; + ap->fdlock = open_append(x) ; + if ((ap->fdlock) < 0) strerr_diefu2sys(111, "open_append ", x) ; + if (lock_exnb(ap->fdlock) < 0) strerr_diefu2sys(111, "lock_exnb ", x) ; + if (coe(ap->fdlock) < 0) strerr_diefu2sys(111, "coe ", x) ; + byte_copy(x + dirlen + 1, 8, "current") ; + if (stat(x, &st) < 0) + { + if (errno != ENOENT) strerr_diefu2sys(111, "stat ", x) ; + } + else if (st.st_mode & S_IXUSR) goto opencurrent ; + byte_copy(x + dirlen + 1, 6, "state") ; + unlink(x) ; + byte_copy(x + dirlen + 1, 9, "newstate") ; + unlink(x) ; + { + int flagprocessed = 0 ; + byte_copy(x + dirlen + 1, 10, "processed") ; + if (stat(x, &st) < 0) + { + if (errno != ENOENT) strerr_diefu2sys(111, "stat ", x) ; + } + else if (st.st_mode & S_IXUSR) flagprocessed = 1 ; + if (flagprocessed) + { + byte_copy(x + dirlen + 1, 9, "previous") ; + unlink(x) ; + if (finish(ap, "processed", 's') < 0) + strerr_diefu2sys(111, "finish processed .s for logdir ", ap->dir) ; + } + else + { + unlink(x) ; + if (finish(ap, "previous", 'u') < 0) + strerr_diefu2sys(111, "finish previous .u for logdir ", ap->dir) ; + } + } + if (finish(ap, "current", 'u') < 0) + strerr_diefu2sys(111, "finish current .u for logdir ", ap->dir) ; + byte_copy(x + dirlen + 1, 6, "state") ; + ap->fd = open_trunc(x) ; + if (ap->fd < 0) strerr_diefu2sys(111, "open_trunc ", x) ; + fd_close(ap->fd) ; + st.st_size = 0 ; + byte_copy(x + dirlen + 1, 8, "current") ; + opencurrent: + ap->fd = open_append(x) ; + if (ap->fd < 0) strerr_diefu2sys(111, "open_append ", x) ; + if (fd_chmod(ap->fd, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) == -1) + strerr_diefu2sys(111, "fd_chmod ", x) ; + if (coe(ap->fd) < 0) strerr_diefu2sys(111, "coe ", x) ; + ap->b = st.st_size ; + tain_copynow(&ap->deadline) ; + bufalloc_init(&ap->out, &logdir_write, (int)index) ; +} + + + /* Script */ + +static int script_update (genalloc *sc, genalloc *sa, genalloc *aa) +{ + scriptelem_t foo ; + genalloc_shrink(sel_t, sa) ; + genalloc_shrink(act_t, aa) ; + foo.selections = *sa ; + foo.actions = *aa ; + if (!genalloc_append(scriptelem_t, sc, &foo)) return 0 ; + *sa = genalloc_zero ; + *aa = genalloc_zero ; + return 1 ; +} + +static inline int script_init (genalloc *sc, char const *const *argv) +{ + tain_t cur_retrytto ; + unsigned int cur_fd2_size = 200 ; + unsigned int cur_status_size = 1001 ; + uint32 cur_s = 99999 ; + uint32 cur_n = 10 ; + uint32 cur_tolerance = 2000 ; + uint64 cur_maxdirsize = 0 ; + genalloc cur_selections = GENALLOC_ZERO ; /* sel_t */ + genalloc cur_actions = GENALLOC_ZERO ; /* act_t */ + char const *cur_processor = 0 ; + int flagacted = 0 ; + tain_uint(&cur_retrytto, 2) ; + + for (; *argv ; argv++) + { + switch (**argv) + { + case 'f' : + { + sel_t selitem ; + if (flagacted) + { + if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ; + flagacted = 0 ; + } + selitem.type = SELTYPE_DEFAULT ; + if (!genalloc_append(sel_t, &cur_selections, &selitem)) return 0 ; + break ; + } + case '+' : + case '-' : + { + sel_t selitem ; + int r ; + if (flagacted) + { + if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ; + flagacted = 0 ; + } + selitem.type = (**argv == '+') ? SELTYPE_PLUS : SELTYPE_MINUS ; + r = regcomp(&selitem.re, *argv+1, REG_EXTENDED | REG_NOSUB | REG_NEWLINE) ; + if (r == REG_ESPACE) return (errno = ENOMEM, 0) ; + if (r) goto fail ; + if (!genalloc_append(sel_t, &cur_selections, &selitem)) return 0 ; + break ; + } + case 'n' : + { + if (!uint320_scan(*argv + 1, &cur_n)) goto fail ; + break ; + } + case 's' : + { + if (!uint320_scan(*argv + 1, &cur_s)) goto fail ; + if (cur_s < 4096) cur_s = 4096 ; + if (cur_s > 16777215) cur_s = 16777215 ; + break ; + } + case 'S' : + { + if (!uint640_scan(*argv + 1, &cur_maxdirsize)) goto fail ; + break ; + } + case 'l' : + { + if (!uint320_scan(*argv + 1, &cur_tolerance)) goto fail ; + if (cur_tolerance > (cur_s >> 1)) + strerr_dief3x(100, "directive ", *argv, " conflicts with previous s directive") ; + break ; + } + case 'r' : + { + uint32 t ; + if (!uint320_scan(*argv + 1, &t)) goto fail ; + if (!tain_from_millisecs(&cur_retrytto, (int)t)) return (errno = EINVAL, 0) ; + break ; + } + case 'E' : + { + if (!uint0_scan(*argv + 1, &cur_fd2_size)) goto fail ; + break ; + } + case '^' : + { + if (!uint0_scan(*argv + 1, &cur_status_size)) goto fail ; + break ; + } + case '!' : + { + cur_processor = (*argv)[1] ? *argv + 1 : 0 ; + break ; + } + case 'e' : + { + act_t a ; + flagacted = 1 ; + a.type = ACTTYPE_FD2 ; + a.data.fd2.size = cur_fd2_size ; + if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ; + break ; + } + case '=' : + { + act_t a ; + flagacted = 1 ; + a.type = ACTTYPE_STATUS ; + a.data.status.file = *argv + 1 ; + a.data.status.content = stralloc_zero ; + if (cur_status_size && !stralloc_ready_tuned(&a.data.status.content, cur_status_size, 0, 0, 1)) return 0 ; + a.data.status.content.len = cur_status_size ; + if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ; + break ; + } + case '.' : + case '/' : + { + act_t a ; + logdir_t ld = LOGDIR_ZERO ; + flagacted = 1 ; + a.type = ACTTYPE_DIR ; + a.data.dir.lindex = genalloc_len(logdir_t, &logdirs) ; + if (!genalloc_append(act_t, &cur_actions, &a)) return 0 ; + logdir_init(&ld, cur_s, cur_n, cur_tolerance, cur_maxdirsize, &cur_retrytto, cur_processor, *argv, genalloc_len(logdir_t, &logdirs)) ; + if (!genalloc_append(logdir_t, &logdirs, &ld)) return 0 ; + break ; + } + default : goto fail ; + } + } + if (flagacted) + { + if (!script_update(sc, &cur_selections, &cur_actions)) return 0 ; + } + else + { + genalloc_deepfree(sel_t, &cur_selections, &sel_free) ; + if (verbosity) strerr_warnw1x("ignoring extraneous non-action directives") ; + } + genalloc_shrink(logdir_t, &logdirs) ; + genalloc_shrink(scriptelem_t, sc) ; + if (!genalloc_len(scriptelem_t, sc)) + strerr_dief1x(100, "no action directive specified") ; + return 1 ; + fail: + strerr_dief2x(100, "unrecognized directive: ", *argv) ; +} + +static inline void doit_fd2 (as_fd2_t const *ap, char const *s, unsigned int len) +{ + if (flagstampalert) + { + char fmt[TIMESTAMP+1] ; + tain_now_g() ; + timestamp_g(fmt) ; + fmt[TIMESTAMP] = ' ' ; + buffer_put(buffer_2, fmt, TIMESTAMP+1) ; + } + buffer_puts(buffer_2, PROG) ; + buffer_puts(buffer_2, ": alert: ") ; + if (ap->size && len > ap->size) len = ap->size ; + buffer_put(buffer_2, s, len) ; + if (len == ap->size) buffer_puts(buffer_2, "...") ; + buffer_putflush(buffer_2, "\n", 1) ; +} + +static inline void doit_status (as_status_t const *ap, char const *s, unsigned int len) +{ + if (ap->content.len) + { + register unsigned int i ; + if (len > ap->content.len) len = ap->content.len ; + byte_copy(ap->content.s, len, s) ; + for (i = len ; i < ap->content.len ; i++) ap->content.s[i] = '\n' ; + if (!openwritenclose_suffix_sync(ap->file, ap->content.s, ap->content.len, ".new")) + strerr_warnwu2sys("openwritenclose ", ap->file) ; + } + else if (!openwritenclose_suffix_sync(ap->file, s, len, ".new")) + strerr_warnwu2sys("openwritenclose ", ap->file) ; +} + +static inline void doit_dir (as_dir_t const *ap, char const *s, unsigned int len) +{ + logdir_t_ref ldp = genalloc_s(logdir_t, &logdirs) + ap->lindex ; + if (!bufalloc_put(&ldp->out, s, len) || !bufalloc_put(&ldp->out, "\n", 1)) + strerr_diefu1sys(111, "bufalloc_put") ; +} + + + /* The script interpreter. */ + +static inline void doit (scriptelem_t const *se, unsigned int n, char const *s, unsigned int len) +{ + int flagselected = 1 ; + int flagacted = 0 ; + unsigned int i = 0 ; + for (; i < n ; i++) + { + unsigned int sellen = genalloc_len(sel_t, &se[i].selections) ; + sel_t *sels = genalloc_s(sel_t, &se[i].selections) ; + unsigned int j = 0 ; + for (; j < sellen ; j++) + { + switch (sels[j].type) + { + case SELTYPE_DEFAULT : + flagselected = !flagacted ; + break ; + case SELTYPE_PLUS : + if (!flagselected && !regexec(&sels[j].re, flagstamp ? s+TIMESTAMP+1 : s, 0, 0, 0)) flagselected = 1 ; + break ; + case SELTYPE_MINUS : + if (flagselected && !regexec(&sels[j].re, flagstamp ? s+TIMESTAMP+1 : s, 0, 0, 0)) flagselected = 0 ; + break ; + default : + strerr_dief2x(101, "internal consistency error in ", "selection type") ; + } + } + if (flagselected) + { + unsigned int actlen = genalloc_len(act_t, &se[i].actions) ; + act_t *acts = genalloc_s(act_t, &se[i].actions) ; + flagacted = 1 ; + for (j = 0 ; j < actlen ; j++) + { + switch (acts[j].type) + { + case ACTTYPE_FD2 : + doit_fd2(&acts[j].data.fd2, s, len) ; + break ; + case ACTTYPE_STATUS : + doit_status(&acts[j].data.status, s, len) ; + break ; + case ACTTYPE_DIR : + doit_dir(&acts[j].data.dir, s, len) ; + break ; + default : + strerr_dief2x(101, "internal consistency error in ", "action type") ; + } + } + } + } + if (flagstamp) tain_now_g() ; +} + +static inline void processor_died (logdir_t_ref ldp, int wstat) +{ + ldp->pid = 0 ; + if (WIFSIGNALED(wstat)) + { + if (verbosity) strerr_warnw2x("processor crashed in ", ldp->dir) ; + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + ldp->rstate = ROTSTATE_RUNPROCESSOR ; + } + else if (WEXITSTATUS(wstat)) + { + if (verbosity) strerr_warnw2x("processor failed in ", ldp->dir) ; + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + ldp->rstate = ROTSTATE_RUNPROCESSOR ; + } + else + { + ldp->rstate = ROTSTATE_SYNCPROCESSED ; + rotator(ldp) ; + } +} + +static void prepare_to_exit (void) +{ + fd_close(0) ; + flagexiting = 1 ; +} + +static void stampanddoit (scriptelem_t const *se, unsigned int n) +{ + if (flagstamp) indata.s[timestamp_g(indata.s)] = ' ' ; + indata.s[indata.len] = 0 ; + doit(se, n, indata.s, indata.len-1) ; + indata.len = flagstamp ? TIMESTAMP+1 : 0 ; +} + +static void normal_stdin (scriptelem_t const *se, unsigned int selen) +{ + int r = sanitize_read(buffer_fill(buffer_0)) ; + if (r < 0) + { + if ((errno != EPIPE) && verbosity) strerr_warnwu1sys("read from stdin") ; + prepare_to_exit() ; + } + else if (r) + while (skagetln_nofill(buffer_0, &indata, '\n') > 0) + stampanddoit(se, selen) ; +} + +static void last_stdin (scriptelem_t const *se, unsigned int selen) +{ + int cont = 1 ; + while (cont) + { + char c ; + switch (sanitize_read(fd_read(0, &c, 1))) + { + case 0 : + cont = 0 ; + break ; + case -1 : + if ((errno != EPIPE) && verbosity) strerr_warnwu1sys("read from stdin") ; + if (indata.len <= (flagstamp ? TIMESTAMP+1 : 0)) + { + prepare_to_exit() ; + cont = 0 ; + break ; + } + c = '\n' ; + case 1 : + if (!stralloc_catb(&indata, &c, 1)) dienomem() ; + if (c == '\n') + { + stampanddoit(se, selen) ; + prepare_to_exit() ; + cont = 0 ; + } + break ; + } + } +} + +static inputprocfunc_t_ref handle_stdin = &normal_stdin ; + +static inline void handle_signals (void) +{ + for (;;) + { + switch (selfpipe_read()) + { + case -1 : strerr_diefu1sys(111, "selfpipe_read") ; + case 0 : return ; + case SIGALRM : + { + unsigned int llen = genalloc_len(logdir_t, &logdirs) ; + logdir_t *ls = genalloc_s(logdir_t, &logdirs) ; + register unsigned int i = 0 ; + for (i = 0 ; i < llen ; i++) + if ((ls[i].rstate == ROTSTATE_WRITABLE) && ls[i].b) + { + ls[i].rstate = ROTSTATE_START ; + rotator(ls + i) ; + } + break ; + } + case SIGTERM : + { + if (flagprotect) break ; + handle_stdin = &last_stdin ; + if (indata.len <= (flagstamp ? TIMESTAMP+1 : 0)) prepare_to_exit() ; + break ; + } + case SIGCHLD : + { + unsigned int llen = genalloc_len(logdir_t, &logdirs) ; + logdir_t *ls = genalloc_s(logdir_t, &logdirs) ; + for (;;) + { + int wstat ; + register unsigned int i = 0 ; + register int r = wait_nohang(&wstat) ; + if (r <= 0) break ; + for (; i < llen ; i++) if ((unsigned int)r == ls[i].pid) break ; + if (i < llen) processor_died(ls + i, wstat) ; + } + break ; + } + default : strerr_dief1x(101, "internal consistency error with signal handling") ; + } + } +} + +static inline int logdir_finalize (logdir_t_ref ldp) +{ + switch (ldp->rstate) + { + case ROTSTATE_WRITABLE : + { + if (fd_sync(ldp->fd) < 0) + { + if (verbosity) strerr_warnwu3sys("fd_sync ", ldp->dir, "/current") ; + goto fail ; + } + tain_now_g() ; + ldp->rstate = ROTSTATE_ENDFCHMOD ; + } + case ROTSTATE_ENDFCHMOD : + { + if (fd_chmod(ldp->fd, S_IRWXU | S_IRGRP | S_IROTH) < 0) + { + if (verbosity) strerr_warnwu3sys("fd_chmod ", ldp->dir, "/current to 0744") ; + goto fail ; + } + ldp->rstate = ROTSTATE_END ; + break ; + } + default : strerr_dief1x(101, "inconsistent state in logdir_finalize()") ; + } + return 1 ; + fail: + tain_add_g(&ldp->deadline, &ldp->retrytto) ; + return 0 ; +} + +static inline void finalize (void) +{ + unsigned int llen = genalloc_len(logdir_t, &logdirs) ; + logdir_t *ls = genalloc_s(logdir_t, &logdirs) ; + unsigned int n = llen ; + for (;;) + { + unsigned int i = 0 ; + tain_t deadline ; + tain_addsec_g(&deadline, 2) ; + for (; i < llen ; i++) + if (ls[i].rstate != ROTSTATE_END) + { + if (logdir_finalize(ls + i)) n-- ; + else if (tain_less(&ls[i].deadline, &deadline)) + deadline = ls[i].deadline ; + } + if (!n) break ; + { + iopause_fd x ; + iopause_g(&x, 0, &deadline) ; + } + } +} + +int main (int argc, char const *const *argv) +{ + genalloc logscript = GENALLOC_ZERO ; /* array of scriptelem_t */ + int flagblock = 0 ; + PROG = "s6-log" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "qvbpte", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'q' : if (verbosity) verbosity-- ; break ; + case 'v' : verbosity++ ; break ; + case 'b' : flagblock = 1 ; break ; + case 'p' : flagprotect = 1 ; break ; + case 't' : flagstamp = 1 ; break ; + case 'e' : flagstampalert = 1 ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (argc < 1) strerr_dieusage(100, USAGE) ; + + fd_close(1) ; + { + int r = tain_now_g() ; + if (flagstamp) + { + char fmt[TIMESTAMP+1] ; + if (!stralloc_catb(&indata, fmt, TIMESTAMP+1)) dienomem() ; + if (!r) strerr_warnwu1sys("read current time - timestamps may be wrong for a while") ; + } + } + if (!script_init(&logscript, argv)) strerr_diefu1sys(111, "initialize logging script") ; + if (ndelay_on(0) < 0) strerr_diefu1sys(111, "ndelay_on(0)") ; + + { + unsigned int llen = genalloc_len(logdir_t, &logdirs) ; + logdir_t *ls = genalloc_s(logdir_t, &logdirs) ; + iopause_fd x[2 + llen] ; + unsigned int active[llen] ; + x[0].fd = 0 ; + x[1].fd = selfpipe_init() ; + if (x[1].fd < 0) strerr_diefu1sys(111, "selfpipe_init") ; + if (sig_ignore(SIGPIPE) < 0) strerr_diefu1sys(111, "sig_ignore(SIGPIPE)") ; + { + sigset_t set ; + sigemptyset(&set) ; + sigaddset(&set, SIGTERM) ; sigaddset(&set, SIGALRM) ; sigaddset(&set, SIGCHLD) ; + if (selfpipe_trapset(&set) < 0) strerr_diefu1sys(111, "selfpipe_trapset") ; + } + x[1].events = IOPAUSE_READ ; + + for (;;) + { + tain_t deadline ; + int r ; + unsigned int j = 0 ; + unsigned int i = 0 ; + int allflushed = 1 ; + tain_add_g(&deadline, &tain_infinite_relative) ; + for (; i < llen ; i++) + { + if (bufalloc_len(&ls[i].out) || (ls[i].rstate != ROTSTATE_WRITABLE)) + { + allflushed = 0 ; + if (!tain_future(&ls[i].deadline)) + { + x[2+j].fd = ls[i].fd ; + x[2+j].events = IOPAUSE_WRITE ; + active[j++] = i ; + } + else if (tain_less(&ls[i].deadline, &deadline)) + deadline = ls[i].deadline ; + } + } + if (flagexiting && allflushed) break ; + x[0].events = (allflushed || !flagblock) ? IOPAUSE_READ : 0 ; + r = iopause_g(x + flagexiting, 2 - flagexiting + j, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + else if (r) + { + if (x[1].revents & IOPAUSE_READ) handle_signals() ; + else if (x[1].revents & IOPAUSE_EXCEPT) strerr_dief1sys(111, "trouble with selfpipe") ; + for (i = 0 ; i < j ; i++) + if (x[2+i].revents & IOPAUSE_WRITE) + rotate_or_flush(ls + active[i]) ; + if (!flagexiting) + { + if (x[0].revents & IOPAUSE_READ) + (*handle_stdin)(genalloc_s(scriptelem_t, &logscript), genalloc_len(scriptelem_t, &logscript)) ; + else if (x[0].revents & IOPAUSE_EXCEPT) + { + prepare_to_exit() ; + if (indata.len > (flagstamp ? TIMESTAMP+1 : 0)) + { + if (!stralloc_0(&indata)) dienomem() ; + stampanddoit(genalloc_s(scriptelem_t, &logscript), genalloc_len(scriptelem_t, &logscript)) ; + } + } + } + } + } + } + genalloc_deepfree(scriptelem_t, &logscript, &scriptelem_free) ; + finalize() ; + return 0 ; +} diff --git a/src/daemontools-extras/s6-notifywhenup.c b/src/daemontools-extras/s6-notifywhenup.c new file mode 100644 index 0000000..a4be329 --- /dev/null +++ b/src/daemontools-extras/s6-notifywhenup.c @@ -0,0 +1,86 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USAGE "s6-notifywhenup [ -d fd ] [ -e fifodir ] [ -f ] [ -t timeout ] prog..." +#define dieusage() strerr_dieusage(100, USAGE) + +static int run_child (int fd, char const *fifodir, unsigned int timeout) +{ + char dummy[4096] ; + iopause_fd x = { .fd = fd, .events = IOPAUSE_READ } ; + tain_t deadline ; + int haswritten = 0 ; + register int r = 0 ; + if (!tain_now_g()) strerr_diefu1sys(111, "tain_now") ; + tain_from_millisecs(&deadline, timeout) ; + tain_add_g(&deadline, &deadline) ; + while (!r) + { + register int r = iopause_g(&x, 1, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + if (!r) return 99 ; + while (r > 0) + { + r = sanitize_read(fd_read(fd, dummy, 4096)) ; + if (r > 0) haswritten = 1 ; + } + } + if (errno != EPIPE) strerr_diefu1sys(111, "read from parent") ; + if (haswritten) ftrigw_notify(fifodir, 'U') ; + return 0 ; +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + unsigned int fd = 1 ; + char const *fifodir = "event" ; + int df = 1 ; + unsigned int timeout = 0 ; + PROG = "s6-notifywhenup" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "d:e:ft:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'd' : if (!uint0_scan(l.arg, &fd)) dieusage() ; break ; + case 'e' : fifodir = l.arg ; break ; + case 'f' : df = 0 ; break ; + case 't' : if (!uint0_scan(l.arg, &timeout)) dieusage() ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + if (!argc) dieusage() ; + + { + int p[2] ; + pid_t pid ; + if (pipe(p) < 0) strerr_diefu1sys(111, "pipe") ; + pid = df ? doublefork() : fork() ; + if (pid < 0) strerr_diefu1sys(111, df ? "doublefork" : "fork") ; + else if (pid) + { + PROG = "s6-notifywhenup (child)" ; + fd_close(p[1]) ; + return run_child(p[0], fifodir, timeout) ; + } + fd_close(p[0]) ; + if (fd_move((int)fd, p[1]) < 0) strerr_diefu1sys(111, "fd_move") ; + } + pathexec_run(argv[0], argv, envp) ; + strerr_dieexec(111, argv[1]) ; +} diff --git a/src/daemontools-extras/s6-setlock.c b/src/daemontools-extras/s6-setlock.c new file mode 100644 index 0000000..2fb6f12 --- /dev/null +++ b/src/daemontools-extras/s6-setlock.c @@ -0,0 +1,87 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USAGE "s6-setlock [ -r | -w ] [ -n | -N | -t timeout ] lockfile prog..." +#define dieusage() strerr_dieusage(100, USAGE) + +typedef int lockfunc_t (int) ; +typedef lockfunc_t *lockfunc_t_ref ; + +static lockfunc_t_ref f[2][2] = { { &lock_sh, &lock_shnb }, { &lock_ex, &lock_exnb } } ; + +int main (int argc, char const *const *argv, char const *const *envp) +{ + unsigned int nb = 0, ex = 1 ; + unsigned int timeout = 0 ; + PROG = "s6-setlock" ; + for (;;) + { + register int opt = subgetopt(argc, argv, "nNrwt:") ; + if (opt == -1) break ; + switch (opt) + { + case 'n' : nb = 1 ; break ; + case 'N' : nb = 0 ; break ; + case 'r' : ex = 0 ; break ; + case 'w' : ex = 1 ; break ; + case 't' : if (!uint0_scan(subgetopt_here.arg, &timeout)) dieusage() ; + nb = 2 ; break ; + default : dieusage() ; + } + } + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + if (argc < 2) dieusage() ; + + if (nb < 2) + { + int fd = open_create(argv[0]) ; + if (fd == -1) strerr_diefu2sys(111, "open_create ", argv[0]) ; + if ((*f[ex][nb])(fd) == -1) strerr_diefu2sys(1, "lock ", argv[0]) ; + } + else + { + char const *cargv[3] = { "s6lockd-helper", argv[0], 0 } ; + char const *cenvp[2] = { ex ? "S6LOCK_EX=1" : 0, 0 } ; + iopause_fd x = { .events = IOPAUSE_READ } ; + tain_t deadline ; + int p[2] ; + unsigned int pid ; + char c ; + if (!tain_now_g()) strerr_diefu1sys(111, "tain_now") ; + tain_from_millisecs(&deadline, timeout) ; + tain_add_g(&deadline, &deadline) ; + pid = child_spawn(S6_BINPREFIX "s6lockd-helper", cargv, cenvp, p, 2) ; + if (!pid) strerr_diefu2sys(111, "spawn ", S6_BINPREFIX "s6lockd-helper") ; + x.fd = p[0] ; + for (;;) + { + register int r = iopause_g(&x, 1, &deadline) ; + if (r < 0) strerr_diefu1sys(111, "iopause") ; + if (!r) + { + kill(pid, SIGTERM) ; + errno = ETIMEDOUT ; + strerr_diefu1sys(1, "acquire lock") ; + } + r = sanitize_read(fd_read(p[0], &c, 1)) ; + if (r < 0) strerr_diefu1sys(111, "read ack from helper") ; + if (r) break ; + } + if (c != '!') strerr_dief1x(111, "helper sent garbage ack") ; + fd_close(p[0]) ; + if (uncoe(p[1]) < 0) strerr_diefu1sys(111, "uncoe fd to helper") ; + } + pathexec_run(argv[1], argv+1, envp) ; + strerr_dieexec(111, argv[1]) ; +} diff --git a/src/daemontools-extras/s6-setsid.c b/src/daemontools-extras/s6-setsid.c new file mode 100644 index 0000000..efd3832 --- /dev/null +++ b/src/daemontools-extras/s6-setsid.c @@ -0,0 +1,35 @@ +/* ISC license. */ + +#include +#include +#include +#include + +#define USAGE "s6-setsid [ -i | -I ] prog..." + +int main (int argc, char const *const *argv, char const *const *envp) +{ + int insist = 0 ; + PROG = "s6-setsid" ; + for (;;) + { + register int opt = subgetopt(argc, argv, "iI") ; + if (opt == -1) break ; + switch (opt) + { + case 'i' : insist = 1 ; break ; + case 'I' : insist = 0 ; break ; + default : strerr_dieusage(100, USAGE) ; + } + } + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + if (!argc) strerr_dieusage(100, USAGE) ; + + if (setsid() < 0) + { + if (insist) strerr_diefu1sys(111, "setsid") ; + else strerr_warnwu1sys("setsid") ; + } + pathexec_run(argv[0], argv, envp) ; + strerr_dieexec(111, argv[0]) ; +} diff --git a/src/daemontools-extras/s6-setuidgid.c b/src/daemontools-extras/s6-setuidgid.c new file mode 100644 index 0000000..d2e7361 --- /dev/null +++ b/src/daemontools-extras/s6-setuidgid.c @@ -0,0 +1,30 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include + +#define USAGE "s6-setuidgid username prog..." +#define dieusage() strerr_dieusage(100, USAGE) + +int main (int argc, char const *const *argv, char const *const *envp) +{ + unsigned int pos ; + PROG = "s6-setuidgid" ; + if (argc < 3) dieusage() ; + pos = str_chr(argv[1], ':') ; + if (argv[1][pos]) + { + unsigned int uid = 0, gid = 0, len = uint_scan(argv[1], &uid) ; + if (len != pos) dieusage() ; + if (argv[1][pos+1] && !uint0_scan(argv[1]+pos+1, &gid)) dieusage() ; + if (gid && setgid(gid)) strerr_diefu1sys(111, "setgid") ; + if (uid && setuid(uid)) strerr_diefu1sys(111, "setuid") ; + } + else if (!prot_setuidgid(argv[1])) + strerr_diefu2sys(111, "change identity to ", argv[1]) ; + pathexec_run(argv[2], argv+2, envp) ; + strerr_dieexec(111, argv[2]) ; +} diff --git a/src/daemontools-extras/s6-softlimit.c b/src/daemontools-extras/s6-softlimit.c new file mode 100644 index 0000000..61e0e4d --- /dev/null +++ b/src/daemontools-extras/s6-softlimit.c @@ -0,0 +1,117 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#define USAGE "s6-softlimit [ -a allbytes ] [ -c corebytes ] [ -d databytes ] [ -f filebytes ] [ -l lockbytes ] [ -m membytes ] [ -o openfiles ] [ -p processes ] [ -r residentbytes ] [ -s stackbytes ] [ -t cpusecs ] prog..." + +static void doit (int res, char const *arg) +{ + struct rlimit r ; + if (getrlimit(res, &r) < 0) strerr_diefu1sys(111, "getrlimit") ; + if ((arg[0] == '=') && !arg[1]) r.rlim_cur = r.rlim_max ; + else + { + uint64 n ; + if (!uint640_scan(arg, &n)) strerr_dieusage(100, USAGE) ; + if (n > (uint64)r.rlim_max) n = (uint64)r.rlim_max ; + r.rlim_cur = (rlim_t)n ; + } + if (setrlimit(res, &r) == -1) strerr_diefu1sys(111, "setrlimit") ; +} + +int main (int argc, char const *const *argv, char const *const *envp) +{ + PROG = "s6-softlimit" ; + for (;;) + { + register int opt = sgetopt(argc, argv, "a:c:d:f:l:m:o:p:r:s:t:") ; + if (opt == -1) break ; + switch (opt) + { + case 'a' : +#ifdef RLIMIT_AS + doit(RLIMIT_AS, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_VMEM + doit(RLIMIT_VMEM, subgetopt_here.arg) ; +#endif + break ; + case 'c' : +#ifdef RLIMIT_CORE + doit(RLIMIT_CORE, subgetopt_here.arg) ; +#endif + break ; + case 'd' : +#ifdef RLIMIT_DATA + doit(RLIMIT_DATA, subgetopt_here.arg) ; +#endif + break ; + case 'f' : +#ifdef RLIMIT_FSIZE + doit(RLIMIT_FSIZE, subgetopt_here.arg) ; +#endif + break ; + case 'l' : +#ifdef RLIMIT_MEMLOCK + doit(RLIMIT_MEMLOCK, subgetopt_here.arg) ; +#endif + break ; + case 'm' : +#ifdef RLIMIT_DATA + doit(RLIMIT_DATA, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_STACK + doit(RLIMIT_STACK, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_MEMLOCK + doit(RLIMIT_MEMLOCK, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_VMEM + doit(RLIMIT_VMEM, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_AS + doit(RLIMIT_AS, subgetopt_here.arg) ; +#endif + break ; + case 'o' : +#ifdef RLIMIT_NOFILE + doit(RLIMIT_NOFILE, subgetopt_here.arg) ; +#endif +#ifdef RLIMIT_OFILE + doit(RLIMIT_OFILE, subgetopt_here.arg) ; +#endif + break ; + case 'p' : +#ifdef RLIMIT_NPROC + doit(RLIMIT_NPROC, subgetopt_here.arg) ; +#endif + break ; + case 'r' : +#ifdef RLIMIT_RSS + doit(RLIMIT_RSS, subgetopt_here.arg) ; +#endif + break ; + case 's' : +#ifdef RLIMIT_STACK + doit(RLIMIT_STACK, subgetopt_here.arg) ; +#endif + break ; + case 't' : +#ifdef RLIMIT_CPU + doit(RLIMIT_CPU, subgetopt_here.arg) ; +#endif + break ; + } + } + argc -= subgetopt_here.ind ; argv += subgetopt_here.ind ; + if (!argc) strerr_dieusage(100, USAGE) ; + pathexec_run(argv[0], argv, envp) ; + strerr_dieexec(111, argv[0]) ; +} diff --git a/src/daemontools-extras/s6-tai64n.c b/src/daemontools-extras/s6-tai64n.c new file mode 100644 index 0000000..085c053 --- /dev/null +++ b/src/daemontools-extras/s6-tai64n.c @@ -0,0 +1,35 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include + +int main (void) +{ + char stamp[TIMESTAMP+1] ; + PROG = "s6-tai64n" ; + stamp[TIMESTAMP] = ' ' ; + for (;;) + { + register int r = skagetln(buffer_0f1, &satmp, '\n') ; + if (r < 0) + if (errno != EPIPE) + strerr_diefu1sys(111, "read from stdin") ; + else + { + r = 1 ; + if (!stralloc_catb(&satmp, "\n", 1)) + strerr_diefu1sys(111, "add newline") ; + } + else if (!r) break ; + timestamp(stamp) ; + if ((buffer_put(buffer_1, stamp, TIMESTAMP+1) < 0) + || (buffer_put(buffer_1, satmp.s, satmp.len) < 0)) + strerr_diefu1sys(111, "write to stdout") ; + satmp.len = 0 ; + } + return 0 ; +} diff --git a/src/daemontools-extras/s6-tai64nlocal.c b/src/daemontools-extras/s6-tai64nlocal.c new file mode 100644 index 0000000..d7be880 --- /dev/null +++ b/src/daemontools-extras/s6-tai64nlocal.c @@ -0,0 +1,47 @@ +/* ISC license. */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main (void) +{ + PROG = "s6-tai64nlocal" ; + for (;;) + { + unsigned int p = 0 ; + int r = skagetln(buffer_0f1, &satmp, '\n') ; + if (r == -1) + if (errno != EPIPE) + strerr_diefu1sys(111, "read from stdin") ; + else r = 1 ; + else if (!r) break ; + if (satmp.len > TIMESTAMP) + { + tain_t a ; + p = timestamp_scan(satmp.s, &a) ; + if (p) + { + char fmt[LOCALTMN_FMT+1] ; + localtmn_t local ; + unsigned int len ; + localtmn_from_tain(&local, &a, 1) ; + len = localtmn_fmt(fmt, &local) ; + fmt[len++] = ' ' ; + if (buffer_put(buffer_1, fmt, len) < 0) + strerr_diefu1sys(111, "write to stdout") ; + } + } + if (buffer_put(buffer_1, satmp.s + p, satmp.len - p) < 0) + strerr_diefu1sys(111, "write to stdout") ; + satmp.len = 0 ; + } + return 0 ; +} diff --git a/src/daemontools-extras/ucspilogd.c b/src/daemontools-extras/ucspilogd.c new file mode 100644 index 0000000..ddb6362 --- /dev/null +++ b/src/daemontools-extras/ucspilogd.c @@ -0,0 +1,117 @@ +/* ISC license. */ + +#ifndef SYSLOG_NAMES +#define SYSLOG_NAMES +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define USAGE "ucspilogd [ -D default ] [ var... ]" +#define dieusage() strerr_dieusage(100, USAGE) + +static inline void die (void) +{ + strerr_diefu1sys(111, "write to stdout") ; +} + +static unsigned int syslog_names (char const *line) +{ + unsigned int fpr, i ; + int fp ; + CODE *p = facilitynames ; + + if (line[0] != '<') return 0 ; + i = uint_scan(line+1, &fpr) ; + if (!i || (line[i+1] != '>')) return 0 ; + i += 2 ; + + fp = LOG_FAC(fpr) << 3 ; + for (; p->c_name ; p++) if (p->c_val == fp) break ; + if (p->c_name) + { + if ((buffer_puts(buffer_1, p->c_name) < 0) + || (buffer_put(buffer_1, ".", 1) < 1)) die() ; + } + else + { + if (buffer_put(buffer_1, "unknown.", 8) < 8) die() ; + i = 0 ; + } + + fp = LOG_PRI(fpr) ; + for (p = prioritynames ; p->c_name ; p++) if (p->c_val == fp) break ; + if (p->c_name) + { + if ((buffer_puts(buffer_1, p->c_name) < 0) + || (buffer_put(buffer_1, ": ", 2) < 2)) die() ; + } + else + { + if (buffer_put(buffer_1, "unknown: ", 9) < 9) die() ; + i = 0 ; + } + return i ; +} + + +int main (int argc, char const *const *argv, char const *const *envp) +{ + char const *d = "" ; + PROG = "ucspilogd" ; + { + subgetopt_t l = SUBGETOPT_ZERO ; + for (;;) + { + register int opt = subgetopt_r(argc, argv, "D:", &l) ; + if (opt == -1) break ; + switch (opt) + { + case 'D' : d = l.arg ; break ; + default : dieusage() ; + } + } + argc -= l.ind ; argv += l.ind ; + } + + { + char const *envs[argc] ; + unsigned int i = 0 ; + for (; i < (unsigned int)argc ; i++) + { + envs[i] = env_get2(envp, argv[i]) ; + if (!envs[i]) envs[i] = d ; + } + for (;;) + { + unsigned int pos = 0 ; + satmp.len = 0 ; + { + register int r = skagetlnsep(buffer_0f1, &satmp, "\n", 2) ; + if (r < 0) strerr_diefu1sys(111, "read from stdin") ; + if (!r) break ; + } + if (!satmp.len) continue ; + satmp.s[satmp.len-1] = '\n' ; + if ((satmp.s[0] == '@') && (satmp.len > 26) && (byte_chr(satmp.s, 26, ' ') == 25)) + { + if (buffer_put(buffer_1, satmp.s, 26) < 26) die() ; + pos += 26 ; + } + for (i = 0 ; i < (unsigned int)argc ; i++) + if ((buffer_puts(buffer_1, envs[i]) < 0) + || (buffer_put(buffer_1, ": ", 2) < 2)) die() ; + pos += syslog_names(satmp.s + pos) ; + if (buffer_put(buffer_1, satmp.s + pos, satmp.len - pos) < (int)(satmp.len - pos)) die() ; + } + } + return 0 ; +} -- cgit v1.2.3