busybox/runit/svlogd.c
<<
>>
Prefs
   1/*
   2Copyright (c) 2001-2006, Gerrit Pape
   3All rights reserved.
   4
   5Redistribution and use in source and binary forms, with or without
   6modification, are permitted provided that the following conditions are met:
   7
   8   1. Redistributions of source code must retain the above copyright notice,
   9      this list of conditions and the following disclaimer.
  10   2. Redistributions in binary form must reproduce the above copyright
  11      notice, this list of conditions and the following disclaimer in the
  12      documentation and/or other materials provided with the distribution.
  13   3. The name of the author may not be used to endorse or promote products
  14      derived from this software without specific prior written permission.
  15
  16THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
  17WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  18MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  19EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  20SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  21PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  22OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  23WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  24OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  25ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26*/
  27
  28/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
  29
  30/*
  31Config files
  32
  33On startup, and after receiving a HUP signal, svlogd checks for each
  34log directory log if the configuration file log/config exists,
  35and if so, reads the file line by line and adjusts configuration
  36for log as follows:
  37
  38If the line is empty, or starts with a #, it is ignored. A line
  39of the form
  40
  41ssize
  42    sets the maximum file size of current when svlogd should rotate
  43    the current log file to size bytes. Default is 1000000.
  44    If size is zero, svlogd doesnt rotate log files
  45    You should set size to at least (2 * len).
  46nnum
  47    sets the number of old log files svlogd should maintain to num.
  48    If svlogd sees more that num old log files in log after log file
  49    rotation, it deletes the oldest one. Default is 10.
  50    If num is zero, svlogd doesnt remove old log files.
  51Nmin
  52    sets the minimum number of old log files svlogd should maintain
  53    to min. min must be less than num. If min is set, and svlogd
  54    cannot write to current because the filesystem is full,
  55    and it sees more than min old log files, it deletes the oldest one.
  56ttimeout
  57    sets the maximum age of the current log file when svlogd should
  58    rotate the current log file to timeout seconds. If current
  59    is timeout seconds old, and is not empty, svlogd forces log file rotation.
  60!processor
  61    tells svlogd to feed each recent log file through processor
  62    (see above) on log file rotation. By default log files are not processed.
  63ua.b.c.d[:port]
  64    tells svlogd to transmit the first len characters of selected
  65    log messages to the IP address a.b.c.d, port number port.
  66    If port isnt set, the default port for syslog is used (514).
  67    len can be set through the -l option, see below. If svlogd
  68    has trouble sending udp packets, it writes error messages
  69    to the log directory. Attention: logging through udp is unreliable,
  70    and should be used in private networks only.
  71Ua.b.c.d[:port]
  72    is the same as the u line above, but the log messages are no longer
  73    written to the log directory, but transmitted through udp only.
  74    Error messages from svlogd concerning sending udp packages still go
  75    to the log directory.
  76pprefix
  77    tells svlogd to prefix each line to be written to the log directory,
  78    to standard error, or through UDP, with prefix.
  79
  80If a line starts with a -, +, e, or E, svlogd matches the first len characters
  81of each log message against pattern and acts accordingly:
  82
  83-pattern
  84    the log message is deselected.
  85+pattern
  86    the log message is selected.
  87epattern
  88    the log message is selected to be printed to standard error.
  89Epattern
  90    the log message is deselected to be printed to standard error.
  91
  92Initially each line is selected to be written to log/current. Deselected
  93log messages are discarded from log. Initially each line is deselected
  94to be written to standard err. Log messages selected for standard error
  95are written to standard error.
  96
  97Pattern Matching
  98
  99svlogd matches a log message against the string pattern as follows:
 100
 101pattern is applied to the log message one character by one, starting
 102with the first. A character not a star (*) and not a plus (+) matches itself.
 103A plus matches the next character in pattern in the log message one
 104or more times. A star before the end of pattern matches any string
 105in the log message that does not include the next character in pattern.
 106A star at the end of pattern matches any string.
 107
 108Timestamps optionally added by svlogd are not considered part
 109of the log message.
 110
 111An svlogd pattern is not a regular expression. For example consider
 112a log message like this
 113
 1142005-12-18_09:13:50.97618 tcpsvd: info: pid 1977 from 10.4.1.14
 115
 116The following pattern doesnt match
 117
 118-*pid*
 119
 120because the first star matches up to the first p in tcpsvd,
 121and then the match fails because i is not s. To match this
 122log message, you can use a pattern like this instead
 123
 124-*: *: pid *
 125*/
 126//config:config SVLOGD
 127//config:       bool "svlogd (16 kb)"
 128//config:       default y
 129//config:       help
 130//config:       svlogd continuously reads log data from its standard input, optionally
 131//config:       filters log messages, and writes the data to one or more automatically
 132//config:       rotated logs.
 133
 134//applet:IF_SVLOGD(APPLET(svlogd, BB_DIR_USR_SBIN, BB_SUID_DROP))
 135
 136//kbuild:lib-$(CONFIG_SVLOGD) += svlogd.o
 137
 138//usage:#define svlogd_trivial_usage
 139//usage:       "[-tttv] [-r C] [-R CHARS] [-l MATCHLEN] [-b BUFLEN] DIR..."
 140//usage:#define svlogd_full_usage "\n\n"
 141//usage:       "Read log data from stdin and write to rotated log files in DIRs"
 142//usage:   "\n"
 143//usage:   "\n""-r C            Replace non-printable characters with C"
 144//usage:   "\n""-R CHARS        Also replace CHARS with C (default _)"
 145//usage:   "\n""-t              Timestamp with @tai64n"
 146//usage:   "\n""-tt             Timestamp with yyyy-mm-dd_hh:mm:ss.sssss"
 147//usage:   "\n""-ttt            Timestamp with yyyy-mm-ddThh:mm:ss.sssss"
 148//usage:   "\n""-v              Verbose"
 149//usage:   "\n"
 150//usage:   "\n""DIR/config file modifies behavior:"
 151//usage:   "\n""sSIZE - when to rotate logs (default 1000000, 0 disables)"
 152//usage:   "\n""nNUM - number of files to retain"
 153///////:   "\n""NNUM - min number files to retain" - confusing
 154///////:   "\n""tSEC - rotate file if it get SEC seconds old" - confusing
 155//usage:   "\n""!PROG - process rotated log with PROG"
 156///////:   "\n""uIPADDR - send log over UDP" - unsupported
 157///////:   "\n""UIPADDR - send log over UDP and DONT log" - unsupported
 158///////:   "\n""pPFX - prefix each line with PFX" - unsupported
 159//usage:   "\n""+,-PATTERN - (de)select line for logging"
 160//usage:   "\n""E,ePATTERN - (de)select line for stderr"
 161
 162#include <sys/file.h>
 163#include "libbb.h"
 164#include "common_bufsiz.h"
 165#include "runit_lib.h"
 166
 167#define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
 168
 169#define FMT_PTIME 30
 170
 171struct logdir {
 172        ////char *btmp;
 173        /* pattern list to match, in "aa\0bb\0\cc\0\0" form */
 174        char *inst;
 175        char *processor;
 176        char *name;
 177        unsigned size;
 178        unsigned sizemax;
 179        unsigned nmax;
 180        unsigned nmin;
 181        unsigned rotate_period;
 182        int ppid;
 183        int fddir;
 184        int fdcur;
 185        FILE* filecur; ////
 186        int fdlock;
 187        unsigned next_rotate;
 188        char fnsave[FMT_PTIME];
 189        char match;
 190        char matcherr;
 191};
 192
 193
 194struct globals {
 195        struct logdir *dir;
 196        unsigned verbose;
 197        int linemax;
 198        ////int buflen;
 199        int linelen;
 200
 201        int fdwdir;
 202        char **fndir;
 203        int wstat;
 204        unsigned nearest_rotate;
 205
 206        void* (*memRchr)(const void *, int, size_t);
 207        char *shell;
 208
 209        smallint exitasap;
 210        smallint rotateasap;
 211        smallint reopenasap;
 212        smallint linecomplete;
 213        smallint tmaxflag;
 214
 215        char repl;
 216        const char *replace;
 217        int fl_flag_0;
 218        unsigned dirn;
 219
 220        sigset_t blocked_sigset;
 221};
 222#define G (*ptr_to_globals)
 223#define dir            (G.dir           )
 224#define verbose        (G.verbose       )
 225#define linemax        (G.linemax       )
 226#define buflen         (G.buflen        )
 227#define linelen        (G.linelen       )
 228#define fndir          (G.fndir         )
 229#define fdwdir         (G.fdwdir        )
 230#define wstat          (G.wstat         )
 231#define memRchr        (G.memRchr       )
 232#define nearest_rotate (G.nearest_rotate)
 233#define exitasap       (G.exitasap      )
 234#define rotateasap     (G.rotateasap    )
 235#define reopenasap     (G.reopenasap    )
 236#define linecomplete   (G.linecomplete  )
 237#define tmaxflag       (G.tmaxflag      )
 238#define repl           (G.repl          )
 239#define replace        (G.replace       )
 240#define blocked_sigset (G.blocked_sigset)
 241#define fl_flag_0      (G.fl_flag_0     )
 242#define dirn           (G.dirn          )
 243#define line bb_common_bufsiz1
 244#define INIT_G() do { \
 245        setup_common_bufsiz(); \
 246        SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
 247        linemax = 1000; \
 248        /*buflen = 1024;*/ \
 249        linecomplete = 1; \
 250        replace = ""; \
 251} while (0)
 252
 253
 254#define FATAL "fatal: "
 255#define WARNING "warning: "
 256#define PAUSE "pausing: "
 257#define INFO "info: "
 258
 259static void fatalx(const char *m0)
 260{
 261        bb_error_msg_and_die(FATAL"%s", m0);
 262}
 263static void warn(const char *m0)
 264{
 265        bb_perror_msg(WARNING"%s", m0);
 266}
 267static void warn2(const char *m0, const char *m1)
 268{
 269        bb_perror_msg(WARNING"%s: %s", m0, m1);
 270}
 271static void warnx(const char *m0, const char *m1)
 272{
 273        bb_error_msg(WARNING"%s: %s", m0, m1);
 274}
 275static void pause_nomem(void)
 276{
 277        bb_error_msg(PAUSE"out of memory");
 278        sleep(3);
 279}
 280static void pause1cannot(const char *m0)
 281{
 282        bb_perror_msg(PAUSE"can't %s", m0);
 283        sleep(3);
 284}
 285static void pause2cannot(const char *m0, const char *m1)
 286{
 287        bb_perror_msg(PAUSE"can't %s %s", m0, m1);
 288        sleep(3);
 289}
 290
 291static char* wstrdup(const char *str)
 292{
 293        char *s;
 294        while (!(s = strdup(str)))
 295                pause_nomem();
 296        return s;
 297}
 298
 299static unsigned pmatch(const char *p, const char *s, unsigned len)
 300{
 301        for (;;) {
 302                char c = *p++;
 303                if (!c) return !len;
 304                switch (c) {
 305                case '*':
 306                        c = *p;
 307                        if (!c) return 1;
 308                        for (;;) {
 309                                if (!len) return 0;
 310                                if (*s == c) break;
 311                                ++s;
 312                                --len;
 313                        }
 314                        continue;
 315                case '+':
 316                        c = *p++;
 317                        if (c != *s) return 0;
 318                        for (;;) {
 319                                if (!len) return 1;
 320                                if (*s != c) break;
 321                                ++s;
 322                                --len;
 323                        }
 324                        continue;
 325                        /*
 326                case '?':
 327                        if (*p == '?') {
 328                                if (*s != '?') return 0;
 329                                ++p;
 330                        }
 331                        ++s; --len;
 332                        continue;
 333                        */
 334                default:
 335                        if (!len) return 0;
 336                        if (*s != c) return 0;
 337                        ++s;
 338                        --len;
 339                        continue;
 340                }
 341        }
 342        return 0;
 343}
 344
 345/*** ex fmt_ptime.[ch] ***/
 346
 347/* NUL terminated */
 348static void fmt_time_human_30nul(char *s, char dt_delim)
 349{
 350        struct tm tm;
 351        struct tm *ptm;
 352        struct timeval tv;
 353
 354        gettimeofday(&tv, NULL);
 355        ptm = gmtime_r(&tv.tv_sec, &tm);
 356        /* ^^^ using gmtime_r() instead of gmtime() to not use static data */
 357        sprintf(s, "%04u-%02u-%02u%c%02u:%02u:%02u.%06u000",
 358                (unsigned)(1900 + ptm->tm_year),
 359                (unsigned)(ptm->tm_mon + 1),
 360                (unsigned)(ptm->tm_mday),
 361                dt_delim,
 362                (unsigned)(ptm->tm_hour),
 363                (unsigned)(ptm->tm_min),
 364                (unsigned)(ptm->tm_sec),
 365                (unsigned)(tv.tv_usec)
 366        );
 367        /* 4+1 + 2+1 + 2+1 + 2+1 + 2+1 + 2+1 + 9 = */
 368        /* 5   + 3   + 3   + 3   + 3   + 3   + 9 = */
 369        /* 20 (up to '.' inclusive) + 9 (not including '\0') */
 370}
 371
 372/* NOT terminated! */
 373static void fmt_time_bernstein_25(char *s)
 374{
 375        uint32_t pack[3];
 376        struct timeval tv;
 377        unsigned sec_hi;
 378
 379        gettimeofday(&tv, NULL);
 380        sec_hi = (0x400000000000000aULL + tv.tv_sec) >> 32;
 381        tv.tv_sec = (time_t)(0x400000000000000aULL) + tv.tv_sec;
 382        tv.tv_usec *= 1000;
 383        /* Network order is big-endian: most significant byte first.
 384         * This is exactly what we want here */
 385        pack[0] = htonl(sec_hi);
 386        pack[1] = htonl(tv.tv_sec);
 387        pack[2] = htonl(tv.tv_usec);
 388        *s++ = '@';
 389        bin2hex(s, (char*)pack, 12);
 390}
 391
 392static void processorstart(struct logdir *ld)
 393{
 394        char sv_ch;
 395        int pid;
 396
 397        if (!ld->processor) return;
 398        if (ld->ppid) {
 399                warnx("processor already running", ld->name);
 400                return;
 401        }
 402
 403        /* vfork'ed child trashes this byte, save... */
 404        sv_ch = ld->fnsave[26];
 405
 406        if (!G.shell)
 407                G.shell = xstrdup(get_shell_name());
 408
 409        while ((pid = vfork()) == -1)
 410                pause2cannot("vfork for processor", ld->name);
 411        if (!pid) {
 412                int fd;
 413
 414                /* child */
 415                /* Non-ignored signals revert to SIG_DFL on exec anyway */
 416                /*bb_signals(0
 417                        + (1 << SIGTERM)
 418                        + (1 << SIGALRM)
 419                        + (1 << SIGHUP)
 420                        , SIG_DFL);*/
 421                sig_unblock(SIGTERM);
 422                sig_unblock(SIGALRM);
 423                sig_unblock(SIGHUP);
 424
 425                if (verbose)
 426                        bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
 427                fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
 428                xmove_fd(fd, 0);
 429                ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
 430                fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
 431                xmove_fd(fd, 1);
 432                fd = open("state", O_RDONLY|O_NDELAY);
 433                if (fd == -1) {
 434                        if (errno != ENOENT)
 435                                bb_perror_msg_and_die(FATAL"can't %s processor %s", "open state for", ld->name);
 436                        close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
 437                        fd = xopen("state", O_RDONLY|O_NDELAY);
 438                }
 439                xmove_fd(fd, 4);
 440                fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
 441                xmove_fd(fd, 5);
 442
 443                execl(G.shell, G.shell, "-c", ld->processor, (char*) NULL);
 444                bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name);
 445        }
 446        ld->fnsave[26] = sv_ch; /* ...restore */
 447        ld->ppid = pid;
 448}
 449
 450static unsigned processorstop(struct logdir *ld)
 451{
 452        char f[28];
 453
 454        if (ld->ppid) {
 455                sig_unblock(SIGHUP);
 456                while (safe_waitpid(ld->ppid, &wstat, 0) == -1)
 457                        pause2cannot("wait for processor", ld->name);
 458                sig_block(SIGHUP);
 459                ld->ppid = 0;
 460        }
 461        if (ld->fddir == -1)
 462                return 1;
 463        while (fchdir(ld->fddir) == -1)
 464                pause2cannot("change directory, want processor", ld->name);
 465        if (WEXITSTATUS(wstat) != 0) {
 466                warnx("processor failed, restart", ld->name);
 467                ld->fnsave[26] = 't';
 468                unlink(ld->fnsave);
 469                ld->fnsave[26] = 'u';
 470                processorstart(ld);
 471                while (fchdir(fdwdir) == -1)
 472                        pause1cannot("change to initial working directory");
 473                return ld->processor ? 0 : 1;
 474        }
 475        ld->fnsave[26] = 't';
 476        memcpy(f, ld->fnsave, 26);
 477        f[26] = 's';
 478        f[27] = '\0';
 479        while (rename(ld->fnsave, f) == -1)
 480                pause2cannot("rename processed", ld->name);
 481        while (chmod(f, 0744) == -1)
 482                pause2cannot("set mode of processed", ld->name);
 483        ld->fnsave[26] = 'u';
 484        if (unlink(ld->fnsave) == -1)
 485                bb_error_msg(WARNING"can't unlink: %s/%s", ld->name, ld->fnsave);
 486        while (rename("newstate", "state") == -1)
 487                pause2cannot("rename state", ld->name);
 488        if (verbose)
 489                bb_error_msg(INFO"processed: %s/%s", ld->name, f);
 490        while (fchdir(fdwdir) == -1)
 491                pause1cannot("change to initial working directory");
 492        return 1;
 493}
 494
 495static void rmoldest(struct logdir *ld)
 496{
 497        DIR *d;
 498        struct dirent *f;
 499        char oldest[FMT_PTIME];
 500        int n = 0;
 501
 502        oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
 503        while (!(d = opendir(".")))
 504                pause2cannot("open directory, want rotate", ld->name);
 505        errno = 0;
 506        while ((f = readdir(d))) {
 507                if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
 508                        if (f->d_name[26] == 't') {
 509                                if (unlink(f->d_name) == -1)
 510                                        warn2("can't unlink processor leftover", f->d_name);
 511                        } else {
 512                                ++n;
 513                                if (strcmp(f->d_name, oldest) < 0)
 514                                        memcpy(oldest, f->d_name, 27);
 515                        }
 516                        errno = 0;
 517                }
 518        }
 519        if (errno)
 520                warn2("can't read directory", ld->name);
 521        closedir(d);
 522
 523        if (ld->nmax && (n > ld->nmax)) {
 524                if (verbose)
 525                        bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
 526                if ((*oldest == '@') && (unlink(oldest) == -1))
 527                        warn2("can't unlink oldest logfile", ld->name);
 528        }
 529}
 530
 531static unsigned rotate(struct logdir *ld)
 532{
 533        struct stat st;
 534        unsigned now;
 535
 536        if (ld->fddir == -1) {
 537                ld->rotate_period = 0;
 538                return 0;
 539        }
 540        if (ld->ppid)
 541                while (!processorstop(ld))
 542                        continue;
 543
 544        while (fchdir(ld->fddir) == -1)
 545                pause2cannot("change directory, want rotate", ld->name);
 546
 547        /* create new filename */
 548        ld->fnsave[25] = '.';
 549        ld->fnsave[26] = 's';
 550        if (ld->processor)
 551                ld->fnsave[26] = 'u';
 552        ld->fnsave[27] = '\0';
 553        do {
 554                fmt_time_bernstein_25(ld->fnsave);
 555                errno = 0;
 556                stat(ld->fnsave, &st);
 557        } while (errno != ENOENT);
 558
 559        now = monotonic_sec();
 560        if (ld->rotate_period && LESS(ld->next_rotate, now)) {
 561                ld->next_rotate = now + ld->rotate_period;
 562                if (LESS(ld->next_rotate, nearest_rotate))
 563                        nearest_rotate = ld->next_rotate;
 564        }
 565
 566        if (ld->size > 0) {
 567                while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
 568                        pause2cannot("fsync current logfile", ld->name);
 569                while (fchmod(ld->fdcur, 0744) == -1)
 570                        pause2cannot("set mode of current", ld->name);
 571                ////close(ld->fdcur);
 572                fclose(ld->filecur);
 573
 574                if (verbose) {
 575                        bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
 576                                        ld->fnsave, ld->size);
 577                }
 578                while (rename("current", ld->fnsave) == -1)
 579                        pause2cannot("rename current", ld->name);
 580                while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
 581                        pause2cannot("create new current", ld->name);
 582                while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL) ////
 583                        pause2cannot("create new current", ld->name); /* very unlikely */
 584                setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
 585                close_on_exec_on(ld->fdcur);
 586                ld->size = 0;
 587                while (fchmod(ld->fdcur, 0644) == -1)
 588                        pause2cannot("set mode of current", ld->name);
 589
 590                rmoldest(ld);
 591                processorstart(ld);
 592        }
 593
 594        while (fchdir(fdwdir) == -1)
 595                pause1cannot("change to initial working directory");
 596        return 1;
 597}
 598
 599static int buffer_pwrite(int n, char *s, unsigned len)
 600{
 601        int i;
 602        struct logdir *ld = &dir[n];
 603
 604        if (ld->sizemax) {
 605                if (ld->size >= ld->sizemax)
 606                        rotate(ld);
 607                if (len > (ld->sizemax - ld->size))
 608                        len = ld->sizemax - ld->size;
 609        }
 610        while (1) {
 611                ////i = full_write(ld->fdcur, s, len);
 612                ////if (i != -1) break;
 613                i = fwrite(s, 1, len, ld->filecur);
 614                if (i == len) break;
 615
 616                if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
 617                        DIR *d;
 618                        struct dirent *f;
 619                        char oldest[FMT_PTIME];
 620                        int j = 0;
 621
 622                        while (fchdir(ld->fddir) == -1)
 623                                pause2cannot("change directory, want remove old logfile",
 624                                                        ld->name);
 625                        oldest[0] = 'A';
 626                        oldest[1] = oldest[27] = '\0';
 627                        while (!(d = opendir(".")))
 628                                pause2cannot("open directory, want remove old logfile",
 629                                                        ld->name);
 630                        errno = 0;
 631                        while ((f = readdir(d)))
 632                                if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
 633                                        ++j;
 634                                        if (strcmp(f->d_name, oldest) < 0)
 635                                                memcpy(oldest, f->d_name, 27);
 636                                }
 637                        if (errno) warn2("can't read directory, want remove old logfile",
 638                                        ld->name);
 639                        closedir(d);
 640                        errno = ENOSPC;
 641                        if (j > ld->nmin) {
 642                                if (*oldest == '@') {
 643                                        bb_error_msg(WARNING"out of disk space, delete: %s/%s",
 644                                                        ld->name, oldest);
 645                                        errno = 0;
 646                                        if (unlink(oldest) == -1) {
 647                                                warn2("can't unlink oldest logfile", ld->name);
 648                                                errno = ENOSPC;
 649                                        }
 650                                        while (fchdir(fdwdir) == -1)
 651                                                pause1cannot("change to initial working directory");
 652                                }
 653                        }
 654                }
 655                if (errno)
 656                        pause2cannot("write to current", ld->name);
 657        }
 658
 659        ld->size += i;
 660        if (ld->sizemax)
 661                if (s[i-1] == '\n')
 662                        if (ld->size >= (ld->sizemax - linemax))
 663                                rotate(ld);
 664        return i;
 665}
 666
 667static void logdir_close(struct logdir *ld)
 668{
 669        if (ld->fddir == -1)
 670                return;
 671        if (verbose)
 672                bb_error_msg(INFO"close: %s", ld->name);
 673        close(ld->fddir);
 674        ld->fddir = -1;
 675        if (ld->fdcur == -1)
 676                return; /* impossible */
 677        while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
 678                pause2cannot("fsync current logfile", ld->name);
 679        while (fchmod(ld->fdcur, 0744) == -1)
 680                pause2cannot("set mode of current", ld->name);
 681        ////close(ld->fdcur);
 682        fclose(ld->filecur);
 683        ld->fdcur = -1;
 684        if (ld->fdlock == -1)
 685                return; /* impossible */
 686        close(ld->fdlock);
 687        ld->fdlock = -1;
 688        free(ld->processor);
 689        ld->processor = NULL;
 690}
 691
 692static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
 693{
 694        char buf[128];
 695        unsigned now;
 696        char *new, *s, *np;
 697        int i;
 698        struct stat st;
 699
 700        now = monotonic_sec();
 701
 702        ld->fddir = open(fn, O_RDONLY|O_NDELAY);
 703        if (ld->fddir == -1) {
 704                warn2("can't open log directory", (char*)fn);
 705                return 0;
 706        }
 707        close_on_exec_on(ld->fddir);
 708        if (fchdir(ld->fddir) == -1) {
 709                logdir_close(ld);
 710                warn2("can't change directory", (char*)fn);
 711                return 0;
 712        }
 713        ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
 714        if ((ld->fdlock == -1)
 715         || (flock(ld->fdlock, LOCK_EX | LOCK_NB) == -1)
 716        ) {
 717                logdir_close(ld);
 718                warn2("can't lock directory", (char*)fn);
 719                while (fchdir(fdwdir) == -1)
 720                        pause1cannot("change to initial working directory");
 721                return 0;
 722        }
 723        close_on_exec_on(ld->fdlock);
 724
 725        ld->size = 0;
 726        ld->sizemax = 1000000;
 727        ld->nmax = ld->nmin = 10;
 728        ld->rotate_period = 0;
 729        ld->name = (char*)fn;
 730        ld->ppid = 0;
 731        ld->match = '+';
 732        free(ld->inst); ld->inst = NULL;
 733        free(ld->processor); ld->processor = NULL;
 734
 735        /* read config */
 736        i = open_read_close("config", buf, sizeof(buf) - 1);
 737        if (i < 0 && errno != ENOENT)
 738                bb_perror_msg(WARNING"%s/config", ld->name);
 739        if (i > 0) {
 740                buf[i] = '\0';
 741                if (verbose)
 742                        bb_error_msg(INFO"read: %s/config", ld->name);
 743                s = buf;
 744                while (s) {
 745                        np = strchr(s, '\n');
 746                        if (np)
 747                                *np++ = '\0';
 748                        switch (s[0]) {
 749                        case '+':
 750                        case '-':
 751                        case 'e':
 752                        case 'E':
 753                                /* Filtering requires one-line buffering,
 754                                 * resetting the "find newline" function
 755                                 * accordingly */
 756                                memRchr = memchr;
 757                                /* Add '\n'-terminated line to ld->inst */
 758                                while (1) {
 759                                        int l = asprintf(&new, "%s%s\n", ld->inst ? ld->inst : "", s);
 760                                        if (l >= 0 && new)
 761                                                break;
 762                                        pause_nomem();
 763                                }
 764                                free(ld->inst);
 765                                ld->inst = new;
 766                                break;
 767                        case 's': {
 768                                ld->sizemax = xatou_sfx(&s[1], km_suffixes);
 769                                break;
 770                        }
 771                        case 'n':
 772                                ld->nmax = xatoi_positive(&s[1]);
 773                                break;
 774                        case 'N':
 775                                ld->nmin = xatoi_positive(&s[1]);
 776                                break;
 777                        case 't': {
 778                                static const struct suffix_mult mh_suffixes[] = {
 779                                        { "m", 60 },
 780                                        { "h", 60*60 },
 781                                        /*{ "d", 24*60*60 },*/
 782                                        { "", 0 }
 783                                };
 784                                ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
 785                                if (ld->rotate_period) {
 786                                        ld->next_rotate = now + ld->rotate_period;
 787                                        if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
 788                                                nearest_rotate = ld->next_rotate;
 789                                        tmaxflag = 1;
 790                                }
 791                                break;
 792                        }
 793                        case '!':
 794                                if (s[1]) {
 795                                        free(ld->processor);
 796                                        ld->processor = wstrdup(&s[1]);
 797                                }
 798                                break;
 799                        }
 800                        s = np;
 801                }
 802                /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
 803                s = ld->inst;
 804                while (s) {
 805                        np = strchr(s, '\n');
 806                        if (np)
 807                                *np++ = '\0';
 808                        s = np;
 809                }
 810        }
 811
 812        /* open current */
 813        i = stat("current", &st);
 814        if (i != -1) {
 815                if (st.st_size && !(st.st_mode & S_IXUSR)) {
 816                        ld->fnsave[25] = '.';
 817                        ld->fnsave[26] = 'u';
 818                        ld->fnsave[27] = '\0';
 819                        do {
 820                                fmt_time_bernstein_25(ld->fnsave);
 821                                errno = 0;
 822                                stat(ld->fnsave, &st);
 823                        } while (errno != ENOENT);
 824                        while (rename("current", ld->fnsave) == -1)
 825                                pause2cannot("rename current", ld->name);
 826                        rmoldest(ld);
 827                        i = -1;
 828                } else {
 829                        /* st.st_size can be not just bigger, but WIDER!
 830                         * This code is safe: if st.st_size > 4GB, we select
 831                         * ld->sizemax (because it's "unsigned") */
 832                        ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
 833                }
 834        } else {
 835                if (errno != ENOENT) {
 836                        logdir_close(ld);
 837                        warn2("can't stat current", ld->name);
 838                        while (fchdir(fdwdir) == -1)
 839                                pause1cannot("change to initial working directory");
 840                        return 0;
 841                }
 842        }
 843        while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
 844                pause2cannot("open current", ld->name);
 845        while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL)
 846                pause2cannot("open current", ld->name); ////
 847        setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
 848
 849        close_on_exec_on(ld->fdcur);
 850        while (fchmod(ld->fdcur, 0644) == -1)
 851                pause2cannot("set mode of current", ld->name);
 852
 853        if (verbose) {
 854                if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
 855                else bb_error_msg(INFO"new: %s/current", ld->name);
 856        }
 857
 858        while (fchdir(fdwdir) == -1)
 859                pause1cannot("change to initial working directory");
 860        return 1;
 861}
 862
 863static void logdirs_reopen(void)
 864{
 865        int l;
 866        int ok = 0;
 867
 868        tmaxflag = 0;
 869        for (l = 0; l < dirn; ++l) {
 870                logdir_close(&dir[l]);
 871                if (logdir_open(&dir[l], fndir[l]))
 872                        ok = 1;
 873        }
 874        if (!ok)
 875                fatalx("no functional log directories");
 876}
 877
 878/* Will look good in libbb one day */
 879static ssize_t ndelay_read(int fd, void *buf, size_t count)
 880{
 881        if (!(fl_flag_0 & O_NONBLOCK))
 882                fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
 883        count = safe_read(fd, buf, count);
 884        if (!(fl_flag_0 & O_NONBLOCK))
 885                fcntl(fd, F_SETFL, fl_flag_0);
 886        return count;
 887}
 888
 889/* Used for reading stdin */
 890static int buffer_pread(/*int fd, */char *s, unsigned len)
 891{
 892        unsigned now;
 893        struct pollfd input;
 894        int i;
 895
 896        input.fd = STDIN_FILENO;
 897        input.events = POLLIN;
 898
 899        do {
 900                if (rotateasap) {
 901                        for (i = 0; i < dirn; ++i)
 902                                rotate(dir + i);
 903                        rotateasap = 0;
 904                }
 905                if (exitasap) {
 906                        if (linecomplete)
 907                                return 0;
 908                        len = 1;
 909                }
 910                if (reopenasap) {
 911                        logdirs_reopen();
 912                        reopenasap = 0;
 913                }
 914                now = monotonic_sec();
 915                nearest_rotate = now + (45 * 60 + 45);
 916                for (i = 0; i < dirn; ++i) {
 917                        if (dir[i].rotate_period) {
 918                                if (LESS(dir[i].next_rotate, now))
 919                                        rotate(dir + i);
 920                                if (LESS(dir[i].next_rotate, nearest_rotate))
 921                                        nearest_rotate = dir[i].next_rotate;
 922                        }
 923                }
 924
 925                sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
 926                i = nearest_rotate - now;
 927                if (i > 1000000)
 928                        i = 1000000;
 929                if (i <= 0)
 930                        i = 1;
 931                poll(&input, 1, i * 1000);
 932                sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
 933
 934                i = ndelay_read(STDIN_FILENO, s, len);
 935                if (i >= 0)
 936                        break;
 937                if (errno == EINTR)
 938                        continue;
 939                if (errno != EAGAIN) {
 940                        warn("can't read standard input");
 941                        break;
 942                }
 943                /* else: EAGAIN - normal, repeat silently */
 944        } while (!exitasap);
 945
 946        if (i > 0) {
 947                int cnt;
 948                linecomplete = (s[i-1] == '\n');
 949                if (!repl)
 950                        return i;
 951
 952                cnt = i;
 953                while (--cnt >= 0) {
 954                        char ch = *s;
 955                        if (ch != '\n') {
 956                                if (ch < 32 || ch > 126)
 957                                        *s = repl;
 958                                else {
 959                                        int j;
 960                                        for (j = 0; replace[j]; ++j) {
 961                                                if (ch == replace[j]) {
 962                                                        *s = repl;
 963                                                        break;
 964                                                }
 965                                        }
 966                                }
 967                        }
 968                        s++;
 969                }
 970        }
 971        return i;
 972}
 973
 974static void sig_term_handler(int sig_no UNUSED_PARAM)
 975{
 976        if (verbose)
 977                bb_error_msg(INFO"sig%s received", "term");
 978        exitasap = 1;
 979}
 980
 981static void sig_child_handler(int sig_no UNUSED_PARAM)
 982{
 983        pid_t pid;
 984        int l;
 985
 986        if (verbose)
 987                bb_error_msg(INFO"sig%s received", "child");
 988        while ((pid = wait_any_nohang(&wstat)) > 0) {
 989                for (l = 0; l < dirn; ++l) {
 990                        if (dir[l].ppid == pid) {
 991                                dir[l].ppid = 0;
 992                                processorstop(&dir[l]);
 993                                break;
 994                        }
 995                }
 996        }
 997}
 998
 999static void sig_alarm_handler(int sig_no UNUSED_PARAM)
