2 Copyright (c) 2001-2006, Gerrit Pape
5 Redistribution and use in source and binary forms, with or without
6 modification, are permitted provided that the following conditions are met:
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.
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.
28 /* Busyboxed by Denis Vlasenko <vda.linux@googlemail.com> */
29 /* TODO: depends on runit_lib.c - review and reduce/eliminate */
34 #include "runit_lib.h"
36 #define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
40 static unsigned verbose;
41 static int linemax = 1000;
42 ////static int buflen = 1024;
48 static unsigned nearest_rotate;
51 static smallint exitasap;
52 static smallint rotateasap;
53 static smallint reopenasap;
54 static smallint linecomplete = 1;
56 static smallint tmaxflag;
59 static const char *replace = "";
61 static sigset_t *blocked_sigset;
64 static struct logdir {
66 /* pattern list to match, in "aa\0bb\0\cc\0\0" form */
74 unsigned rotate_period;
81 char fnsave[FMT_PTIME];
87 #define FATAL "fatal: "
88 #define WARNING "warning: "
89 #define PAUSE "pausing: "
92 #define usage() bb_show_usage()
93 static void fatalx(const char *m0)
95 bb_error_msg_and_die(FATAL"%s", m0);
97 static void warn(const char *m0)
99 bb_perror_msg(WARNING"%s", m0);
101 static void warn2(const char *m0, const char *m1)
103 bb_perror_msg(WARNING"%s: %s", m0, m1);
105 static void warnx(const char *m0, const char *m1)
107 bb_error_msg(WARNING"%s: %s", m0, m1);
109 static void pause_nomem(void)
111 bb_error_msg(PAUSE"out of memory");
114 static void pause1cannot(const char *m0)
116 bb_perror_msg(PAUSE"cannot %s", m0);
119 static void pause2cannot(const char *m0, const char *m1)
121 bb_perror_msg(PAUSE"cannot %s %s", m0, m1);
125 static char* wstrdup(const char *str)
128 while (!(s = strdup(str)))
133 /*** ex fmt_ptime.[ch] ***/
136 static void fmt_time_human_30nul(char *s)
141 gettimeofday(&tv, NULL);
142 t = gmtime(&(tv.tv_sec));
143 sprintf(s, "%04u-%02u-%02u_%02u:%02u:%02u.%06u000",
144 (unsigned)(1900 + t->tm_year),
145 (unsigned)(t->tm_mon + 1),
146 (unsigned)(t->tm_mday),
147 (unsigned)(t->tm_hour),
148 (unsigned)(t->tm_min),
149 (unsigned)(t->tm_sec),
150 (unsigned)(tv.tv_usec)
152 /* 4+1 + 2+1 + 2+1 + 2+1 + 2+1 + 2+1 + 9 = */
153 /* 5 + 3 + 3 + 3 + 3 + 3 + 9 = */
154 /* 20 (up to '.' inclusive) + 9 (not including '\0') */
157 /* NOT terminated! */
158 static void fmt_time_bernstein_25(char *s)
164 gettimeofday(&tv, NULL);
165 sec_hi = (0x400000000000000aULL + tv.tv_sec) >> 32;
166 tv.tv_sec = (time_t)(0x400000000000000aULL) + tv.tv_sec;
168 /* Network order is big-endian: most significant byte first.
169 * This is exactly what we want here */
170 pack[0] = htonl(sec_hi);
171 pack[1] = htonl(tv.tv_sec);
172 pack[2] = htonl(tv.tv_usec);
174 bin2hex(s, (char*)pack, 12);
177 static unsigned processorstart(struct logdir *ld)
181 if (!ld->processor) return 0;
183 warnx("processor already running", ld->name);
186 while ((pid = fork()) == -1)
187 pause2cannot("fork for processor", ld->name);
193 signal(SIGTERM, SIG_DFL);
194 signal(SIGALRM, SIG_DFL);
195 signal(SIGHUP, SIG_DFL);
196 sig_unblock(SIGTERM);
197 sig_unblock(SIGALRM);
201 bb_error_msg(INFO"processing: %s/%s", ld->name, ld->fnsave);
202 fd = xopen(ld->fnsave, O_RDONLY|O_NDELAY);
204 ld->fnsave[26] = 't';
205 fd = xopen(ld->fnsave, O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
207 fd = open_read("state");
210 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "open state for", ld->name);
211 close(xopen("state", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT));
212 fd = xopen("state", O_RDONLY|O_NDELAY);
215 fd = xopen("newstate", O_WRONLY|O_NDELAY|O_TRUNC|O_CREAT);
219 prog[0] = (char*)"sh";
220 prog[1] = (char*)"-c";
221 prog[2] = ld->processor;
223 execve("/bin/sh", prog, environ);
224 bb_perror_msg_and_die(FATAL"cannot %s processor %s", "run", ld->name);
230 static unsigned processorstop(struct logdir *ld)
236 while (wait_pid(&wstat, ld->ppid) == -1)
237 pause2cannot("wait for processor", ld->name);
241 if (ld->fddir == -1) return 1;
242 while (fchdir(ld->fddir) == -1)
243 pause2cannot("change directory, want processor", ld->name);
244 if (wait_exitcode(wstat) != 0) {
245 warnx("processor failed, restart", ld->name);
246 ld->fnsave[26] = 't';
248 ld->fnsave[26] = 'u';
250 while (fchdir(fdwdir) == -1)
251 pause1cannot("change to initial working directory");
252 return ld->processor ? 0 : 1;
254 ld->fnsave[26] = 't';
255 memcpy(f, ld->fnsave, 26);
258 while (rename(ld->fnsave, f) == -1)
259 pause2cannot("rename processed", ld->name);
260 while (chmod(f, 0744) == -1)
261 pause2cannot("set mode of processed", ld->name);
262 ld->fnsave[26] = 'u';
263 if (unlink(ld->fnsave) == -1)
264 bb_error_msg(WARNING"cannot unlink: %s/%s", ld->name, ld->fnsave);
265 while (rename("newstate", "state") == -1)
266 pause2cannot("rename state", ld->name);
268 bb_error_msg(INFO"processed: %s/%s", ld->name, f);
269 while (fchdir(fdwdir) == -1)
270 pause1cannot("change to initial working directory");
274 static void rmoldest(struct logdir *ld)
278 char oldest[FMT_PTIME];
281 oldest[0] = 'A'; oldest[1] = oldest[27] = 0;
282 while (!(d = opendir(".")))
283 pause2cannot("open directory, want rotate", ld->name);
285 while ((f = readdir(d))) {
286 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
287 if (f->d_name[26] == 't') {
288 if (unlink(f->d_name) == -1)
289 warn2("cannot unlink processor leftover", f->d_name);
292 if (strcmp(f->d_name, oldest) < 0)
293 memcpy(oldest, f->d_name, 27);
299 warn2("cannot read directory", ld->name);
302 if (ld->nmax && (n > ld->nmax)) {
304 bb_error_msg(INFO"delete: %s/%s", ld->name, oldest);
305 if ((*oldest == '@') && (unlink(oldest) == -1))
306 warn2("cannot unlink oldest logfile", ld->name);
310 static unsigned rotate(struct logdir *ld)
315 if (ld->fddir == -1) {
316 ld->rotate_period = 0;
320 while (!processorstop(ld))
323 while (fchdir(ld->fddir) == -1)
324 pause2cannot("change directory, want rotate", ld->name);
326 /* create new filename */
327 ld->fnsave[25] = '.';
328 ld->fnsave[26] = 's';
330 ld->fnsave[26] = 'u';
331 ld->fnsave[27] = '\0';
333 fmt_time_bernstein_25(ld->fnsave);
335 stat(ld->fnsave, &st);
336 } while (errno != ENOENT);
338 now = monotonic_sec();
339 if (ld->rotate_period && LESS(ld->next_rotate, now)) {
340 ld->next_rotate = now + ld->rotate_period;
341 if (LESS(ld->next_rotate, nearest_rotate))
342 nearest_rotate = ld->next_rotate;
346 while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
347 pause2cannot("fsync current logfile", ld->name);
348 while (fchmod(ld->fdcur, 0744) == -1)
349 pause2cannot("set mode of current", ld->name);
350 ////close(ld->fdcur);
354 bb_error_msg(INFO"rename: %s/current %s %u", ld->name,
355 ld->fnsave, ld->size);
357 while (rename("current", ld->fnsave) == -1)
358 pause2cannot("rename current", ld->name);
359 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
360 pause2cannot("create new current", ld->name);
361 /* we presume this cannot fail */
362 ld->filecur = fdopen(ld->fdcur, "a"); ////
363 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
366 while (fchmod(ld->fdcur, 0644) == -1)
367 pause2cannot("set mode of current", ld->name);
372 while (fchdir(fdwdir) == -1)
373 pause1cannot("change to initial working directory");
377 static int buffer_pwrite(int n, char *s, unsigned len)
380 struct logdir *ld = &dir[n];
383 if (ld->size >= ld->sizemax)
385 if (len > (ld->sizemax - ld->size))
386 len = ld->sizemax - ld->size;
389 ////i = full_write(ld->fdcur, s, len);
390 ////if (i != -1) break;
391 i = fwrite(s, 1, len, ld->filecur);
394 if ((errno == ENOSPC) && (ld->nmin < ld->nmax)) {
397 char oldest[FMT_PTIME];
400 while (fchdir(ld->fddir) == -1)
401 pause2cannot("change directory, want remove old logfile",
404 oldest[1] = oldest[27] = '\0';
405 while (!(d = opendir(".")))
406 pause2cannot("open directory, want remove old logfile",
409 while ((f = readdir(d)))
410 if ((f->d_name[0] == '@') && (strlen(f->d_name) == 27)) {
412 if (strcmp(f->d_name, oldest) < 0)
413 memcpy(oldest, f->d_name, 27);
415 if (errno) warn2("cannot read directory, want remove old logfile",
420 if (*oldest == '@') {
421 bb_error_msg(WARNING"out of disk space, delete: %s/%s",
424 if (unlink(oldest) == -1) {
425 warn2("cannot unlink oldest logfile", ld->name);
428 while (fchdir(fdwdir) == -1)
429 pause1cannot("change to initial working directory");
434 pause2cannot("write to current", ld->name);
440 if (ld->size >= (ld->sizemax - linemax))
445 static void logdir_close(struct logdir *ld)
450 bb_error_msg(INFO"close: %s", ld->name);
454 return; /* impossible */
455 while (fflush(ld->filecur) || fsync(ld->fdcur) == -1)
456 pause2cannot("fsync current logfile", ld->name);
457 while (fchmod(ld->fdcur, 0744) == -1)
458 pause2cannot("set mode of current", ld->name);
459 ////close(ld->fdcur);
462 if (ld->fdlock == -1)
463 return; /* impossible */
467 ld->processor = NULL;
470 static unsigned logdir_open(struct logdir *ld, const char *fn)
478 now = monotonic_sec();
480 ld->fddir = open(fn, O_RDONLY|O_NDELAY);
481 if (ld->fddir == -1) {
482 warn2("cannot open log directory", (char*)fn);
486 if (fchdir(ld->fddir) == -1) {
488 warn2("cannot change directory", (char*)fn);
491 ld->fdlock = open("lock", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
492 if ((ld->fdlock == -1)
493 || (lock_exnb(ld->fdlock) == -1)
496 warn2("cannot lock directory", (char*)fn);
497 while (fchdir(fdwdir) == -1)
498 pause1cannot("change to initial working directory");
504 ld->sizemax = 1000000;
505 ld->nmax = ld->nmin = 10;
506 ld->rotate_period = 0;
507 ld->name = (char*)fn;
510 free(ld->inst); ld->inst = NULL;
511 free(ld->processor); ld->processor = NULL;
514 i = open_read_close("config", buf, sizeof(buf));
515 if (i < 0 && errno != ENOENT)
516 bb_perror_msg(WARNING": %s/config", ld->name);
519 bb_error_msg(INFO"read: %s/config", ld->name);
522 np = strchr(s, '\n');
523 if (np) *np++ = '\0';
530 int l = asprintf(&new, "%s%s\n", ld->inst?:"", s);
531 if (l >= 0 && new) break;
538 static const struct suffix_mult km_suffixes[] = {
543 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
547 ld->nmax = xatoi_u(&s[1]);
550 ld->nmin = xatoi_u(&s[1]);
553 static const struct suffix_mult mh_suffixes[] = {
556 /*{ "d", 24*60*60 },*/
559 ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
560 if (ld->rotate_period) {
561 ld->next_rotate = now + ld->rotate_period;
562 if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
563 nearest_rotate = ld->next_rotate;
571 ld->processor = wstrdup(s);
577 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
580 np = strchr(s, '\n');
581 if (np) *np++ = '\0';
587 i = stat("current", &st);
589 if (st.st_size && ! (st.st_mode & S_IXUSR)) {
590 ld->fnsave[25] = '.';
591 ld->fnsave[26] = 'u';
592 ld->fnsave[27] = '\0';
594 fmt_time_bernstein_25(ld->fnsave);
596 stat(ld->fnsave, &st);
597 } while (errno != ENOENT);
598 while (rename("current", ld->fnsave) == -1)
599 pause2cannot("rename current", ld->name);
603 /* Be paranoid: st.st_size can be not just bigger, but WIDER! */
604 /* (bug in original svlogd. remove this comment when fixed there) */
605 ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
608 if (errno != ENOENT) {
610 warn2("cannot stat current", ld->name);
611 while (fchdir(fdwdir) == -1)
612 pause1cannot("change to initial working directory");
616 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
617 pause2cannot("open current", ld->name);
618 /* we presume this cannot fail */
619 ld->filecur = fdopen(ld->fdcur, "a"); ////
620 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
623 while (fchmod(ld->fdcur, 0644) == -1)
624 pause2cannot("set mode of current", ld->name);
627 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
628 else bb_error_msg(INFO"new: %s/current", ld->name);
631 while (fchdir(fdwdir) == -1)
632 pause1cannot("change to initial working directory");
636 static void logdirs_reopen(void)
642 for (l = 0; l < dirn; ++l) {
643 logdir_close(&dir[l]);
644 if (logdir_open(&dir[l], fndir[l]))
648 fatalx("no functional log directories");
651 /* Will look good in libbb one day */
652 static ssize_t ndelay_read(int fd, void *buf, size_t count)
654 if (!(fl_flag_0 & O_NONBLOCK))
655 fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
656 count = safe_read(fd, buf, count);
657 if (!(fl_flag_0 & O_NONBLOCK))
658 fcntl(fd, F_SETFL, fl_flag_0);
662 /* Used for reading stdin */
663 static int buffer_pread(int fd, char *s, unsigned len)
670 input.events = POLLIN|POLLHUP|POLLERR;
674 for (i = 0; i < dirn; ++i)
687 now = monotonic_sec();
688 nearest_rotate = now + (45 * 60 + 45);
689 for (i = 0; i < dirn; ++i) {
690 if (dir[i].rotate_period) {
691 if (LESS(dir[i].next_rotate, now))
693 if (LESS(dir[i].next_rotate, nearest_rotate))
694 nearest_rotate = dir[i].next_rotate;
698 sigprocmask(SIG_UNBLOCK, blocked_sigset, NULL);
699 i = nearest_rotate - now;
704 poll(&input, 1, i * 1000);
705 sigprocmask(SIG_BLOCK, blocked_sigset, NULL);
707 i = ndelay_read(fd, s, len);
712 if (errno != EAGAIN) {
713 warn("cannot read standard input");
716 /* else: EAGAIN - normal, repeat silently */
721 linecomplete = (s[i-1] == '\n');
729 if (ch < 32 || ch > 126)
733 for (j = 0; replace[j]; ++j) {
734 if (ch == replace[j]) {
748 static void sig_term_handler(int sig_no)
751 bb_error_msg(INFO"sig%s received", "term");
755 static void sig_child_handler(int sig_no)
760 bb_error_msg(INFO"sig%s received", "child");
761 while ((pid = wait_nohang(&wstat)) > 0) {
762 for (l = 0; l < dirn; ++l) {
763 if (dir[l].ppid == pid) {
765 processorstop(&dir[l]);
772 static void sig_alarm_handler(int sig_no)
775 bb_error_msg(INFO"sig%s received", "alarm");
779 static void sig_hangup_handler(int sig_no)
782 bb_error_msg(INFO"sig%s received", "hangup");
786 static void logmatch(struct logdir *ld)
797 if (pmatch(s+1, line, linelen))
802 if (pmatch(s+1, line, linelen))
810 int svlogd_main(int argc, char **argv);
811 int svlogd_main(int argc, char **argv)
815 ssize_t stdin_cnt = 0;
818 unsigned timestamp = 0;
819 void* (*memRchr)(const void *, int, size_t) = memchr;
821 #define line bb_common_bufsiz1
823 opt_complementary = "tt:vv";
824 opt = getopt32(argv, "r:R:l:b:tv",
825 &r, &replace, &l, &b, ×tamp, &verbose);
828 if (!repl || r[1]) usage();
830 if (opt & 2) if (!repl) repl = '_'; // -R
832 linemax = xatou_range(l, 0, BUFSIZ-26);
833 if (linemax == 0) linemax = BUFSIZ-26;
834 if (linemax < 256) linemax = 256;
836 ////if (opt & 8) { // -b
837 //// buflen = xatoi_u(b);
838 //// if (buflen == 0) buflen = 1024;
840 //if (opt & 0x10) timestamp++; // -t
841 //if (opt & 0x20) verbose++; // -v
842 //if (timestamp > 2) timestamp = 2;
847 if (dirn <= 0) usage();
848 ////if (buflen <= linemax) usage();
849 fdwdir = xopen(".", O_RDONLY|O_NDELAY);
851 dir = xzalloc(dirn * sizeof(struct logdir));
852 for (i = 0; i < dirn; ++i) {
855 ////dir[i].btmp = xmalloc(buflen);
858 /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
860 /* We cannot set NONBLOCK on fd #0 permanently - this setting
861 * _isn't_ per-process! It is shared among all other processes
862 * with the same stdin */
863 fl_flag_0 = fcntl(0, F_GETFL);
865 blocked_sigset = &ss;
867 sigaddset(&ss, SIGTERM);
868 sigaddset(&ss, SIGCHLD);
869 sigaddset(&ss, SIGALRM);
870 sigaddset(&ss, SIGHUP);
871 sigprocmask(SIG_BLOCK, &ss, NULL);
872 sig_catch(SIGTERM, sig_term_handler);
873 sig_catch(SIGCHLD, sig_child_handler);
874 sig_catch(SIGALRM, sig_alarm_handler);
875 sig_catch(SIGHUP, sig_hangup_handler);
879 /* Without timestamps, we don't have to print each line
880 * separately, so we can look for _last_ newline, not first,
881 * thus batching writes */
885 setvbuf(stderr, NULL, _IOFBF, linelen);
887 /* Each iteration processes one or more lines */
889 char stamp[FMT_PTIME];
900 /* lineptr[0..linemax-1] - buffer for stdin */
901 /* (possibly has some unprocessed data from prev loop) */
903 /* Refill the buffer if needed */
904 np = memRchr(lineptr, '\n', stdin_cnt);
905 if (!np && !exitasap) {
906 i = linemax - stdin_cnt; /* avail. bytes at tail */
908 i = buffer_pread(0, lineptr + stdin_cnt, i);
909 if (i <= 0) /* EOF or error on stdin */
912 np = memRchr(lineptr + stdin_cnt, '\n', i);
917 if (stdin_cnt <= 0 && exitasap)
920 /* Search for '\n' (in fact, np already holds the result) */
923 print_to_nl: /* NB: starting from here lineptr may point
924 * farther out into line[] */
925 linelen = np - lineptr + 1;
927 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
928 ch = lineptr[linelen-1];
930 /* Biggest performance hit was coming from the fact
931 * that we did not buffer writes. We were reading many lines
932 * in one read() above, but wrote one line per write().
933 * We are using stdio to fix that */
935 /* write out lineptr[0..linelen-1] to each log destination
936 * (or lineptr[-26..linelen-1] if timestamping) */
941 fmt_time_bernstein_25(stamp);
943 fmt_time_human_30nul(stamp);
946 memcpy(printptr, stamp, 25);
949 for (i = 0; i < dirn; ++i) {
950 struct logdir *ld = &dir[i];
951 if (ld->fddir == -1) continue;
954 if (ld->matcherr == 'e')
955 ////full_write(2, printptr, printlen);
956 fwrite(lineptr, 1, linelen, stderr);
957 if (ld->match != '+') continue;
958 buffer_pwrite(i, printptr, printlen);
961 /* If we didn't see '\n' (long input line), */
962 /* read/write repeatedly until we see it */
964 /* lineptr is emptied now, safe to use as buffer */
965 stdin_cnt = exitasap ? -1 : buffer_pread(0, lineptr, linemax);
966 if (stdin_cnt <= 0) { /* EOF or error on stdin */
968 lineptr[0] = ch = '\n';
973 np = memRchr(lineptr, '\n', stdin_cnt);
975 linelen = np - lineptr + 1;
976 ch = lineptr[linelen-1];
978 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
979 for (i = 0; i < dirn; ++i) {
980 if (dir[i].fddir == -1) continue;
981 if (dir[i].matcherr == 'e')
982 ////full_write(2, lineptr, linelen);
983 fwrite(lineptr, 1, linelen, stderr);
984 if (dir[i].match != '+') continue;
985 buffer_pwrite(i, lineptr, linelen);
989 stdin_cnt -= linelen;
992 /* If we see another '\n', we don't need to read
993 * next piece of input: can print what we have */
994 np = memRchr(lineptr, '\n', stdin_cnt);
997 /* Move unprocessed data to the front of line */
998 memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1003 for (i = 0; i < dirn; ++i) {
1005 while (!processorstop(&dir[i]))
1007 logdir_close(&dir[i]);