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');
530 /* Add '\n'-terminated line to ld->inst */
532 int l = asprintf(&new, "%s%s\n", ld->inst ? : "", s);
541 static const struct suffix_mult km_suffixes[] = {
546 ld->sizemax = xatou_sfx(&s[1], km_suffixes);
550 ld->nmax = xatoi_u(&s[1]);
553 ld->nmin = xatoi_u(&s[1]);
556 static const struct suffix_mult mh_suffixes[] = {
559 /*{ "d", 24*60*60 },*/
562 ld->rotate_period = xatou_sfx(&s[1], mh_suffixes);
563 if (ld->rotate_period) {
564 ld->next_rotate = now + ld->rotate_period;
565 if (!tmaxflag || LESS(ld->next_rotate, nearest_rotate))
566 nearest_rotate = ld->next_rotate;
574 ld->processor = wstrdup(s);
580 /* Convert "aa\nbb\ncc\n\0" to "aa\0bb\0cc\0\0" */
583 np = strchr(s, '\n');
591 i = stat("current", &st);
593 if (st.st_size && !(st.st_mode & S_IXUSR)) {
594 ld->fnsave[25] = '.';
595 ld->fnsave[26] = 'u';
596 ld->fnsave[27] = '\0';
598 fmt_time_bernstein_25(ld->fnsave);
600 stat(ld->fnsave, &st);
601 } while (errno != ENOENT);
602 while (rename("current", ld->fnsave) == -1)
603 pause2cannot("rename current", ld->name);
607 /* st.st_size can be not just bigger, but WIDER!
608 * This code is safe: if st.st_size > 4GB, we select
609 * ld->sizemax (because it's "unsigned") */
610 ld->size = (st.st_size > ld->sizemax) ? ld->sizemax : st.st_size;
613 if (errno != ENOENT) {
615 warn2("cannot stat current", ld->name);
616 while (fchdir(fdwdir) == -1)
617 pause1cannot("change to initial working directory");
621 while ((ld->fdcur = open("current", O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600)) == -1)
622 pause2cannot("open current", ld->name);
623 /* we presume this cannot fail */
624 ld->filecur = fdopen(ld->fdcur, "a"); ////
625 setvbuf(ld->filecur, NULL, _IOFBF, linelen); ////
628 while (fchmod(ld->fdcur, 0644) == -1)
629 pause2cannot("set mode of current", ld->name);
632 if (i == 0) bb_error_msg(INFO"append: %s/current", ld->name);
633 else bb_error_msg(INFO"new: %s/current", ld->name);
636 while (fchdir(fdwdir) == -1)
637 pause1cannot("change to initial working directory");
641 static void logdirs_reopen(void)
647 for (l = 0; l < dirn; ++l) {
648 logdir_close(&dir[l]);
649 if (logdir_open(&dir[l], fndir[l]))
653 fatalx("no functional log directories");
656 /* Will look good in libbb one day */
657 static ssize_t ndelay_read(int fd, void *buf, size_t count)
659 if (!(fl_flag_0 & O_NONBLOCK))
660 fcntl(fd, F_SETFL, fl_flag_0 | O_NONBLOCK);
661 count = safe_read(fd, buf, count);
662 if (!(fl_flag_0 & O_NONBLOCK))
663 fcntl(fd, F_SETFL, fl_flag_0);
667 /* Used for reading stdin */
668 static int buffer_pread(int fd, char *s, unsigned len)
675 input.events = POLLIN;
679 for (i = 0; i < dirn; ++i)
692 now = monotonic_sec();
693 nearest_rotate = now + (45 * 60 + 45);
694 for (i = 0; i < dirn; ++i) {
695 if (dir[i].rotate_period) {
696 if (LESS(dir[i].next_rotate, now))
698 if (LESS(dir[i].next_rotate, nearest_rotate))
699 nearest_rotate = dir[i].next_rotate;
703 sigprocmask(SIG_UNBLOCK, blocked_sigset, NULL);
704 i = nearest_rotate - now;
709 poll(&input, 1, i * 1000);
710 sigprocmask(SIG_BLOCK, blocked_sigset, NULL);
712 i = ndelay_read(fd, s, len);
717 if (errno != EAGAIN) {
718 warn("cannot read standard input");
721 /* else: EAGAIN - normal, repeat silently */
726 linecomplete = (s[i-1] == '\n');
734 if (ch < 32 || ch > 126)
738 for (j = 0; replace[j]; ++j) {
739 if (ch == replace[j]) {
752 static void sig_term_handler(int sig_no)
755 bb_error_msg(INFO"sig%s received", "term");
759 static void sig_child_handler(int sig_no)
764 bb_error_msg(INFO"sig%s received", "child");
765 while ((pid = wait_nohang(&wstat)) > 0) {
766 for (l = 0; l < dirn; ++l) {
767 if (dir[l].ppid == pid) {
769 processorstop(&dir[l]);
776 static void sig_alarm_handler(int sig_no)
779 bb_error_msg(INFO"sig%s received", "alarm");
783 static void sig_hangup_handler(int sig_no)
786 bb_error_msg(INFO"sig%s received", "hangup");
790 static void logmatch(struct logdir *ld)
801 if (pmatch(s+1, line, linelen))
806 if (pmatch(s+1, line, linelen))
814 int svlogd_main(int argc, char **argv);
815 int svlogd_main(int argc, char **argv)
819 ssize_t stdin_cnt = 0;
822 unsigned timestamp = 0;
823 void* (*memRchr)(const void *, int, size_t) = memchr;
825 #define line bb_common_bufsiz1
827 opt_complementary = "tt:vv";
828 opt = getopt32(argv, "r:R:l:b:tv",
829 &r, &replace, &l, &b, ×tamp, &verbose);
832 if (!repl || r[1]) usage();
834 if (opt & 2) if (!repl) repl = '_'; // -R
836 linemax = xatou_range(l, 0, BUFSIZ-26);
837 if (linemax == 0) linemax = BUFSIZ-26;
838 if (linemax < 256) linemax = 256;
840 ////if (opt & 8) { // -b
841 //// buflen = xatoi_u(b);
842 //// if (buflen == 0) buflen = 1024;
844 //if (opt & 0x10) timestamp++; // -t
845 //if (opt & 0x20) verbose++; // -v
846 //if (timestamp > 2) timestamp = 2;
851 if (dirn <= 0) usage();
852 ////if (buflen <= linemax) usage();
853 fdwdir = xopen(".", O_RDONLY|O_NDELAY);
855 dir = xzalloc(dirn * sizeof(struct logdir));
856 for (i = 0; i < dirn; ++i) {
859 ////dir[i].btmp = xmalloc(buflen);
862 /* line = xmalloc(linemax + (timestamp ? 26 : 0)); */
864 /* We cannot set NONBLOCK on fd #0 permanently - this setting
865 * _isn't_ per-process! It is shared among all other processes
866 * with the same stdin */
867 fl_flag_0 = fcntl(0, F_GETFL);
869 blocked_sigset = &ss;
871 sigaddset(&ss, SIGTERM);
872 sigaddset(&ss, SIGCHLD);
873 sigaddset(&ss, SIGALRM);
874 sigaddset(&ss, SIGHUP);
875 sigprocmask(SIG_BLOCK, &ss, NULL);
876 sig_catch(SIGTERM, sig_term_handler);
877 sig_catch(SIGCHLD, sig_child_handler);
878 sig_catch(SIGALRM, sig_alarm_handler);
879 sig_catch(SIGHUP, sig_hangup_handler);
883 /* Without timestamps, we don't have to print each line
884 * separately, so we can look for _last_ newline, not first,
885 * thus batching writes */
889 setvbuf(stderr, NULL, _IOFBF, linelen);
891 /* Each iteration processes one or more lines */
893 char stamp[FMT_PTIME];
904 /* lineptr[0..linemax-1] - buffer for stdin */
905 /* (possibly has some unprocessed data from prev loop) */
907 /* Refill the buffer if needed */
908 np = memRchr(lineptr, '\n', stdin_cnt);
909 if (!np && !exitasap) {
910 i = linemax - stdin_cnt; /* avail. bytes at tail */
912 i = buffer_pread(0, lineptr + stdin_cnt, i);
913 if (i <= 0) /* EOF or error on stdin */
916 np = memRchr(lineptr + stdin_cnt, '\n', i);
921 if (stdin_cnt <= 0 && exitasap)
924 /* Search for '\n' (in fact, np already holds the result) */
927 print_to_nl: /* NB: starting from here lineptr may point
928 * farther out into line[] */
929 linelen = np - lineptr + 1;
931 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
932 ch = lineptr[linelen-1];
934 /* Biggest performance hit was coming from the fact
935 * that we did not buffer writes. We were reading many lines
936 * in one read() above, but wrote one line per write().
937 * We are using stdio to fix that */
939 /* write out lineptr[0..linelen-1] to each log destination
940 * (or lineptr[-26..linelen-1] if timestamping) */
945 fmt_time_bernstein_25(stamp);
947 fmt_time_human_30nul(stamp);
950 memcpy(printptr, stamp, 25);
953 for (i = 0; i < dirn; ++i) {
954 struct logdir *ld = &dir[i];
955 if (ld->fddir == -1) continue;
958 if (ld->matcherr == 'e')
959 ////full_write(2, printptr, printlen);
960 fwrite(lineptr, 1, linelen, stderr);
961 if (ld->match != '+') continue;
962 buffer_pwrite(i, printptr, printlen);
965 /* If we didn't see '\n' (long input line), */
966 /* read/write repeatedly until we see it */
968 /* lineptr is emptied now, safe to use as buffer */
969 stdin_cnt = exitasap ? -1 : buffer_pread(0, lineptr, linemax);
970 if (stdin_cnt <= 0) { /* EOF or error on stdin */
972 lineptr[0] = ch = '\n';
977 np = memRchr(lineptr, '\n', stdin_cnt);
979 linelen = np - lineptr + 1;
980 ch = lineptr[linelen-1];
982 /* linelen == no of chars incl. '\n' (or == stdin_cnt) */
983 for (i = 0; i < dirn; ++i) {
984 if (dir[i].fddir == -1) continue;
985 if (dir[i].matcherr == 'e')
986 ////full_write(2, lineptr, linelen);
987 fwrite(lineptr, 1, linelen, stderr);
988 if (dir[i].match != '+') continue;
989 buffer_pwrite(i, lineptr, linelen);
993 stdin_cnt -= linelen;
996 /* If we see another '\n', we don't need to read
997 * next piece of input: can print what we have */
998 np = memRchr(lineptr, '\n', stdin_cnt);
1001 /* Move unprocessed data to the front of line */
1002 memmove((timestamp ? line+26 : line), lineptr, stdin_cnt);
1007 for (i = 0; i < dirn; ++i) {
1009 while (!processorstop(&dir[i]))
1011 logdir_close(&dir[i]);