1000{
1001        if (verbose)
1002                bb_error_msg(INFO"sig%s received", "alarm");
1003        rotateasap = 1;
1004}
1005
1006static void sig_hangup_handler(int sig_no UNUSED_PARAM)
1007{
1008        if (verbose)
1009                bb_error_msg(INFO"sig%s received", "hangup");
1010        reopenasap = 1;
1011}
1012
1013static void logmatch(struct logdir *ld, char* lineptr, int lineptr_len)
1014{
1015        char *s;
1016
1017        ld->match = '+';
1018        ld->matcherr = 'E';
1019        s = ld->inst;
1020        while (s && s[0]) {
1021                switch (s[0]) {
1022                case '+':
1023                case '-':
1024                        if (pmatch(s+1, lineptr, lineptr_len))
1025                                ld->match = s[0];
1026                        break;
1027                case 'e':
1028                case 'E':
1029                        if (pmatch(s+1, lineptr, lineptr_len))
1030                                ld->matcherr = s[0];
1031                        break;
1032                }
1033                s += strlen(s) + 1;
1034        }
1035}
1036
1037int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1038int svlogd_main(int argc, char **argv)
1039{
1040        char *r, *l, *b;
1041        ssize_t stdin_cnt = 0;
1042        int i;
1043        unsigned opt;
1044        unsigned timestamp = 0;
1045
1046        INIT_G();
1047
1048        opt = getopt32(argv, "^"
1049                        "r:R:l:b:tv" "\0" "tt:vv",
1050                        &r, &replace, &l, &b, &timestamp, &verbose
1051        );
1052        if (opt & 1) { // -r
1053                repl = r[0];
1054                if (!repl || r[1])
1055                        bb_show_usage();
1056        }
1057        if (opt & 2) if (!repl) repl = '_'; // -R
1058        if (opt & 4) { // -l
1059                linemax = xatou_range(l, 0, COMMON_BUFSIZE-26);
1060                if (linemax == 0)
1061                        linemax = COMMON_BUFSIZE-26;
1062                if (linemax < 256)
1063                        linemax = 256;
1064        }
1065        ////if (opt & 8) { // -b
1066        ////    buflen = xatoi_positive(b);
1067        ////    if (buflen == 0) buflen = 1024;
1068        ////}
1069        //if (opt & 0x10) timestamp++; // -t
1070        //if (opt & 0x20) verbose++; // -v
1071        //if (timestamp > 2) timestamp = 2;
1072        argv += optind;
1073        argc -= optind;
1074
1075        dirn = argc;
1076        if (dirn <= 0)
1077                bb_show_usage();
1078        ////if (buflen <= linemax) bb_show_usage();
1079        fdwdir = xopen(".", O_RDONLY|O_NDELAY);
1080        close_on_exec_on(fdwdir);
1081        dir = xzalloc(dirn * sizeof(dir[0]));
1082        for (i = 0; i < dirn; ++i) {
1083                dir[i].fddir = -1;
1084                dir[i].fdcur = -1;
1085                ////dir[i].btmp = xmalloc(buflen);
1086                /*dir[i].ppid = 0;*/
1087        }
1088        /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
1089        fndir = argv;
1090        /* We cannot set NONBLOCK on fd #0 permanently - this setting
1091         * _isn't_ per-process! It is shared among all other processes
1092         * with the same stdin */
1093        fl_flag_0 = fcntl(0, F_GETFL);
1094
1095        sigemptyset(&blocked_sigset);
1096        sigaddset(&blocked_sigset, SIGTERM);
1097        sigaddset(&blocked_sigset, SIGCHLD);
1098        sigaddset(&blocked_sigset, SIGALRM);
1099        sigaddset(&blocked_sigset, SIGHUP);
1100        sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
1101        bb_signals_recursive_norestart(1 << SIGTERM, sig_term_handler);
1102        bb_signals_recursive_norestart(1 << SIGCHLD, sig_child_handler);
1103        bb_signals_recursive_norestart(1 << SIGALRM, sig_alarm_handler);
1104        bb_signals_recursive_norestart(1 << SIGHUP, sig_hangup_handler);
1105
1106        /* Without timestamps, we don't have to print each line
1107         * separately, so we can look for _last_ newline, not first,
1108         * thus batching writes. If filtering is enabled in config,
1109         * logdirs_reopen resets it to memchr.
1110         */
1111        memRchr = (timestamp ? memchr : memrchr);
1112
1113        logdirs_reopen();
1114
1115        setvbuf(stderr, NULL, _IOFBF, linelen);
1116
1117        /* Each iteration processes one or more lines */
1118        while (1) {
1119                char stamp[FMT_PTIME];
1120                char *lineptr;
1121                char *printptr;
1122                char *np;
1123                int printlen;
1124                char ch;
1125
1126                lineptr = line;
1127                if (timestamp)
1128                        lineptr += 26;
1129
1130                /* lineptr[0..linemax-1] - buffer for stdin */
1131                /* (possibly has some unprocessed data from prev loop) */
1132
1133                /* Refill the buffer if needed */
1134                np = memRchr(lineptr, '\n', stdin_cnt);
1135                if (!np && !exitasap) {
1136                        i = linemax - stdin_cnt; /* avail. bytes at tail */
1137                        if (i >= 128) {
1138                                i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
1139                                if (i <= 0) /* EOF or error on stdin */
1140                                        exitasap = 1;
1141                                else {
1142                                        np = memRchr(lineptr + stdin_cnt, '\n', i);
1143                                        stdin_cnt += i;
1144                                }
1145                        }
1146                }
1147                if (stdin_cnt <= 0 && exitasap)
1148                        break;
1149
1150                /* Search for '\n' (in fact, np already holds the result) */
1151                linelen = stdin_cnt;
1152                if (np) {
1153 print_to_nl:
1154                        /* NB: starting from here lineptr may point
1155                         * farther out into line[] */
1156                        linelen = np - lineptr + 1;
1157                }
1158                /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1159                ch = lineptr[linelen-1];
1160
1161                /* Biggest performance hit was coming from the fact
1162                 * that we did not buffer writes. We were reading many lines
1163                 * in one read() above, but wrote one line per write().
1164                 * We are using stdio to fix that */
1165
1166                /* write out lineptr[0..linelen-1] to each log destination
1167                 * (or lineptr[-26..linelen-1] if timestamping) */
1168                printlen = linelen;
1169                printptr = lineptr;
1170                if (timestamp) {
1171                        if (timestamp == 1)
1172                                fmt_time_bernstein_25(stamp);
1173                        else /* 2+: */
1174                                fmt_time_human_30nul(stamp, timestamp == 2 ? '_' : 'T');
1175                        printlen += 26;
1176                        printptr -= 26;
1177                        memcpy(printptr, stamp, 25);
1178                        printptr[25] = ' ';
1179                }
1180                for (i = 0; i < dirn; ++i) {
1181                        struct logdir *ld = &dir[i];
1182                        if (ld->fddir == -1)
1183                                continue;
1184                        if (ld->inst)
1185                                logmatch(ld, lineptr, linelen);
1186                        if (ld->matcherr == 'e') {
1187                                /* runit-1.8.0 compat: if timestamping, do it on stderr too */
1188                                ////full_write(STDERR_FILENO, printptr, printlen);
1189                                fwrite(printptr, 1, printlen, stderr);
1190                        }
1191                        if (ld->match != '+')
1192                                continue;
1193                        buffer_pwrite(i, printptr, printlen);
1194                }
1195
1196                /* If we didn't see '\n' (long input line), */
1197                /* read/write repeatedly until we see it */
1198                while (ch != '\n') {
1199                        /* lineptr is emptied now, safe to use as buffer */
1200                        stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
1201                        if (stdin_cnt <= 0) { /* EOF or error on stdin */
1202                                exitasap = 1;
1203                                lineptr[0] = ch = '\n';
1204                                linelen = 1;
1205                                stdin_cnt = 1;
1206                        } else {
1207                                linelen = stdin_cnt;
1208                                np = memRchr(lineptr, '\n', stdin_cnt);
1209                                if (np)
1210                                        linelen = np - lineptr + 1;
1211                                ch = lineptr[linelen-1];
1212                        }
1213                        /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1214                        for (i = 0; i < dirn; ++i) {
1215                                if (dir[i].fddir == -1)
1216                                        continue;
1217                                if (dir[i].matcherr == 'e') {
1218                                        ////full_write(STDERR_FILENO, lineptr, linelen);
1219                                        fwrite(lineptr, 1, linelen, stderr);
1220                                }
1221                                if (dir[i].match != '+')
1222                                        continue;
1223                                buffer_pwrite(i, lineptr, linelen);
1224                        }
1225                }
1226
1227                stdin_cnt -= linelen;
1228                if (stdin_cnt > 0) {
1229                        lineptr += linelen;
1230                        /* If we see another '\n', we don't need to read
1231                         * next piece of input: can print what we have */
1232                        np = memRchr(lineptr, '\n', stdin_cnt);
1233                        if (np)
1234                                goto print_to_nl;
1235                        /* Move unprocessed data to the front of line */
1236                        memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1237                }
1238                fflush_all();////
1239        }
1240
1241        for (i = 0; i < dirn; ++i) {
1242                if (dir[i].ppid)
1243                        while (!processorstop(&dir[i]))
1244                                continue;
1245                logdir_close(&dir[i]);
1246        }
1247        return 0;
1248}
1249