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