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