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_simple_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        xgettimeofday(&tv);
 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        xgettimeofday(&tv);
 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                 * But we can get signals BEFORE execl(), this is unlikely
 417                 * but wouldn't be good...
 418                 */
 419                /*bb_signals(0
 420                        + (1 << SIGTERM)
 421                        //+ (1 << SIGCHLD)
 422                        + (1 << SIGALRM)
 423                        + (1 << SIGHUP)
 424                        , SIG_DFL);*/
 425                /* runit 2.1.2 does not unblock SIGCHLD, a bug? we do: */
 426                sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
 427
 428                if (verbose)
 429                        bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
 430
 431                fd = open_or_warn(ld->fnsave, O_RDONLY|O_NDELAY);
 432                /* Used to have xopen() above, but it causes infinite restarts of processor
 433                 * if file is gone - which can happen even because of _us_!
 434                 * Users report that if on reboot, time is reset to before existing
 435                 * logfiles creation time, rmoldest() deletes the newest logfile (!)
 436                 * and we end up here trying to open this now-deleted file.
 437                 */
 438                if (fd < 0)
 439                        _exit(0); /* fake "success": do not run processor again */
 440
 441                xmove_fd(fd, 0);
 442                ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
 443                fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
 444                xmove_fd(fd, 1);
 445                fd = open("state", O_RDONLY|O_NDELAY);
 446                if (fd == -1) {
 447                        if (errno != ENOENT)
 448                                bb_perror_msg_and_die(FATAL"can't %s processor %s", "open state for", ld->name);
 449                        close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
 450                        fd = xopen("state", O_RDONLY|O_NDELAY);
 451                }
 452                xmove_fd(fd, 4);
 453                fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
 454                xmove_fd(fd, 5);
 455
 456                execl(G.shell, G.shell, "-c", ld->processor, (char*) NULL);
 457                bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name);
 458        }
 459        ld->fnsave[26] = sv_ch; /* ...restore */
 460        ld->ppid = pid;
 461}
 462
 463static unsigned processorstop(struct logdir *ld)
 464{
 465        char f[28];
 466
 467        if (ld->ppid) {
 468                sig_unblock(SIGHUP);
 469                while (safe_waitpid(ld->ppid, &wstat, 0) == -1)
 470                        pause2cannot("wait for processor", ld->name);
 471                sig_block(SIGHUP);
 472                ld->ppid = 0;
 473        }
 474        if (ld->fddir == -1)
 475                return 1;
 476        while (fchdir(ld->fddir) == -1)
 477                pause2cannot("change directory, want processor", ld->name);
 478        if (WEXITSTATUS(wstat) != 0) {
 479                warnx("processor failed, restart", ld->name);
 480                ld->fnsave[26] = 't';
 481                unlink(ld->fnsave);
 482                ld->fnsave[26] = 'u';
 483                processorstart(ld);
 484                while (fchdir(fdwdir) == -1)
 485                        pause1cannot("change to initial working directory");
 486                return ld->processor ? 0 : 1;
 487        }
 488        ld->fnsave[26] = 't';
 489        memcpy(f, ld->fnsave, 26);
 490        f[26] = 's';
 491        f[27] = '\0';
 492        while (rename(ld->fnsave, f) == -1)
 493                pause2cannot("rename processed", ld->name);
 494        while (chmod(f, 0744) == -1)
 495                pause2cannot("set mode of processed", ld->name);
 496        ld->fnsave[26] = 'u';
 497        if (unlink(ld->fnsave) == -1)
 498                bb_error_msg(WARNING"can't unlink: %s/%s", ld->name, ld->fnsave);
 499        while (rename("newstate", "state") == -1)
 500                pause2cannot("rename state", ld->name);
 501        if (verbose)
 502                bb_error_msg(INFO"processed: %s/%s", ld->name, f);
 503        while (fchdir(fdwdir) == -1)
 504                pause1cannot("change to initial working directory");
 505        return 1;
 506}
 507
 508static void rmoldest(struct logdir *ld)
 509{
 510        DIR *d;
 511        struct dirent *f;
 512        char oldest[FMT_PTIME];
 513        int n = 0;
 514
 515        oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
 516        while (!(d = opendir(".")))
 517                pause2cannot("open directory, want rotate", ld->name);
 518        errno = 0;
 519        while ((f = readdir(d))) {
 520                if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
 521                        if (f->d_name[26] == 't') {
 522                                if (unlink(f->d_name) == -1)
 523                                        warn2("can't unlink processor leftover", f->d_name);
 524                        } else {
 525                                ++n;
 526                                if (strcmp(f->d_name, oldest) < 0)
 527                                        memcpy(oldest, f->d_name, 27);
 528                        }
 529                        errno = 0;
 530                }
 531        }
 532        if (errno)
 533                warn2("can't read directory", ld->name);
 534        closedir(d);
 535
 536        if (ld->nmax && (n > ld->nmax)) {
 537                if (verbose)
 538                        bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
 539                if ((*oldest == '@') && (unlink(oldest) == -1))
 540                        warn2("can't unlink oldest logfile", ld->name);
 541        }
 542}
 543
 544static unsigned rotate(struct logdir *ld)
 545{
 546        struct stat st;
 547        unsigned now;
 548
 549        if (ld->fddir == -1) {
 550                ld->rotate_period = 0;
 551                return 0;
 552        }
 553        if (ld->ppid)
 554                while (!processorstop(ld))
 555                        continue;
 556
 557        while (fchdir(ld->fddir) == -1)
 558                pause2cannot("change directory, want rotate", ld->name);
 559
 560        /* create new filename */
 561        ld->fnsave[25] = '.';
 562        ld->fnsave[26] = 's';
 563        if (ld->processor)
 564                ld->fnsave[26] = 'u';
 565        ld->fnsave[27] = '\0';
 566        do {
 567                fmt_time_bernstein_25(ld->fnsave);
 568                errno = 0;
 569                stat(ld->fnsave, &st);
 570        } while (errno != ENOENT);
 571
 572        now = monotonic_sec();
 573        if (ld->rotate_period && LESS(ld->next_rotate, now)) {
 574                ld->next_rotate = now + ld->rotate_period;
 575                if (LESS(ld->next_rotate, nearest_rotate))
 576                        nearest_rotate = ld->next_rotate;
 577        }
 578
 579        if (ld->size > 0) {
 580                while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
 581                        pause2cannot("fsync current logfile", ld->name);
 582                while (fchmod(ld->fdcur, 0744) == -1)
 583                        pause2cannot("set mode of current", ld->name);
 584                ////close(ld->fdcur);
 585                fclose(ld->filecur);
 586
 587                if (verbose) {
 588                        bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
 589                                        ld->fnsave, ld->size);
 590                }
 591                while (rename("current", ld->fnsave) == -1)
 592                        pause2cannot("rename current", ld->name);
 593                while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
 594                        pause2cannot("create new current", ld->name);
 595                while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL) ////
 596                        pause2cannot("create new current", ld->name); /* very unlikely */
 597                setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
 598                close_on_exec_on(ld->fdcur);
 599                ld->size = 0;
 600                while (fchmod(ld->fdcur, 0644) == -1)
 601                        pause2cannot("set mode of current", ld->name);
 602
 603                rmoldest(ld);
 604                processorstart(ld);
 605        }
 606
 607        while (fchdir(fdwdir) == -1)
 608                pause1cannot("change to initial working directory");
 609        return 1;
 610}
 611
 612static int buffer_pwrite(int n, char *s, unsigned len)
 613{
 614        int i;
 615        struct logdir *ld = &dir[n];
 616
 617        if (ld->sizemax) {
 618                if (ld->size >= ld->sizemax)
 619                        rotate(ld);
 620                if (len > (ld->sizemax - ld->size))
 621                        len = ld->sizemax - ld->size;
 622        }
 623        while (1) {
 624                ////i = full_write(ld->fdcur, s, len);
 625                ////if (i != -1) break;
 626                i = fwrite(s, 1, len, ld->filecur);
 627                if (i == len) break;
 628
 629                if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
 630                        DIR *d;
 631                        struct dirent *f;
 632                        char oldest[FMT_PTIME];
 633                        int j = 0;
 634
 635                        while (fchdir(ld->fddir) == -1)
 636                                pause2cannot("change directory, want remove old logfile",
 637                                                        ld->name);
 638                        oldest[0] = 'A';
 639                        oldest[1] = oldest[27] = '\0';
 640                        while (!(d = opendir(".")))
 641                                pause2cannot("open directory, want remove old logfile",
 642                                                        ld->name);
 643                        errno = 0;
 644                        while ((f = readdir(d)))
 645                                if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
 646                                        ++j;
 647                                        if (strcmp(f->d_name, oldest) < 0)
 648                                                memcpy(oldest, f->d_name, 27);
 649                                }
 650                        if (errno) warn2("can't read directory, want remove old logfile",
 651                                        ld->name);
 652                        closedir(d);
 653                        errno = ENOSPC;
 654                        if (j > ld->nmin) {
 655                                if (*oldest == '@') {
 656                                        bb_error_msg(WARNING"out of disk space, delete: %s/%s",
 657                                                        ld->name, oldest);
 658                                        errno = 0;
 659                                        if (unlink(oldest) == -1) {
 660                                                warn2("can't unlink oldest logfile", ld->name);
 661                                                errno = ENOSPC;
 662                                        }
 663                                        while (fchdir(fdwdir) == -1)
 664                                                pause1cannot("change to initial working directory");
 665                                }
 666                        }
 667                }
 668                if (errno)
 669                        pause2cannot("write to current", ld->name);
 670        }
 671
 672        ld->size += i;
 673        if (ld->sizemax)
 674                if (s[i-1] == '\n')
 675                        if (ld->size >= (ld->sizemax - linemax))
 676                                rotate(ld);
 677        return i;
 678}
 679
 680static void logdir_close(struct logdir *ld)
 681{
 682        if (ld->fddir == -1)
 683                return;
 684        if (verbose)
 685                bb_error_msg(INFO"close: %s", ld->name);
 686        close(ld->fddir);
 687        ld->fddir = -1;
 688        if (ld->fdcur == -1)
 689                return; /* impossible */
 690        while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
 691                pause2cannot("fsync current logfile", ld->name);
 692        while (fchmod(ld->fdcur, 0744) == -1)
 693                pause2cannot("set mode of current", ld->name);
 694        ////close(ld->fdcur);
 695        fclose(ld->filecur);
 696        ld->fdcur = -1;
 697        if (ld->fdlock == -1)
 698                return; /* impossible */
 699        close(ld->fdlock);
 700        ld->fdlock = -1;
 701        free(ld->processor);
 702        ld->processor = NULL;
 703}
 704
 705static NOINLINE unsigned logdir_open(struct logdir *ld, const char *fn)
 706{
 707        char buf[128];
 708        unsigned now;
 709        char *new, *s, *np;
 710        int i;
 711        struct stat st;
 712
 713        now = monotonic_sec();
 714
 715        ld->fddir = open(fn, O_RDONLY|O_NDELAY);
 716        if (ld->fddir == -1) {
 717                warn2("can't open log directory", (char*)fn);
 718                return 0;
 719        }
 720        close_on_exec_on(ld->fddir);
 721        if (fchdir(ld->fddir) == -1) {
 722                logdir_close(ld);
 723                warn2("can't change directory", (char*)fn);
 724                return 0;
 725        }
 726        ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
 727        if ((ld->fdlock == -1)
 728         || (flock(ld->fdlock, LOCK_EX | LOCK_NB) == -1)
 729        ) {
 730                logdir_close(ld);
 731                warn2("can't lock directory", (char*)fn);
 732                while (fchdir(fdwdir) == -1)
 733                        pause1cannot("change to initial working directory");
 734                return 0;
 735        }
 736        close_on_exec_on(ld->fdlock);
 737
 738        ld->size = 0;
 739        ld->sizemax = 1000000;
 740        ld->nmax = ld->nmin = 10;
 741        ld->rotate_period = 0;
 742        ld->name = (char*)fn;
 743        ld->ppid = 0;
 744        ld->match = '+';
 745        free(ld->inst); ld->inst = NULL;
 746        free(ld->processor); ld->processor = NULL;
 747
 748        /* read config */
 749        i = open_read_close("config", buf, sizeof(buf) - 1);
 750        if (i < 0 && errno != ENOENT)
 751                bb_perror_msg(WARNING"%s/config", ld->name);
 752        if (i > 0) {
 753                buf[i] = '\0';
 754                if (verbose)
 755                        bb_error_msg(INFO"read: %s/config", ld->name);
 756                s = buf;
 757                while (s) {
 758                        np = strchr(s, '\n');
 759                        if (np)
 760                                *np++ = '\0';
 761                        switch (s[0]) {
 762                        case '+':
 763                        case '-':
 764                        case 'e':
 765                        case 'E':
 766                                /* Filtering requires one-line buffering,
 767                                 * resetting the "find newline" function
 768                                 * accordingly */
 769                                memRchr = memchr;
 770                                /* Add '\n'-terminated line to ld->inst */
 771                                while (1) {
 772                                        int l = asprintf(&new, "%s%s\n", ld->inst ? ld->inst : "", s);
 773                                        if (l >= 0 && new)
 774                                                break;
 775                                        pause_nomem();
 776                                }
 777                                free(ld->inst);
 778                                ld->inst = new;
 779                                break;
 780                        case 's': {
 781                                ld->sizemax = xatou_sfx(&s[1], km_suffixes);
 782                                break;
 783                        }
 784                        case 'n':
 785                                ld->nmax = xatoi_positive(&s[1]);
 786                                break;
 787                        case 'N':
 788                                ld->nmin = xatoi_positive(&s[1]);
 789                                break;
 790                        case 't': {
 791                                static const struct suffix_mult mh_suffixes[] ALIGN_SUFFIX = {
 792                                        { "m", 60 },
 793                                        { "h", 60*60 },
 794                                        /*{ "d", 24*60*60 },*/
 795                                        { "", 0 }
 796                                };
 797                                ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
 798                                if (ld->rotate_period) {
 799                                        ld->next_rotate = now + ld->rotate_period;
 800                                        if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
 801                                                nearest_rotate = ld->next_rotate;
 802                                        tmaxflag = 1;
 803                                }
 804                                break;
 805                        }
 806                        case '!':
 807                                if (s[1]) {
 808                                        free(ld->processor);
 809                                        ld->processor = wstrdup(&s[1]);
 810                                }
 811                                break;
 812                        }
 813                        s = np;
 814                }
 815                /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
 816                s = ld->inst;
 817                while (s) {
 818                        np = strchr(s, '\n');
 819                        if (np)
 820                                *np++ = '\0';
 821                        s = np;
 822                }
 823        }
 824
 825        /* open current */
 826        i = stat("current", &st);
 827        if (i != -1) {
 828                if (st.st_size && !(st.st_mode & S_IXUSR)) {
 829                        ld->fnsave[25] = '.';
 830                        ld->fnsave[26] = 'u';
 831                        ld->fnsave[27] = '\0';
 832                        do {
 833                                fmt_time_bernstein_25(ld->fnsave);
 834                                errno = 0;
 835                                stat(ld->fnsave, &st);
 836                        } while (errno != ENOENT);
 837                        while (rename("current", ld->fnsave) == -1)
 838                                pause2cannot("rename current", ld->name);
 839                        rmoldest(ld);
 840                        i = -1;
 841                } else {
 842                        /* st.st_size can be not just bigger, but WIDER!
 843                         * This code is safe: if st.st_size > 4GB, we select
 844                         * ld->sizemax (because it's "unsigned") */
 845                        ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
 846                }
 847        } else {
 848                if (errno != ENOENT) {
 849                        logdir_close(ld);
 850                        warn2("can't stat current", ld->name);
 851                        while (fchdir(fdwdir) == -1)
 852                                pause1cannot("change to initial working directory");
 853                        return 0;
 854                }
 855        }
 856        while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
 857                pause2cannot("open current", ld->name);
 858        while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL)
 859                pause2cannot("open current", ld->name); ////
 860        setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
 861
 862        close_on_exec_on(ld->fdcur);
 863        while (fchmod(ld->fdcur, 0644) == -1)
 864                pause2cannot("set mode of current", ld->name);
 865
 866        if (verbose) {
 867                if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
 868                else bb_error_msg(INFO"new: %s/current", ld->name);
 869        }
 870
 871        while (fchdir(fdwdir) == -1)
 872                pause1cannot("change to initial working directory");
 873        return 1;
 874}
 875
 876static void logdirs_reopen(void)
 877{
 878        int l;
 879        int ok = 0;
 880
 881        tmaxflag = 0;
 882        for (l = 0; l < dirn; ++l) {
 883                logdir_close(&dir[l]);
 884                if (logdir_open(&dir[l], fndir[l]))
 885                        ok = 1;
 886        }
 887        if (!ok)
 888                fatalx("no functional log directories");
 889}
 890
 891/* Will look good in libbb one day */
 892static ssize_t ndelay_read(int fd, void *buf, size_t count)
 893{
 894        if (!(fl_flag_0 & O_NONBLOCK))
 895                fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
 896        count = safe_read(fd, buf, count);
 897        if (!(fl_flag_0 & O_NONBLOCK))
 898                fcntl(fd, F_SETFL, fl_flag_0);
 899        return count;
 900}
 901
 902/* Used for reading stdin */
 903static int buffer_pread(/*int fd, */char *s, unsigned len)
 904{
 905        unsigned now;
 906        struct pollfd input;
 907        int i;
 908
 909        input.fd = STDIN_FILENO;
 910        input.events = POLLIN;
 911
 912        do {
 913                if (rotateasap) {
 914                        for (i = 0; i < dirn; ++i)
 915                                rotate(dir + i);
 916                        rotateasap = 0;
 917                }
 918                if (exitasap) {
 919                        if (linecomplete)
 920                                return 0;
 921                        len = 1;
 922                }
 923                if (reopenasap) {
 924                        logdirs_reopen();
 925                        reopenasap = 0;
 926                }
 927                now = monotonic_sec();
 928                nearest_rotate = now + (45 * 60 + 45);
 929                for (i = 0; i < dirn; ++i) {
 930                        if (dir[i].rotate_period) {
 931                                if (LESS(dir[i].next_rotate, now))
 932                                        rotate(dir + i);
 933                                if (LESS(dir[i].next_rotate, nearest_rotate))
 934                                        nearest_rotate = dir[i].next_rotate;
 935                        }
 936                }
 937
 938                sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
 939                i = nearest_rotate - now;
 940                if (i > 1000000)
 941                        i = 1000000;
 942                if (i <= 0)
 943                        i = 1;
 944                poll(&input, 1, i * 1000);
 945                sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
 946
 947                i = ndelay_read(STDIN_FILENO, s, len);
 948                if (i >= 0)
 949                        break;
 950                if (errno == EINTR)
 951                        continue;
 952                if (errno != EAGAIN) {
 953                        warn("can't read standard input");
 954                        break;
 955                }
 956                /* else: EAGAIN - normal, repeat silently */
 957        } while (!exitasap);
 958
 959        if (i > 0) {
 960                int cnt;
 961                linecomplete = (s[i-1] == '\n');
 962                if (!repl)
 963                        return i;
 964
 965                cnt = i;
 966                while (--cnt >= 0) {
 967                        char ch = *s;
 968                        if (ch != '\n') {
 969                                if (ch < 32 || ch > 126)
 970                                        *s = repl;
 971                                else {
 972                                        int j;
 973                                        for (j = 0; replace[j]; ++j) {
 974                                                if (ch == replace[j]) {
 975                                                        *s = repl;
 976                                                        break;
 977                                                }
 978                                        }
 979                                }
 980                        }
 981                        s++;
 982                }
 983        }
 984        return i;
 985}
 986
 987static void sig_term_handler(int sig_no UNUSED_PARAM)
 988{
 989        if (verbose)
 990                bb_error_msg(INFO"sig%s received", "term");
 991        exitasap = 1;
 992}
 993
 994static void sig_child_handler(int sig_no UNUSED_PARAM)
 995{
 996        pid_t pid;
 997        int l;
 998
 999        if (verbose)
1000                bb_error_msg(INFO"sig%s received", "child");
1001        while ((pid = wait_any_nohang(&wstat)) > 0) {
1002                for (l = 0; l < dirn; ++l) {
1003                        if (dir[l].ppid == pid) {
1004                                dir[l].ppid = 0;
1005                                processorstop(&dir[l]);
1006                                break;
1007                        }
1008                }
1009        }
1010}
1011
1012static void sig_alarm_handler(int sig_no UNUSED_PARAM)
1013{
1014        if (verbose)
1015                bb_error_msg(INFO"sig%s received", "alarm");
1016        rotateasap = 1;
1017}
1018
1019static void sig_hangup_handler(int sig_no UNUSED_PARAM)
1020{
1021        if (verbose)
1022                bb_error_msg(INFO"sig%s received", "hangup");
1023        reopenasap = 1;
1024}
1025
1026static void logmatch(struct logdir *ld, char* lineptr, int lineptr_len)
1027{
1028        char *s;
1029
1030        ld->match = '+';
1031        ld->matcherr = 'E';
1032        s = ld->inst;
1033        while (s && s[0]) {
1034                switch (s[0]) {
1035                case '+':
1036                case '-':
1037                        if (pmatch(s+1, lineptr, lineptr_len))
1038                                ld->match = s[0];
1039                        break;
1040                case 'e':
1041                case 'E':
1042                        if (pmatch(s+1, lineptr, lineptr_len))
1043                                ld->matcherr = s[0];
1044                        break;
1045                }
1046                s += strlen(s) + 1;
1047        }
1048}
1049
1050int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1051int svlogd_main(int argc, char **argv)
1052{
1053        char *r, *l, *b;
1054        ssize_t stdin_cnt = 0;
1055        int i;
1056        unsigned opt;
1057        unsigned timestamp = 0;
1058
1059        INIT_G();
1060
1061        opt = getopt32(argv, "^"
1062                        "r:R:l:b:tv" "\0" "tt:vv",
1063                        &r, &replace, &l, &b, &timestamp, &verbose
1064        );
1065        if (opt & 1) { // -r
1066                repl = r[0];
1067                if (!repl || r[1])
1068                        bb_show_usage();
1069        }
1070        if (opt & 2) if (!repl) repl = '_'; // -R
1071        if (opt & 4) { // -l
1072                linemax = xatou_range(l, 0, COMMON_BUFSIZE-26);
1073                if (linemax == 0)
1074                        linemax = COMMON_BUFSIZE-26;
1075                if (linemax < 256)
1076                        linemax = 256;
1077        }
1078        ////if (opt & 8) { // -b
1079        ////    buflen = xatoi_positive(b);
1080        ////    if (buflen == 0) buflen = 1024;
1081        ////}
1082        //if (opt & 0x10) timestamp++; // -t
1083        //if (opt & 0x20) verbose++; // -v
1084        //if (timestamp > 2) timestamp = 2;
1085        argv += optind;
1086        argc -= optind;
1087
1088        dirn = argc;
1089        if (dirn <= 0)
1090                bb_show_usage();
1091        ////if (buflen <= linemax) bb_show_usage();
1092        fdwdir = xopen(".", O_RDONLY|O_NDELAY);
1093        close_on_exec_on(fdwdir);
1094        dir = xzalloc(dirn * sizeof(dir[0]));
1095        for (i = 0; i < dirn; ++i) {
1096                dir[i].fddir = -1;
1097                dir[i].fdcur = -1;
1098                ////dir[i].btmp = xmalloc(buflen);
1099                /*dir[i].ppid = 0;*/
1100        }
1101        /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
1102        fndir = argv;
1103        /* We cannot set NONBLOCK on fd #0 permanently - this setting
1104         * _isn't_ per-process! It is shared among all other processes
1105         * with the same stdin */
1106        fl_flag_0 = fcntl(0, F_GETFL);
1107
1108        sigemptyset(&blocked_sigset);
1109        sigaddset(&blocked_sigset, SIGTERM);
1110        sigaddset(&blocked_sigset, SIGCHLD);
1111        sigaddset(&blocked_sigset, SIGALRM);
1112        sigaddset(&blocked_sigset, SIGHUP);
1113        sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
1114        bb_signals_norestart(1 << SIGTERM, sig_term_handler);
1115        bb_signals_norestart(1 << SIGCHLD, sig_child_handler);
1116        bb_signals_norestart(1 << SIGALRM, sig_alarm_handler);
1117        bb_signals_norestart(1 << SIGHUP, sig_hangup_handler);
1118
1119        /* Without timestamps, we don't have to print each line
1120         * separately, so we can look for _last_ newline, not first,
1121         * thus batching writes. If filtering is enabled in config,
1122         * logdirs_reopen resets it to memchr.
1123         */
1124        memRchr = (timestamp ? memchr : memrchr);
1125
1126        logdirs_reopen();
1127
1128        setvbuf(stderr, NULL, _IOFBF, linelen);
1129
1130        /* Each iteration processes one or more lines */
1131        while (1) {
1132                char stamp[FMT_PTIME];
1133                char *lineptr;
1134                char *printptr;
1135                char *np;
1136                int printlen;
1137                char ch;
1138
1139                lineptr = line;
1140                if (timestamp)
1141                        lineptr += 26;
1142
1143                /* lineptr[0..linemax-1] - buffer for stdin */
1144                /* (possibly has some unprocessed data from prev loop) */
1145
1146                /* Refill the buffer if needed */
1147                np = memRchr(lineptr, '\n', stdin_cnt);
1148                if (!np && !exitasap) {
1149                        i = linemax - stdin_cnt; /* avail. bytes at tail */
1150                        if (i >= 128) {
1151                                i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
1152                                if (i <= 0) /* EOF or error on stdin */
1153                                        exitasap = 1;
1154                                else {
1155                                        np = memRchr(lineptr + stdin_cnt, '\n', i);
1156                                        stdin_cnt += i;
1157                                }
1158                        }
1159                }
1160                if (stdin_cnt <= 0 && exitasap)
1161                        break;
1162
1163                /* Search for '\n' (in fact, np already holds the result) */
1164                linelen = stdin_cnt;
1165                if (np) {
1166 print_to_nl:
1167                        /* NB: starting from here lineptr may point
1168                         * farther out into line[] */
1169                        linelen = np - lineptr + 1;
1170                }
1171                /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1172                ch = lineptr[linelen-1];
1173
1174                /* Biggest performance hit was coming from the fact
1175                 * that we did not buffer writes. We were reading many lines
1176                 * in one read() above, but wrote one line per write().
1177                 * We are using stdio to fix that */
1178
1179                /* write out lineptr[0..linelen-1] to each log destination
1180                 * (or lineptr[-26..linelen-1] if timestamping) */
1181                printlen = linelen;
1182                printptr = lineptr;
1183                if (timestamp) {
1184                        if (timestamp == 1)
1185                                fmt_time_bernstein_25(stamp);
1186                        else /* 2+: */
1187                                fmt_time_human_30nul(stamp, timestamp == 2 ? '_' : 'T');
1188                        printlen += 26;
1189                        printptr -= 26;
1190                        memcpy(printptr, stamp, 25);
1191                        printptr[25] = ' ';
1192                }
1193                for (i = 0; i < dirn; ++i) {
1194                        struct logdir *ld = &dir[i];
1195                        if (ld->fddir == -1)
1196                                continue;
1197                        if (ld->inst)
1198                                logmatch(ld, lineptr, linelen);
1199                        if (ld->matcherr == 'e') {
1200                                /* runit-1.8.0 compat: if timestamping, do it on stderr too */
1201                                ////full_write(STDERR_FILENO, printptr, printlen);
1202                                fwrite(printptr, 1, printlen, stderr);
1203                        }
1204                        if (ld->match != '+')
1205                                continue;
1206                        buffer_pwrite(i, printptr, printlen);
1207                }
1208
1209                /* If we didn't see '\n' (long input line), */
1210                /* read/write repeatedly until we see it */
1211                while (ch != '\n') {
1212                        /* lineptr is emptied now, safe to use as buffer */
1213                        stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
1214                        if (stdin_cnt <= 0) { /* EOF or error on stdin */
1215                                exitasap = 1;
1216                                lineptr[0] = ch = '\n';
1217                                linelen = 1;
1218                                stdin_cnt = 1;
1219                        } else {
1220                                linelen = stdin_cnt;
1221                                np = memRchr(lineptr, '\n', stdin_cnt);
1222                                if (np)
1223                                        linelen = np - lineptr + 1;
1224                                ch = lineptr[linelen-1];
1225                        }
1226                        /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1227                        for (i = 0; i < dirn; ++i) {
1228                                if (dir[i].fddir == -1)
1229                                        continue;
1230                                if (dir[i].matcherr == 'e') {
1231                                        ////full_write(STDERR_FILENO, lineptr, linelen);
1232                                        fwrite(lineptr, 1, linelen, stderr);
1233                                }
1234                                if (dir[i].match != '+')
1235                                        continue;
1236                                buffer_pwrite(i, lineptr, linelen);
1237                        }
1238                }
1239
1240                stdin_cnt -= linelen;
1241                if (stdin_cnt > 0) {
1242                        lineptr += linelen;
1243                        /* If we see another '\n', we don't need to read
1244                         * next piece of input: can print what we have */
1245                        np = memRchr(lineptr, '\n', stdin_cnt);
1246                        if (np)
1247                                goto print_to_nl;
1248                        /* Move unprocessed data to the front of line */
1249                        memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1250                }
1251                fflush_all();////
1252        }
1253
1254        for (i = 0; i < dirn; ++i) {
1255                if (dir[i].ppid)
1256                        while (!processorstop(&dir[i]))
1257                                continue;
1258                logdir_close(&dir[i]);
1259        }
1260        return 0;
1261}
1262