svlogd: do not overrun read buffer (fixes bug 439)
[oweals/busybox.git] / runit / svlogd.c
1 /*
2 Copyright (c) 2001-2006, Gerrit Pape
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, 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
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 ADVISED 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 #include <sys/poll.h>
32 #include <sys/file.h>
33 #include "libbb.h"
34 #include "runit_lib.h"
35
36 #define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
37
38 #define FMT_PTIME 30
39
40 struct logdir {
41         ////char *btmp;
42         /* pattern list to match, in "aa\0bb\0\cc\0\0" form */
43         char *inst;
44         char *processor;
45         char *name;
46         unsigned size;
47         unsigned sizemax;
48         unsigned nmax;
49         unsigned nmin;
50         unsigned rotate_period;
51         int ppid;
52         int fddir;
53         int fdcur;
54         FILE* filecur; ////
55         int fdlock;
56         unsigned next_rotate;
57         char fnsave[FMT_PTIME];
58         char match;
59         char matcherr;
60 };
61
62
63 struct globals {
64         struct logdir *dir;
65         unsigned verbose;
66         int linemax;
67         ////int buflen;
68         int linelen;
69
70         int fdwdir;
71         char **fndir;
72         int wstat;
73         unsigned nearest_rotate;
74
75         smallint exitasap;
76         smallint rotateasap;
77         smallint reopenasap;
78         smallint linecomplete;
79         smallint tmaxflag;
80
81         char repl;
82         const char *replace;
83         int fl_flag_0;
84         unsigned dirn;
85
86         sigset_t blocked_sigset;
87 };
88 #define G (*(struct globals*)ptr_to_globals)
89 #define dir            (G.dir           )
90 #define verbose        (G.verbose       )
91 #define linemax        (G.linemax       )
92 #define buflen         (G.buflen        )
93 #define linelen        (G.linelen       )
94 #define fndir          (G.fndir         )
95 #define fdwdir         (G.fdwdir        )
96 #define wstat          (G.wstat         )
97 #define nearest_rotate (G.nearest_rotate)
98 #define exitasap       (G.exitasap      )
99 #define rotateasap     (G.rotateasap    )
100 #define reopenasap     (G.reopenasap    )
101 #define linecomplete   (G.linecomplete  )
102 #define tmaxflag       (G.tmaxflag      )
103 #define repl           (G.repl          )
104 #define replace        (G.replace       )
105 #define blocked_sigset (G.blocked_sigset)
106 #define fl_flag_0      (G.fl_flag_0     )
107 #define dirn           (G.dirn          )
108 #define INIT_G() do { \
109         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
110         linemax = 1000; \
111         /*buflen = 1024;*/ \
112         linecomplete = 1; \
113         replace = ""; \
114 } while (0)
115
116 #define line bb_common_bufsiz1
117
118
119 #define FATAL "fatal: "
120 #define WARNING "warning: "
121 #define PAUSE "pausing: "
122 #define INFO "info: "
123
124 #define usage() bb_show_usage()
125 static void fatalx(const char *m0)
126 {
127         bb_error_msg_and_die(FATAL"%s", m0);
128 }
129 static void warn(const char *m0)
130 {
131         bb_perror_msg(WARNING"%s", m0);
132 }
133 static void warn2(const char *m0, const char *m1)
134 {
135         bb_perror_msg(WARNING"%s: %s", m0, m1);
136 }
137 static void warnx(const char *m0, const char *m1)
138 {
139         bb_error_msg(WARNING"%s: %s", m0, m1);
140 }
141 static void pause_nomem(void)
142 {
143         bb_error_msg(PAUSE"out of memory");
144         sleep(3);
145 }
146 static void pause1cannot(const char *m0)
147 {
148         bb_perror_msg(PAUSE"can't %s", m0);
149         sleep(3);
150 }
151 static void pause2cannot(const char *m0, const char *m1)
152 {
153         bb_perror_msg(PAUSE"can't %s %s", m0, m1);
154         sleep(3);
155 }
156
157 static char* wstrdup(const char *str)
158 {
159         char *s;
160         while (!(s = strdup(str)))
161                 pause_nomem();
162         return s;
163 }
164
165 /*** ex fmt_ptime.[ch] ***/
166
167 /* NUL terminated */
168 static void fmt_time_human_30nul(char *s)
169 {
170         struct tm *t;
171         struct timeval tv;
172
173         gettimeofday(&tv, NULL);
174         t = gmtime(&(tv.tv_sec));
175         sprintf(s, "%04u-%02u-%02u_%02u:%02u:%02u.%06u000",
176                 (unsigned)(1900 + t->tm_year),
177                 (unsigned)(t->tm_mon + 1),
178                 (unsigned)(t->tm_mday),
179                 (unsigned)(t->tm_hour),
180                 (unsigned)(t->tm_min),
181                 (unsigned)(t->tm_sec),
182                 (unsigned)(tv.tv_usec)
183         );
184         /* 4+1 + 2+1 + 2+1 + 2+1 + 2+1 + 2+1 + 9 = */
185         /* 5   + 3   + 3   + 3   + 3   + 3   + 9 = */
186         /* 20 (up to '.' inclusive) + 9 (not including '\0') */
187 }
188
189 /* NOT terminated! */
190 static void fmt_time_bernstein_25(char *s)
191 {
192         uint32_t pack[3];
193         struct timeval tv;
194         unsigned sec_hi;
195
196         gettimeofday(&tv, NULL);
197         sec_hi = (0x400000000000000aULL + tv.tv_sec) >> 32;
198         tv.tv_sec = (time_t)(0x400000000000000aULL) + tv.tv_sec;
199         tv.tv_usec *= 1000;
200         /* Network order is big-endian: most significant byte first.
201          * This is exactly what we want here */
202         pack[0] = htonl(sec_hi);
203         pack[1] = htonl(tv.tv_sec);
204         pack[2] = htonl(tv.tv_usec);
205         *s++ = '@';
206         bin2hex(s, (char*)pack, 12);
207 }
208
209 static void processorstart(struct logdir *ld)
210 {
211         char sv_ch;
212         int pid;
213
214         if (!ld->processor) return;
215         if (ld->ppid) {
216                 warnx("processor already running", ld->name);
217                 return;
218         }
219
220         /* vfork'ed child trashes this byte, save... */
221         sv_ch = ld->fnsave[26];
222
223         while ((pid = vfork()) == -1)
224                 pause2cannot("vfork for processor", ld->name);
225         if (!pid) {
226                 int fd;
227
228                 /* child */
229                 /* Non-ignored signals revert to SIG_DFL on exec anyway */
230                 /*bb_signals(0
231                         + (1 << SIGTERM)
232                         + (1 << SIGALRM)
233                         + (1 << SIGHUP)
234                         , SIG_DFL);*/
235                 sig_unblock(SIGTERM);
236                 sig_unblock(SIGALRM);
237                 sig_unblock(SIGHUP);
238
239                 if (verbose)
240                         bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
241                 fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
242                 xmove_fd(fd, 0);
243                 ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
244                 fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
245                 xmove_fd(fd, 1);
246                 fd = open_read("state");
247                 if (fd == -1) {
248                         if (errno != ENOENT)
249                                 bb_perror_msg_and_die(FATAL"can't %s processor %s", "open state for", ld->name);
250                         close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
251                         fd = xopen("state", O_RDONLY|O_NDELAY);
252                 }
253                 xmove_fd(fd, 4);
254                 fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
255                 xmove_fd(fd, 5);
256
257 // getenv("SHELL")?
258                 execl("/bin/sh", "/bin/sh" + 5, "-c", ld->processor, (char*) NULL);
259                 bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name);
260         }
261         ld->fnsave[26] = sv_ch; /* ...restore */
262         ld->ppid = pid;
263 }
264
265 static unsigned processorstop(struct logdir *ld)
266 {
267         char f[28];
268
269         if (ld->ppid) {
270                 sig_unblock(SIGHUP);
271                 while (safe_waitpid(ld->ppid, &wstat, 0) == -1)
272                         pause2cannot("wait for processor", ld->name);
273                 sig_block(SIGHUP);
274                 ld->ppid = 0;
275         }
276         if (ld->fddir == -1) return 1;
277         while (fchdir(ld->fddir) == -1)
278                 pause2cannot("change directory, want processor", ld->name);
279         if (WEXITSTATUS(wstat) != 0) {
280                 warnx("processor failed, restart", ld->name);
281                 ld->fnsave[26] = 't';
282                 unlink(ld->fnsave);
283                 ld->fnsave[26] = 'u';
284                 processorstart(ld);
285                 while (fchdir(fdwdir) == -1)
286                         pause1cannot("change to initial working directory");
287                 return ld->processor ? 0 : 1;
288         }
289         ld->fnsave[26] = 't';
290         memcpy(f, ld->fnsave, 26);
291         f[26] = 's';
292         f[27] = '\0';
293         while (rename(ld->fnsave, f) == -1)
294                 pause2cannot("rename processed", ld->name);
295         while (chmod(f, 0744) == -1)
296                 pause2cannot("set mode of processed", ld->name);
297         ld->fnsave[26] = 'u';
298         if (unlink(ld->fnsave) == -1)
299                 bb_error_msg(WARNING"can't unlink: %s/%s", ld->name, ld->fnsave);
300         while (rename("newstate", "state") == -1)
301                 pause2cannot("rename state", ld->name);
302         if (verbose)
303                 bb_error_msg(INFO"processed: %s/%s", ld->name, f);
304         while (fchdir(fdwdir) == -1)
305                 pause1cannot("change to initial working directory");
306         return 1;
307 }
308
309 static void rmoldest(struct logdir *ld)
310 {
311         DIR *d;
312         struct dirent *f;
313         char oldest[FMT_PTIME];
314         int n = 0;
315
316         oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
317         while (!(d = opendir(".")))
318                 pause2cannot("open directory, want rotate", ld->name);
319         errno = 0;
320         while ((f = readdir(d))) {
321                 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
322                         if (f->d_name[26] == 't') {
323                                 if (unlink(f->d_name) == -1)
324                                         warn2("can't unlink processor leftover", f->d_name);
325                         } else {
326                                 ++n;
327                                 if (strcmp(f->d_name, oldest) < 0)
328                                         memcpy(oldest, f->d_name, 27);
329                         }
330                         errno = 0;
331                 }
332         }
333         if (errno)
334                 warn2("can't read directory", ld->name);
335         closedir(d);
336
337         if (ld->nmax && (n > ld->nmax)) {
338                 if (verbose)
339                         bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
340                 if ((*oldest == '@') && (unlink(oldest) == -1))
341                         warn2("can't unlink oldest logfile", ld->name);
342         }
343 }
344
345 static unsigned rotate(struct logdir *ld)
346 {
347         struct stat st;
348         unsigned now;
349
350         if (ld->fddir == -1) {
351                 ld->rotate_period = 0;
352                 return 0;
353         }
354         if (ld->ppid)
355                 while (!processorstop(ld))
356                         continue;
357
358         while (fchdir(ld->fddir) == -1)
359                 pause2cannot("change directory, want rotate", ld->name);
360
361         /* create new filename */
362         ld->fnsave[25] = '.';
363         ld->fnsave[26] = 's';
364         if (ld->processor)
365                 ld->fnsave[26] = 'u';
366         ld->fnsave[27] = '\0';
367         do {
368                 fmt_time_bernstein_25(ld->fnsave);
369                 errno = 0;
370                 stat(ld->fnsave, &st);
371         } while (errno != ENOENT);
372
373         now = monotonic_sec();
374         if (ld->rotate_period && LESS(ld->next_rotate, now)) {
375                 ld->next_rotate = now + ld->rotate_period;
376                 if (LESS(ld->next_rotate, nearest_rotate))
377                         nearest_rotate = ld->next_rotate;
378         }
379
380         if (ld->size > 0) {
381                 while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
382                         pause2cannot("fsync current logfile", ld->name);
383                 while (fchmod(ld->fdcur, 0744) == -1)
384                         pause2cannot("set mode of current", ld->name);
385                 ////close(ld->fdcur);
386                 fclose(ld->filecur);
387
388                 if (verbose) {
389                         bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
390                                         ld->fnsave, ld->size);
391                 }
392                 while (rename("current", ld->fnsave) == -1)
393                         pause2cannot("rename current", ld->name);
394                 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
395                         pause2cannot("create new current", ld->name);
396                 while ((ld->filecur = fdopen(ld->fdcur, "a")) == NULL) ////
397                         pause2cannot("create new current", ld->name); /* very unlikely */
398                 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
399                 close_on_exec_on(ld->fdcur);
400                 ld->size = 0;
401                 while (fchmod(ld->fdcur, 0644) == -1)
402                         pause2cannot("set mode of current", ld->name);
403
404                 rmoldest(ld);
405                 processorstart(ld);
406         }
407
408         while (fchdir(fdwdir) == -1)
409                 pause1cannot("change to initial working directory");
410         return 1;
411 }
412
413 static int buffer_pwrite(int n, char *s, unsigned len)
414 {
415         int i;
416         struct logdir *ld = &dir[n];
417
418         if (ld->sizemax) {
419                 if (ld->size >= ld->sizemax)
420                         rotate(ld);
421                 if (len > (ld->sizemax - ld->size))
422                         len = ld->sizemax - ld->size;
423         }
424         while (1) {
425                 ////i = full_write(ld->fdcur, s, len);
426                 ////if (i != -1) break;
427                 i = fwrite(s, 1, len, ld->filecur);
428                 if (i == len) break;
429
430                 if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
431                         DIR *d;
432                         struct dirent *f;
433                         char oldest[FMT_PTIME];
434                         int j = 0;
435
436                         while (fchdir(ld->fddir) == -1)
437                                 pause2cannot("change directory, want remove old logfile",
438                                                          ld->name);
439                         oldest[0] = 'A';
440                         oldest[1] = oldest[27] = '\0';
441                         while (!(d = opendir(".")))
442                                 pause2cannot("open directory, want remove old logfile",
443                                                          ld->name);
444                         errno = 0;
445                         while ((f = readdir(d)))
446                                 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
447                                         ++j;
448                                         if (strcmp(f->d_name, oldest) < 0)
449                                                 memcpy(oldest, f->d_name, 27);
450                                 }
451                         if (errno) warn2("can't read directory, want remove old logfile",
452                                         ld->name);
453                         closedir(d);
454                         errno = ENOSPC;
455                         if (j > ld->nmin) {
456                                 if (*oldest == '@') {
457                                         bb_error_msg(WARNING"out of disk space, delete: %s/%s",
458                                                         ld->name, oldest);
459                                         errno = 0;
460                                         if (unlink(oldest) == -1) {
461                                                 warn2("can't unlink oldest logfile", ld->name);
462                                                 errno = ENOSPC;
463                                         }
464                                         while (fchdir(fdwdir) == -1)
465                                                 pause1cannot("change to initial working directory");
466                                 }
467                         }
468                 }
469                 if (errno)
470                         pause2cannot("write to current", ld->name);
471         }
472
473         ld->size += i;
474         if (ld->sizemax)
475                 if (s[i-1] == '\n')
476                         if (ld->size >= (ld->sizemax - linemax))
477                                 rotate(ld);
478         return i;
479 }
480
481 static void logdir_close(struct logdir *ld)
482 {
483         if (ld->fddir == -1)
484                 return;
485         if (verbose)
486                 bb_error_msg(INFO"close: %s", ld->name);
487         close(ld->fddir);
488         ld->fddir = -1;
489         if (ld->fdcur == -1)
490                 return; /* impossible */
491         while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
492                 pause2cannot("fsync current logfile", ld->name);
493         while (fchmod(ld->fdcur, 0744) == -1)
494                 pause2cannot("set mode of current", ld->name);
495         ////close(ld->fdcur);
496         fclose(ld->filecur);
497         ld->fdcur = -1;
498         if (ld->fdlock == -1)
499                 return; /* impossible */
500         close(ld->fdlock);
501         ld->fdlock = -1;
502         free(ld->processor);
503         ld->processor = NULL;
504 }
505
506 static unsigned logdir_open(struct logdir *ld, const char *fn)
507 {
508         char buf[128];
509         unsigned now;
510         char *new, *s, *np;
511         int i;
512         struct stat st;
513
514         now = monotonic_sec();
515
516         ld->fddir = open(fn, O_RDONLY|O_NDELAY);
517         if (ld->fddir == -1) {
518                 warn2("can't open log directory", (char*)fn);
519                 return 0;
520         }
521         close_on_exec_on(ld->fddir);
522         if (fchdir(ld->fddir) == -1) {
523                 logdir_close(ld);
524                 warn2("can't change directory", (char*)fn);
525                 return 0;
526         }
527         ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
528         if ((ld->fdlock == -1)
529          || (lock_exnb(ld->fdlock) == -1)
530         ) {
531                 logdir_close(ld);
532                 warn2("can't lock directory", (char*)fn);
533                 while (fchdir(fdwdir) == -1)
534                         pause1cannot("change to initial working directory");
535                 return 0;
536         }
537         close_on_exec_on(ld->fdlock);
538
539         ld->size = 0;
540         ld->sizemax = 1000000;
541         ld->nmax = ld->nmin = 10;
542         ld->rotate_period = 0;
543         ld->name = (char*)fn;
544         ld->ppid = 0;
545         ld->match = '+';
546         free(ld->inst); ld->inst = NULL;
547         free(ld->processor); ld->processor = NULL;
548
549         /* read config */
550         i = open_read_close("config", buf, sizeof(buf) - 1);
551         if (i < 0 && errno != ENOENT)
552                 bb_perror_msg(WARNING"%s/config", ld->name);
553         if (i > 0) {
554                 buf[i] = '\0';
555                 if (verbose)
556                         bb_error_msg(INFO"read: %s/config", ld->name);
557                 s = buf;
558                 while (s) {
559                         np = strchr(s, '\n');
560                         if (np)
561                                 *np++ = '\0';
562                         switch (s[0]) {
563                         case '+':
564                         case '-':
565                         case 'e':
566                         case 'E':
567                                 /* Add '\n'-terminated line to ld->inst */
568                                 while (1) {
569                                         int l = asprintf(&new, "%s%s\n", ld->inst ? : "", s);
570                                         if (l >= 0 && new)
571                                                 break;
572                                         pause_nomem();
573                                 }
574                                 free(ld->inst);
575                                 ld->inst = new;
576                                 break;
577                         case 's': {
578                                 static const struct suffix_mult km_suffixes[] = {
579                                         { "k", 1024 },
580                                         { "m", 1024*1024 },
581                                         { }
582                                 };
583                                 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
584                                 break;
585                         }
586                         case 'n':
587                                 ld->nmax = xatoi_u(&s[1]);
588                                 break;
589                         case 'N':
590                                 ld->nmin = xatoi_u(&s[1]);
591                                 break;
592                         case 't': {
593                                 static const struct suffix_mult mh_suffixes[] = {
594                                         { "m", 60 },
595                                         { "h", 60*60 },
596                                         /*{ "d", 24*60*60 },*/
597                                         { }
598                                 };
599                                 ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
600                                 if (ld->rotate_period) {
601                                         ld->next_rotate = now + ld->rotate_period;
602                                         if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
603                                                 nearest_rotate = ld->next_rotate;
604                                         tmaxflag = 1;
605                                 }
606                                 break;
607                         }
608                         case '!':
609                                 if (s[1]) {
610                                         free(ld->processor);
611                                         ld->processor = wstrdup(s);
612                                 }
613                                 break;
614                         }
615                         s = np;
616                 }
617                 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
618                 s = ld->inst;
619                 while (s) {
620                         np = strchr(s, '\n');
621                         if (np)
622                                 *np++ = '\0';
623                         s = np;
624                 }
625         }
626
627         /* open current */
628         i = stat("current", &st);
629         if (i != -1) {
630                 if (st.st_size && !(st.st_mode & S_IXUSR)) {
631                         ld->fnsave[25] = '.';
632                         ld->fnsave[26] = 'u';
633                         ld->fnsave[27] = '\0';
634                         do {
635                                 fmt_time_bernstein_25(ld->fnsave);
636                                 errno = 0;
637                                 stat(ld->fnsave, &st);
638                         } while (errno != ENOENT);
639                         while (rename("current", ld->fnsave) == -1)
640                                 pause2cannot("rename current", ld->name);
641                         rmoldest(ld);
642                         i = -1;
643                 } else {
644                         /* st.st_size can be not just bigger, but WIDER!
645                          * This code is safe: if st.st_size > 4GB, we select
646                          * ld->sizemax (because it's "unsigned") */
647                         ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
648                 }
649         } else {
650                 if (errno != ENOENT) {
651                         logdir_close(ld);
652                         warn2("can't stat current", ld->name);
653                         while (fchdir(fdwdir) == -1)
654                                 pause1cannot("change to initial working directory");
655                         return 0;
656                 }
657         }
658         while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
659                 pause2cannot("open current", ld->name);
660         /* we presume this cannot fail */
661         ld->filecur = fdopen(ld->fdcur, "a"); ////
662         setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
663
664         close_on_exec_on(ld->fdcur);
665         while (fchmod(ld->fdcur, 0644) == -1)
666                 pause2cannot("set mode of current", ld->name);
667
668         if (verbose) {
669                 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
670                 else bb_error_msg(INFO"new: %s/current", ld->name);
671         }
672
673         while (fchdir(fdwdir) == -1)
674                 pause1cannot("change to initial working directory");
675         return 1;
676 }
677
678 static void logdirs_reopen(void)
679 {
680         int l;
681         int ok = 0;
682
683         tmaxflag = 0;
684         for (l = 0; l < dirn; ++l) {
685                 logdir_close(&dir[l]);
686                 if (logdir_open(&dir[l], fndir[l]))
687                         ok = 1;
688         }
689         if (!ok)
690                 fatalx("no functional log directories");
691 }
692
693 /* Will look good in libbb one day */
694 static ssize_t ndelay_read(int fd, void *buf, size_t count)
695 {
696         if (!(fl_flag_0 & O_NONBLOCK))
697                 fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
698         count = safe_read(fd, buf, count);
699         if (!(fl_flag_0 & O_NONBLOCK))
700                 fcntl(fd, F_SETFL, fl_flag_0);
701         return count;
702 }
703
704 /* Used for reading stdin */
705 static int buffer_pread(/*int fd, */char *s, unsigned len)
706 {
707         unsigned now;
708         struct pollfd input;
709         int i;
710
711         input.fd = 0;
712         input.events = POLLIN;
713
714         do {
715                 if (rotateasap) {
716                         for (i = 0; i < dirn; ++i)
717                                 rotate(dir + i);
718                         rotateasap = 0;
719                 }
720                 if (exitasap) {
721                         if (linecomplete)
722                                 return 0;
723                         len = 1;
724                 }
725                 if (reopenasap) {
726                         logdirs_reopen();
727                         reopenasap = 0;
728                 }
729                 now = monotonic_sec();
730                 nearest_rotate = now + (45 * 60 + 45);
731                 for (i = 0; i < dirn; ++i) {
732                         if (dir[i].rotate_period) {
733                                 if (LESS(dir[i].next_rotate, now))
734                                         rotate(dir + i);
735                                 if (LESS(dir[i].next_rotate, nearest_rotate))
736                                         nearest_rotate = dir[i].next_rotate;
737                         }
738                 }
739
740                 sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
741                 i = nearest_rotate - now;
742                 if (i > 1000000)
743                         i = 1000000;
744                 if (i <= 0)
745                         i = 1;
746                 poll(&input, 1, i * 1000);
747                 sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
748
749                 i = ndelay_read(STDIN_FILENO, s, len);
750                 if (i >= 0)
751                         break;
752                 if (errno == EINTR)
753                         continue;
754                 if (errno != EAGAIN) {
755                         warn("can't read standard input");
756                         break;
757                 }
758                 /* else: EAGAIN - normal, repeat silently */
759         } while (!exitasap);
760
761         if (i > 0) {
762                 int cnt;
763                 linecomplete = (s[i-1] == '\n');
764                 if (!repl)
765                         return i;
766
767                 cnt = i;
768                 while (--cnt >= 0) {
769                         char ch = *s;
770                         if (ch != '\n') {
771                                 if (ch < 32 || ch > 126)
772                                         *s = repl;
773                                 else {
774                                         int j;
775                                         for (j = 0; replace[j]; ++j) {
776                                                 if (ch == replace[j]) {
777                                                         *s = repl;
778                                                         break;
779                                                 }
780                                         }
781                                 }
782                         }
783                         s++;
784                 }
785         }
786         return i;
787 }
788
789 static void sig_term_handler(int sig_no UNUSED_PARAM)
790 {
791         if (verbose)
792                 bb_error_msg(INFO"sig%s received", "term");
793         exitasap = 1;
794 }
795
796 static void sig_child_handler(int sig_no UNUSED_PARAM)
797 {
798         pid_t pid;
799         int l;
800
801         if (verbose)
802                 bb_error_msg(INFO"sig%s received", "child");
803         while ((pid = wait_any_nohang(&wstat)) > 0) {
804                 for (l = 0; l < dirn; ++l) {
805                         if (dir[l].ppid == pid) {
806                                 dir[l].ppid = 0;
807                                 processorstop(&dir[l]);
808                                 break;
809                         }
810                 }
811         }
812 }
813
814 static void sig_alarm_handler(int sig_no UNUSED_PARAM)
815 {
816         if (verbose)
817                 bb_error_msg(INFO"sig%s received", "alarm");
818         rotateasap = 1;
819 }
820
821 static void sig_hangup_handler(int sig_no UNUSED_PARAM)
822 {
823         if (verbose)
824                 bb_error_msg(INFO"sig%s received", "hangup");
825         reopenasap = 1;
826 }
827
828 static void logmatch(struct logdir *ld)
829 {
830         char *s;
831
832         ld->match = '+';
833         ld->matcherr = 'E';
834         s = ld->inst;
835         while (s && s[0]) {
836                 switch (s[0]) {
837                 case '+':
838                 case '-':
839                         if (pmatch(s+1, line, linelen))
840                                 ld->match = s[0];
841                         break;
842                 case 'e':
843                 case 'E':
844                         if (pmatch(s+1, line, linelen))
845                                 ld->matcherr = s[0];
846                         break;
847                 }
848                 s += strlen(s) + 1;
849         }
850 }
851
852 int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
853 int svlogd_main(int argc, char **argv)
854 {
855         char *r,*l,*b;
856         ssize_t stdin_cnt = 0;
857         int i;
858         unsigned opt;
859         unsigned timestamp = 0;
860         void* (*memRchr)(const void *, int, size_t) = memchr;
861
862         INIT_G();
863
864         opt_complementary = "tt:vv";
865         opt = getopt32(argv, "r:R:l:b:tv",
866                         &r, &replace, &l, &b, &timestamp, &verbose);
867         if (opt & 1) { // -r
868                 repl = r[0];
869                 if (!repl || r[1]) usage();
870         }
871         if (opt & 2) if (!repl) repl = '_'; // -R
872         if (opt & 4) { // -l
873                 linemax = xatou_range(l, 0, BUFSIZ-26);
874                 if (linemax == 0) linemax = BUFSIZ-26;
875                 if (linemax < 256) linemax = 256;
876         }
877         ////if (opt & 8) { // -b
878         ////    buflen = xatoi_u(b);
879         ////    if (buflen == 0) buflen = 1024;
880         ////}
881         //if (opt & 0x10) timestamp++; // -t
882         //if (opt & 0x20) verbose++; // -v
883         //if (timestamp > 2) timestamp = 2;
884         argv += optind;
885         argc -= optind;
886
887         dirn = argc;
888         if (dirn <= 0) usage();
889         ////if (buflen <= linemax) usage();
890         fdwdir = xopen(".", O_RDONLY|O_NDELAY);
891         close_on_exec_on(fdwdir);
892         dir = xzalloc(dirn * sizeof(struct logdir));
893         for (i = 0; i < dirn; ++i) {
894                 dir[i].fddir = -1;
895                 dir[i].fdcur = -1;
896                 ////dir[i].btmp = xmalloc(buflen);
897                 /*dir[i].ppid = 0;*/
898         }
899         /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
900         fndir = argv;
901         /* We cannot set NONBLOCK on fd #0 permanently - this setting
902          * _isn't_ per-process! It is shared among all other processes
903          * with the same stdin */
904         fl_flag_0 = fcntl(0, F_GETFL);
905
906         sigemptyset(&blocked_sigset);
907         sigaddset(&blocked_sigset, SIGTERM);
908         sigaddset(&blocked_sigset, SIGCHLD);
909         sigaddset(&blocked_sigset, SIGALRM);
910         sigaddset(&blocked_sigset, SIGHUP);
911         sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
912         bb_signals_recursive_norestart(1 << SIGTERM, sig_term_handler);
913         bb_signals_recursive_norestart(1 << SIGCHLD, sig_child_handler);
914         bb_signals_recursive_norestart(1 << SIGALRM, sig_alarm_handler);
915         bb_signals_recursive_norestart(1 << SIGHUP, sig_hangup_handler);
916
917         logdirs_reopen();
918
919         /* Without timestamps, we don't have to print each line
920          * separately, so we can look for _last_ newline, not first,
921          * thus batching writes */
922         if (!timestamp)
923                 memRchr = memrchr;
924
925         setvbuf(stderr, NULL, _IOFBF, linelen);
926
927         /* Each iteration processes one or more lines */
928         while (1) {
929                 char stamp[FMT_PTIME];
930                 char *lineptr;
931                 char *printptr;
932                 char *np;
933                 int printlen;
934                 char ch;
935
936                 lineptr = line;
937                 if (timestamp)
938                         lineptr += 26;
939
940                 /* lineptr[0..linemax-1] - buffer for stdin */
941                 /* (possibly has some unprocessed data from prev loop) */
942
943                 /* Refill the buffer if needed */
944                 np = memRchr(lineptr, '\n', stdin_cnt);
945                 if (!np && !exitasap) {
946                         i = linemax - stdin_cnt; /* avail. bytes at tail */
947                         if (i >= 128) {
948                                 i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
949                                 if (i <= 0) /* EOF or error on stdin */
950                                         exitasap = 1;
951                                 else {
952                                         np = memRchr(lineptr + stdin_cnt, '\n', i);
953                                         stdin_cnt += i;
954                                 }
955                         }
956                 }
957                 if (stdin_cnt <= 0 && exitasap)
958                         break;
959
960                 /* Search for '\n' (in fact, np already holds the result) */
961                 linelen = stdin_cnt;
962                 if (np) {
963  print_to_nl:           /* NB: starting from here lineptr may point
964                          * farther out into line[] */
965                         linelen = np - lineptr + 1;
966                 }
967                 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
968                 ch = lineptr[linelen-1];
969
970                 /* Biggest performance hit was coming from the fact
971                  * that we did not buffer writes. We were reading many lines
972                  * in one read() above, but wrote one line per write().
973                  * We are using stdio to fix that */
974
975                 /* write out lineptr[0..linelen-1] to each log destination
976                  * (or lineptr[-26..linelen-1] if timestamping) */
977                 printlen = linelen;
978                 printptr = lineptr;
979                 if (timestamp) {
980                         if (timestamp == 1)
981                                 fmt_time_bernstein_25(stamp);
982                         else /* 2: */
983                                 fmt_time_human_30nul(stamp);
984                         printlen += 26;
985                         printptr -= 26;
986                         memcpy(printptr, stamp, 25);
987                         printptr[25] = ' ';
988                 }
989                 for (i = 0; i < dirn; ++i) {
990                         struct logdir *ld = &dir[i];
991                         if (ld->fddir == -1) continue;
992                         if (ld->inst)
993                                 logmatch(ld);
994                         if (ld->matcherr == 'e') {
995                                 /* runit-1.8.0 compat: if timestamping, do it on stderr too */
996                                 ////full_write(STDERR_FILENO, printptr, printlen);
997                                 fwrite(printptr, 1, printlen, stderr);
998                         }
999                         if (ld->match != '+') continue;
1000                         buffer_pwrite(i, printptr, printlen);
1001                 }
1002
1003                 /* If we didn't see '\n' (long input line), */
1004                 /* read/write repeatedly until we see it */
1005                 while (ch != '\n') {
1006                         /* lineptr is emptied now, safe to use as buffer */
1007                         stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
1008                         if (stdin_cnt <= 0) { /* EOF or error on stdin */
1009                                 exitasap = 1;
1010                                 lineptr[0] = ch = '\n';
1011                                 linelen = 1;
1012                                 stdin_cnt = 1;
1013                         } else {
1014                                 linelen = stdin_cnt;
1015                                 np = memRchr(lineptr, '\n', stdin_cnt);
1016                                 if (np)
1017                                         linelen = np - lineptr + 1;
1018                                 ch = lineptr[linelen-1];
1019                         }
1020                         /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1021                         for (i = 0; i < dirn; ++i) {
1022                                 if (dir[i].fddir == -1) continue;
1023                                 if (dir[i].matcherr == 'e') {
1024                                         ////full_write(STDERR_FILENO, lineptr, linelen);
1025                                         fwrite(lineptr, 1, linelen, stderr);
1026                                 }
1027                                 if (dir[i].match != '+') continue;
1028                                 buffer_pwrite(i, lineptr, linelen);
1029                         }
1030                 }
1031
1032                 stdin_cnt -= linelen;
1033                 if (stdin_cnt > 0) {
1034                         lineptr += linelen;
1035                         /* If we see another '\n', we don't need to read
1036                          * next piece of input: can print what we have */
1037                         np = memRchr(lineptr, '\n', stdin_cnt);
1038                         if (np)
1039                                 goto print_to_nl;
1040                         /* Move unprocessed data to the front of line */
1041                         memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1042                 }
1043                 fflush(NULL);////
1044         }
1045
1046         for (i = 0; i < dirn; ++i) {
1047                 if (dir[i].ppid)
1048                         while (!processorstop(&dir[i]))
1049                                 /* repeat */;
1050                 logdir_close(&dir[i]);
1051         }
1052         return 0;
1053 }