*: a bit of code shrink
[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                 char *prog[4];
227                 int fd;
228
229                 /* child */
230                 /* Non-ignored signals revert to SIG_DFL on exec anyway */
231                 /*bb_signals(0
232                         + (1 << SIGTERM)
233                         + (1 << SIGALRM)
234                         + (1 << SIGHUP)
235                         , SIG_DFL);*/
236                 sig_unblock(SIGTERM);
237                 sig_unblock(SIGALRM);
238                 sig_unblock(SIGHUP);
239
240                 if (verbose)
241                         bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
242                 fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
243                 xmove_fd(fd, 0);
244                 ld->fnsave[26] = 't'; /* <- that's why we need sv_ch! */
245                 fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
246                 xmove_fd(fd, 1);
247                 fd = open_read("state");
248                 if (fd == -1) {
249                         if (errno != ENOENT)
250                                 bb_perror_msg_and_die(FATAL"can't %s processor %s", "open state for", ld->name);
251                         close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
252                         fd = xopen("state", O_RDONLY|O_NDELAY);
253                 }
254                 xmove_fd(fd, 4);
255                 fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
256                 xmove_fd(fd, 5);
257
258 // getenv("SHELL")?
259                 prog[0] = (char*)"sh";
260                 prog[1] = (char*)"-c";
261                 prog[2] = ld->processor;
262                 prog[3] = NULL;
263                 execv("/bin/sh", prog);
264                 bb_perror_msg_and_die(FATAL"can't %s processor %s", "run", ld->name);
265         }
266         ld->fnsave[26] = sv_ch; /* ...restore */
267         ld->ppid = pid;
268 }
269
270 static unsigned processorstop(struct logdir *ld)
271 {
272         char f[28];
273
274         if (ld->ppid) {
275                 sig_unblock(SIGHUP);
276                 while (safe_waitpid(ld->ppid, &wstat, 0) == -1)
277                         pause2cannot("wait for processor", ld->name);
278                 sig_block(SIGHUP);
279                 ld->ppid = 0;
280         }
281         if (ld->fddir == -1) return 1;
282         while (fchdir(ld->fddir) == -1)
283                 pause2cannot("change directory, want processor", ld->name);
284         if (wait_exitcode(wstat) != 0) {
285                 warnx("processor failed, restart", ld->name);
286                 ld->fnsave[26] = 't';
287                 unlink(ld->fnsave);
288                 ld->fnsave[26] = 'u';
289                 processorstart(ld);
290                 while (fchdir(fdwdir) == -1)
291                         pause1cannot("change to initial working directory");
292                 return ld->processor ? 0 : 1;
293         }
294         ld->fnsave[26] = 't';
295         memcpy(f, ld->fnsave, 26);
296         f[26] = 's';
297         f[27] = '\0';
298         while (rename(ld->fnsave, f) == -1)
299                 pause2cannot("rename processed", ld->name);
300         while (chmod(f, 0744) == -1)
301                 pause2cannot("set mode of processed", ld->name);
302         ld->fnsave[26] = 'u';
303         if (unlink(ld->fnsave) == -1)
304                 bb_error_msg(WARNING"can't unlink: %s/%s", ld->name, ld->fnsave);
305         while (rename("newstate", "state") == -1)
306                 pause2cannot("rename state", ld->name);
307         if (verbose)
308                 bb_error_msg(INFO"processed: %s/%s", ld->name, f);
309         while (fchdir(fdwdir) == -1)
310                 pause1cannot("change to initial working directory");
311         return 1;
312 }
313
314 static void rmoldest(struct logdir *ld)
315 {
316         DIR *d;
317         struct dirent *f;
318         char oldest[FMT_PTIME];
319         int n = 0;
320
321         oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
322         while (!(d = opendir(".")))
323                 pause2cannot("open directory, want rotate", ld->name);
324         errno = 0;
325         while ((f = readdir(d))) {
326                 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
327                         if (f->d_name[26] == 't') {
328                                 if (unlink(f->d_name) == -1)
329                                         warn2("can't unlink processor leftover", f->d_name);
330                         } else {
331                                 ++n;
332                                 if (strcmp(f->d_name, oldest) < 0)
333                                         memcpy(oldest, f->d_name, 27);
334                         }
335                         errno = 0;
336                 }
337         }
338         if (errno)
339                 warn2("can't read directory", ld->name);
340         closedir(d);
341
342         if (ld->nmax && (n > ld->nmax)) {
343                 if (verbose)
344                         bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
345                 if ((*oldest == '@') && (unlink(oldest) == -1))
346                         warn2("can't unlink oldest logfile", ld->name);
347         }
348 }
349
350 static unsigned rotate(struct logdir *ld)
351 {
352         struct stat st;
353         unsigned now;
354
355         if (ld->fddir == -1) {
356                 ld->rotate_period = 0;
357                 return 0;
358         }
359         if (ld->ppid)
360                 while (!processorstop(ld))
361                         continue;
362
363         while (fchdir(ld->fddir) == -1)
364                 pause2cannot("change directory, want rotate", ld->name);
365
366         /* create new filename */
367         ld->fnsave[25] = '.';
368         ld->fnsave[26] = 's';
369         if (ld->processor)
370                 ld->fnsave[26] = 'u';
371         ld->fnsave[27] = '\0';
372         do {
373                 fmt_time_bernstein_25(ld->fnsave);
374                 errno = 0;
375                 stat(ld->fnsave, &st);
376         } while (errno != ENOENT);
377
378         now = monotonic_sec();
379         if (ld->rotate_period && LESS(ld->next_rotate, now)) {
380                 ld->next_rotate = now + ld->rotate_period;
381                 if (LESS(ld->next_rotate, nearest_rotate))
382                         nearest_rotate = ld->next_rotate;
383         }
384
385         if (ld->size > 0) {
386                 while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
387                         pause2cannot("fsync current logfile", ld->name);
388                 while (fchmod(ld->fdcur, 0744) == -1)
389                         pause2cannot("set mode of current", ld->name);
390                 ////close(ld->fdcur);
391                 fclose(ld->filecur);
392
393                 if (verbose) {
394                         bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
395                                         ld->fnsave, ld->size);
396                 }
397                 while (rename("current", ld->fnsave) == -1)
398                         pause2cannot("rename current", ld->name);
399                 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
400                         pause2cannot("create new current", ld->name);
401                 /* we presume this cannot fail */
402                 ld->filecur = fdopen(ld->fdcur, "a"); ////
403                 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
404                 close_on_exec_on(ld->fdcur);
405                 ld->size = 0;
406                 while (fchmod(ld->fdcur, 0644) == -1)
407                         pause2cannot("set mode of current", ld->name);
408                 rmoldest(ld);
409                 processorstart(ld);
410         }
411
412         while (fchdir(fdwdir) == -1)
413                 pause1cannot("change to initial working directory");
414         return 1;
415 }
416
417 static int buffer_pwrite(int n, char *s, unsigned len)
418 {
419         int i;
420         struct logdir *ld = &dir[n];
421
422         if (ld->sizemax) {
423                 if (ld->size >= ld->sizemax)
424                         rotate(ld);
425                 if (len > (ld->sizemax - ld->size))
426                         len = ld->sizemax - ld->size;
427         }
428         while (1) {
429                 ////i = full_write(ld->fdcur, s, len);
430                 ////if (i != -1) break;
431                 i = fwrite(s, 1, len, ld->filecur);
432                 if (i == len) break;
433
434                 if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
435                         DIR *d;
436                         struct dirent *f;
437                         char oldest[FMT_PTIME];
438                         int j = 0;
439
440                         while (fchdir(ld->fddir) == -1)
441                                 pause2cannot("change directory, want remove old logfile",
442                                                          ld->name);
443                         oldest[0] = 'A';
444                         oldest[1] = oldest[27] = '\0';
445                         while (!(d = opendir(".")))
446                                 pause2cannot("open directory, want remove old logfile",
447                                                          ld->name);
448                         errno = 0;
449                         while ((f = readdir(d)))
450                                 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
451                                         ++j;
452                                         if (strcmp(f->d_name, oldest) < 0)
453                                                 memcpy(oldest, f->d_name, 27);
454                                 }
455                         if (errno) warn2("can't read directory, want remove old logfile",
456                                         ld->name);
457                         closedir(d);
458                         errno = ENOSPC;
459                         if (j > ld->nmin) {
460                                 if (*oldest == '@') {
461                                         bb_error_msg(WARNING"out of disk space, delete: %s/%s",
462                                                         ld->name, oldest);
463                                         errno = 0;
464                                         if (unlink(oldest) == -1) {
465                                                 warn2("can't unlink oldest logfile", ld->name);
466                                                 errno = ENOSPC;
467                                         }
468                                         while (fchdir(fdwdir) == -1)
469                                                 pause1cannot("change to initial working directory");
470                                 }
471                         }
472                 }
473                 if (errno)
474                         pause2cannot("write to current", ld->name);
475         }
476
477         ld->size += i;
478         if (ld->sizemax)
479                 if (s[i-1] == '\n')
480                         if (ld->size >= (ld->sizemax - linemax))
481                                 rotate(ld);
482         return i;
483 }
484
485 static void logdir_close(struct logdir *ld)
486 {
487         if (ld->fddir == -1)
488                 return;
489         if (verbose)
490                 bb_error_msg(INFO"close: %s", ld->name);
491         close(ld->fddir);
492         ld->fddir = -1;
493         if (ld->fdcur == -1)
494                 return; /* impossible */
495         while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
496                 pause2cannot("fsync current logfile", ld->name);
497         while (fchmod(ld->fdcur, 0744) == -1)
498                 pause2cannot("set mode of current", ld->name);
499         ////close(ld->fdcur);
500         fclose(ld->filecur);
501         ld->fdcur = -1;
502         if (ld->fdlock == -1)
503                 return; /* impossible */
504         close(ld->fdlock);
505         ld->fdlock = -1;
506         free(ld->processor);
507         ld->processor = NULL;
508 }
509
510 static unsigned logdir_open(struct logdir *ld, const char *fn)
511 {
512         char buf[128];
513         unsigned now;
514         char *new, *s, *np;
515         int i;
516         struct stat st;
517
518         now = monotonic_sec();
519
520         ld->fddir = open(fn, O_RDONLY|O_NDELAY);
521         if (ld->fddir == -1) {
522                 warn2("can't open log directory", (char*)fn);
523                 return 0;
524         }
525         close_on_exec_on(ld->fddir);
526         if (fchdir(ld->fddir) == -1) {
527                 logdir_close(ld);
528                 warn2("can't change directory", (char*)fn);
529                 return 0;
530         }
531         ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
532         if ((ld->fdlock == -1)
533          || (lock_exnb(ld->fdlock) == -1)
534         ) {
535                 logdir_close(ld);
536                 warn2("can't lock directory", (char*)fn);
537                 while (fchdir(fdwdir) == -1)
538                         pause1cannot("change to initial working directory");
539                 return 0;
540         }
541         close_on_exec_on(ld->fdlock);
542
543         ld->size = 0;
544         ld->sizemax = 1000000;
545         ld->nmax = ld->nmin = 10;
546         ld->rotate_period = 0;
547         ld->name = (char*)fn;
548         ld->ppid = 0;
549         ld->match = '+';
550         free(ld->inst); ld->inst = NULL;
551         free(ld->processor); ld->processor = NULL;
552
553         /* read config */
554         i = open_read_close("config", buf, sizeof(buf));
555         if (i < 0 && errno != ENOENT)
556                 bb_perror_msg(WARNING"%s/config", ld->name);
557         if (i > 0) {
558                 if (verbose)
559                         bb_error_msg(INFO"read: %s/config", ld->name);
560                 s = buf;
561                 while (s) {
562                         np = strchr(s, '\n');
563                         if (np)
564                                 *np++ = '\0';
565                         switch (s[0]) {
566                         case '+':
567                         case '-':
568                         case 'e':
569                         case 'E':
570                                 /* Add '\n'-terminated line to ld->inst */
571                                 while (1) {
572                                         int l = asprintf(&new, "%s%s\n", ld->inst ? : "", s);
573                                         if (l >= 0 && new)
574                                                 break;
575                                         pause_nomem();
576                                 }
577                                 free(ld->inst);
578                                 ld->inst = new;
579                                 break;
580                         case 's': {
581                                 static const struct suffix_mult km_suffixes[] = {
582                                         { "k", 1024 },
583                                         { "m", 1024*1024 },
584                                         { }
585                                 };
586                                 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
587                                 break;
588                         }
589                         case 'n':
590                                 ld->nmax = xatoi_u(&s[1]);
591                                 break;
592                         case 'N':
593                                 ld->nmin = xatoi_u(&s[1]);
594                                 break;
595                         case 't': {
596                                 static const struct suffix_mult mh_suffixes[] = {
597                                         { "m", 60 },
598                                         { "h", 60*60 },
599                                         /*{ "d", 24*60*60 },*/
600                                         { }
601                                 };
602                                 ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
603                                 if (ld->rotate_period) {
604                                         ld->next_rotate = now + ld->rotate_period;
605                                         if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
606                                                 nearest_rotate = ld->next_rotate;
607                                         tmaxflag = 1;
608                                 }
609                                 break;
610                         }
611                         case '!':
612                                 if (s[1]) {
613                                         free(ld->processor);
614                                         ld->processor = wstrdup(s);
615                                 }
616                                 break;
617                         }
618                         s = np;
619                 }
620                 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
621                 s = ld->inst;
622                 while (s) {
623                         np = strchr(s, '\n');
624                         if (np)
625                                 *np++ = '\0';
626                         s = np;
627                 }
628         }
629
630         /* open current */
631         i = stat("current", &st);
632         if (i != -1) {
633                 if (st.st_size && !(st.st_mode & S_IXUSR)) {
634                         ld->fnsave[25] = '.';
635                         ld->fnsave[26] = 'u';
636                         ld->fnsave[27] = '\0';
637                         do {
638                                 fmt_time_bernstein_25(ld->fnsave);
639                                 errno = 0;
640                                 stat(ld->fnsave, &st);
641                         } while (errno != ENOENT);
642                         while (rename("current", ld->fnsave) == -1)
643                                 pause2cannot("rename current", ld->name);
644                         rmoldest(ld);
645                         i = -1;
646                 } else {
647                         /* st.st_size can be not just bigger, but WIDER!
648                          * This code is safe: if st.st_size > 4GB, we select
649                          * ld->sizemax (because it's "unsigned") */
650                         ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
651                 }
652         } else {
653                 if (errno != ENOENT) {
654                         logdir_close(ld);
655                         warn2("can't stat current", ld->name);
656                         while (fchdir(fdwdir) == -1)
657                                 pause1cannot("change to initial working directory");
658                         return 0;
659                 }
660         }
661         while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
662                 pause2cannot("open current", ld->name);
663         /* we presume this cannot fail */
664         ld->filecur = fdopen(ld->fdcur, "a"); ////
665         setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
666
667         close_on_exec_on(ld->fdcur);
668         while (fchmod(ld->fdcur, 0644) == -1)
669                 pause2cannot("set mode of current", ld->name);
670
671         if (verbose) {
672                 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
673                 else bb_error_msg(INFO"new: %s/current", ld->name);
674         }
675
676         while (fchdir(fdwdir) == -1)
677                 pause1cannot("change to initial working directory");
678         return 1;
679 }
680
681 static void logdirs_reopen(void)
682 {
683         int l;
684         int ok = 0;
685
686         tmaxflag = 0;
687         for (l = 0; l < dirn; ++l) {
688                 logdir_close(&dir[l]);
689                 if (logdir_open(&dir[l], fndir[l]))
690                         ok = 1;
691         }
692         if (!ok)
693                 fatalx("no functional log directories");
694 }
695
696 /* Will look good in libbb one day */
697 static ssize_t ndelay_read(int fd, void *buf, size_t count)
698 {
699         if (!(fl_flag_0 & O_NONBLOCK))
700                 fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
701         count = safe_read(fd, buf, count);
702         if (!(fl_flag_0 & O_NONBLOCK))
703                 fcntl(fd, F_SETFL, fl_flag_0);
704         return count;
705 }
706
707 /* Used for reading stdin */
708 static int buffer_pread(/*int fd, */char *s, unsigned len)
709 {
710         unsigned now;
711         struct pollfd input;
712         int i;
713
714         input.fd = 0;
715         input.events = POLLIN;
716
717         do {
718                 if (rotateasap) {
719                         for (i = 0; i < dirn; ++i)
720                                 rotate(dir + i);
721                         rotateasap = 0;
722                 }
723                 if (exitasap) {
724                         if (linecomplete)
725                                 return 0;
726                         len = 1;
727                 }
728                 if (reopenasap) {
729                         logdirs_reopen();
730                         reopenasap = 0;
731                 }
732                 now = monotonic_sec();
733                 nearest_rotate = now + (45 * 60 + 45);
734                 for (i = 0; i < dirn; ++i) {
735                         if (dir[i].rotate_period) {
736                                 if (LESS(dir[i].next_rotate, now))
737                                         rotate(dir + i);
738                                 if (LESS(dir[i].next_rotate, nearest_rotate))
739                                         nearest_rotate = dir[i].next_rotate;
740                         }
741                 }
742
743                 sigprocmask(SIG_UNBLOCK, &blocked_sigset, NULL);
744                 i = nearest_rotate - now;
745                 if (i > 1000000)
746                         i = 1000000;
747                 if (i <= 0)
748                         i = 1;
749                 poll(&input, 1, i * 1000);
750                 sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
751
752                 i = ndelay_read(STDIN_FILENO, s, len);
753                 if (i >= 0)
754                         break;
755                 if (errno == EINTR)
756                         continue;
757                 if (errno != EAGAIN) {
758                         warn("can't read standard input");
759                         break;
760                 }
761                 /* else: EAGAIN - normal, repeat silently */
762         } while (!exitasap);
763
764         if (i > 0) {
765                 int cnt;
766                 linecomplete = (s[i-1] == '\n');
767                 if (!repl)
768                         return i;
769
770                 cnt = i;
771                 while (--cnt >= 0) {
772                         char ch = *s;
773                         if (ch != '\n') {
774                                 if (ch < 32 || ch > 126)
775                                         *s = repl;
776                                 else {
777                                         int j;
778                                         for (j = 0; replace[j]; ++j) {
779                                                 if (ch == replace[j]) {
780                                                         *s = repl;
781                                                         break;
782                                                 }
783                                         }
784                                 }
785                         }
786                         s++;
787                 }
788         }
789         return i;
790 }
791
792 static void sig_term_handler(int sig_no UNUSED_PARAM)
793 {
794         if (verbose)
795                 bb_error_msg(INFO"sig%s received", "term");
796         exitasap = 1;
797 }
798
799 static void sig_child_handler(int sig_no UNUSED_PARAM)
800 {
801         pid_t pid;
802         int l;
803
804         if (verbose)
805                 bb_error_msg(INFO"sig%s received", "child");
806         while ((pid = wait_any_nohang(&wstat)) > 0) {
807                 for (l = 0; l < dirn; ++l) {
808                         if (dir[l].ppid == pid) {
809                                 dir[l].ppid = 0;
810                                 processorstop(&dir[l]);
811                                 break;
812                         }
813                 }
814         }
815 }
816
817 static void sig_alarm_handler(int sig_no UNUSED_PARAM)
818 {
819         if (verbose)
820                 bb_error_msg(INFO"sig%s received", "alarm");
821         rotateasap = 1;
822 }
823
824 static void sig_hangup_handler(int sig_no UNUSED_PARAM)
825 {
826         if (verbose)
827                 bb_error_msg(INFO"sig%s received", "hangup");
828         reopenasap = 1;
829 }
830
831 static void logmatch(struct logdir *ld)
832 {
833         char *s;
834
835         ld->match = '+';
836         ld->matcherr = 'E';
837         s = ld->inst;
838         while (s && s[0]) {
839                 switch (s[0]) {
840                 case '+':
841                 case '-':
842                         if (pmatch(s+1, line, linelen))
843                                 ld->match = s[0];
844                         break;
845                 case 'e':
846                 case 'E':
847                         if (pmatch(s+1, line, linelen))
848                                 ld->matcherr = s[0];
849                         break;
850                 }
851                 s += strlen(s) + 1;
852         }
853 }
854
855 int svlogd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
856 int svlogd_main(int argc, char **argv)
857 {
858         char *r,*l,*b;
859         ssize_t stdin_cnt = 0;
860         int i;
861         unsigned opt;
862         unsigned timestamp = 0;
863         void* (*memRchr)(const void *, int, size_t) = memchr;
864
865         INIT_G();
866
867         opt_complementary = "tt:vv";
868         opt = getopt32(argv, "r:R:l:b:tv",
869                         &r, &replace, &l, &b, &timestamp, &verbose);
870         if (opt & 1) { // -r
871                 repl = r[0];
872                 if (!repl || r[1]) usage();
873         }
874         if (opt & 2) if (!repl) repl = '_'; // -R
875         if (opt & 4) { // -l
876                 linemax = xatou_range(l, 0, BUFSIZ-26);
877                 if (linemax == 0) linemax = BUFSIZ-26;
878                 if (linemax < 256) linemax = 256;
879         }
880         ////if (opt & 8) { // -b
881         ////    buflen = xatoi_u(b);
882         ////    if (buflen == 0) buflen = 1024;
883         ////}
884         //if (opt & 0x10) timestamp++; // -t
885         //if (opt & 0x20) verbose++; // -v
886         //if (timestamp > 2) timestamp = 2;
887         argv += optind;
888         argc -= optind;
889
890         dirn = argc;
891         if (dirn <= 0) usage();
892         ////if (buflen <= linemax) usage();
893         fdwdir = xopen(".", O_RDONLY|O_NDELAY);
894         close_on_exec_on(fdwdir);
895         dir = xzalloc(dirn * sizeof(struct logdir));
896         for (i = 0; i < dirn; ++i) {
897                 dir[i].fddir = -1;
898                 dir[i].fdcur = -1;
899                 ////dir[i].btmp = xmalloc(buflen);
900                 /*dir[i].ppid = 0;*/
901         }
902         /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
903         fndir = argv;
904         /* We cannot set NONBLOCK on fd #0 permanently - this setting
905          * _isn't_ per-process! It is shared among all other processes
906          * with the same stdin */
907         fl_flag_0 = fcntl(0, F_GETFL);
908
909         sigemptyset(&blocked_sigset);
910         sigaddset(&blocked_sigset, SIGTERM);
911         sigaddset(&blocked_sigset, SIGCHLD);
912         sigaddset(&blocked_sigset, SIGALRM);
913         sigaddset(&blocked_sigset, SIGHUP);
914         sigprocmask(SIG_BLOCK, &blocked_sigset, NULL);
915         bb_signals_recursive(1 << SIGTERM, sig_term_handler);
916         bb_signals_recursive(1 << SIGCHLD, sig_child_handler);
917         bb_signals_recursive(1 << SIGALRM, sig_alarm_handler);
918         bb_signals_recursive(1 << SIGHUP, sig_hangup_handler);
919
920         logdirs_reopen();
921
922         /* Without timestamps, we don't have to print each line
923          * separately, so we can look for _last_ newline, not first,
924          * thus batching writes */
925         if (!timestamp)
926                 memRchr = memrchr;
927
928         setvbuf(stderr, NULL, _IOFBF, linelen);
929
930         /* Each iteration processes one or more lines */
931         while (1) {
932                 char stamp[FMT_PTIME];
933                 char *lineptr;
934                 char *printptr;
935                 char *np;
936                 int printlen;
937                 char ch;
938
939                 lineptr = line;
940                 if (timestamp)
941                         lineptr += 26;
942
943                 /* lineptr[0..linemax-1] - buffer for stdin */
944                 /* (possibly has some unprocessed data from prev loop) */
945
946                 /* Refill the buffer if needed */
947                 np = memRchr(lineptr, '\n', stdin_cnt);
948                 if (!np && !exitasap) {
949                         i = linemax - stdin_cnt; /* avail. bytes at tail */
950                         if (i >= 128) {
951                                 i = buffer_pread(/*0, */lineptr + stdin_cnt, i);
952                                 if (i <= 0) /* EOF or error on stdin */
953                                         exitasap = 1;
954                                 else {
955                                         np = memRchr(lineptr + stdin_cnt, '\n', i);
956                                         stdin_cnt += i;
957                                 }
958                         }
959                 }
960                 if (stdin_cnt <= 0 && exitasap)
961                         break;
962
963                 /* Search for '\n' (in fact, np already holds the result) */
964                 linelen = stdin_cnt;
965                 if (np) {
966  print_to_nl:           /* NB: starting from here lineptr may point
967                          * farther out into line[] */
968                         linelen = np - lineptr + 1;
969                 }
970                 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
971                 ch = lineptr[linelen-1];
972
973                 /* Biggest performance hit was coming from the fact
974                  * that we did not buffer writes. We were reading many lines
975                  * in one read() above, but wrote one line per write().
976                  * We are using stdio to fix that */
977
978                 /* write out lineptr[0..linelen-1] to each log destination
979                  * (or lineptr[-26..linelen-1] if timestamping) */
980                 printlen = linelen;
981                 printptr = lineptr;
982                 if (timestamp) {
983                         if (timestamp == 1)
984                                 fmt_time_bernstein_25(stamp);
985                         else /* 2: */
986                                 fmt_time_human_30nul(stamp);
987                         printlen += 26;
988                         printptr -= 26;
989                         memcpy(printptr, stamp, 25);
990                         printptr[25] = ' ';
991                 }
992                 for (i = 0; i < dirn; ++i) {
993                         struct logdir *ld = &dir[i];
994                         if (ld->fddir == -1) continue;
995                         if (ld->inst)
996                                 logmatch(ld);
997                         if (ld->matcherr == 'e') {
998                                 /* runit-1.8.0 compat: if timestamping, do it on stderr too */
999                                 ////full_write(STDERR_FILENO, printptr, printlen);
1000                                 fwrite(printptr, 1, printlen, stderr);
1001                         }
1002                         if (ld->match != '+') continue;
1003                         buffer_pwrite(i, printptr, printlen);
1004                 }
1005
1006                 /* If we didn't see '\n' (long input line), */
1007                 /* read/write repeatedly until we see it */
1008                 while (ch != '\n') {
1009                         /* lineptr is emptied now, safe to use as buffer */
1010                         stdin_cnt = exitasap ? -1 : buffer_pread(/*0, */lineptr, linemax);
1011                         if (stdin_cnt <= 0) { /* EOF or error on stdin */
1012                                 exitasap = 1;
1013                                 lineptr[0] = ch = '\n';
1014                                 linelen = 1;
1015                                 stdin_cnt = 1;
1016                         } else {
1017                                 linelen = stdin_cnt;
1018                                 np = memRchr(lineptr, '\n', stdin_cnt);
1019                                 if (np)
1020                                         linelen = np - lineptr + 1;
1021                                 ch = lineptr[linelen-1];
1022                         }
1023                         /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
1024                         for (i = 0; i < dirn; ++i) {
1025                                 if (dir[i].fddir == -1) continue;
1026                                 if (dir[i].matcherr == 'e') {
1027                                         ////full_write(STDERR_FILENO, lineptr, linelen);
1028                                         fwrite(lineptr, 1, linelen, stderr);
1029                                 }
1030                                 if (dir[i].match != '+') continue;
1031                                 buffer_pwrite(i, lineptr, linelen);
1032                         }
1033                 }
1034
1035                 stdin_cnt -= linelen;
1036                 if (stdin_cnt > 0) {
1037                         lineptr += linelen;
1038                         /* If we see another '\n', we don't need to read
1039                          * next piece of input: can print what we have */
1040                         np = memRchr(lineptr, '\n', stdin_cnt);
1041                         if (np)
1042                                 goto print_to_nl;
1043                         /* Move unprocessed data to the front of line */
1044                         memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1045                 }
1046                 fflush(NULL);////
1047         }
1048
1049         for (i = 0; i < dirn; ++i) {
1050                 if (dir[i].ppid)
1051                         while (!processorstop(&dir[i]))
1052                                 /* repeat */;
1053                 logdir_close(&dir[i]);
1054         }
1055         return 0;
1056 }