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