chpst: simple code shrink
[platform/upstream/busybox.git] / runit / runsv.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 //usage:#define runsv_trivial_usage
32 //usage:       "DIR"
33 //usage:#define runsv_full_usage "\n\n"
34 //usage:       "Start and monitor a service and optionally an appendant log service"
35
36 #include <sys/poll.h>
37 #include <sys/file.h>
38 #include "libbb.h"
39 #include "runit_lib.h"
40
41 #if ENABLE_MONOTONIC_SYSCALL
42 #include <sys/syscall.h>
43
44 /* libc has incredibly messy way of doing this,
45  * typically requiring -lrt. We just skip all this mess */
46 static void gettimeofday_ns(struct timespec *ts)
47 {
48         syscall(__NR_clock_gettime, CLOCK_REALTIME, ts);
49 }
50 #else
51 static void gettimeofday_ns(struct timespec *ts)
52 {
53         if (sizeof(struct timeval) == sizeof(struct timespec)
54          && sizeof(((struct timeval*)ts)->tv_usec) == sizeof(ts->tv_nsec)
55         ) {
56                 /* Cheat */
57                 gettimeofday((void*)ts, NULL);
58                 ts->tv_nsec *= 1000;
59         } else {
60                 extern void BUG_need_to_implement_gettimeofday_ns(void);
61                 BUG_need_to_implement_gettimeofday_ns();
62         }
63 }
64 #endif
65
66 /* Compare possibly overflowing unsigned counters */
67 #define LESS(a,b) ((int)((unsigned)(b) - (unsigned)(a)) > 0)
68
69 /* state */
70 #define S_DOWN 0
71 #define S_RUN 1
72 #define S_FINISH 2
73 /* ctrl */
74 #define C_NOOP 0
75 #define C_TERM 1
76 #define C_PAUSE 2
77 /* want */
78 #define W_UP 0
79 #define W_DOWN 1
80 #define W_EXIT 2
81
82 struct svdir {
83         int pid;
84         smallint state;
85         smallint ctrl;
86         smallint sd_want;
87         smallint islog;
88         struct timespec start;
89         int fdlock;
90         int fdcontrol;
91         int fdcontrolwrite;
92         int wstat;
93 };
94
95 struct globals {
96         smallint haslog;
97         smallint sigterm;
98         smallint pidchanged;
99         struct fd_pair selfpipe;
100         struct fd_pair logpipe;
101         char *dir;
102         struct svdir svd[2];
103 } FIX_ALIASING;
104 #define G (*(struct globals*)&bb_common_bufsiz1)
105 #define haslog       (G.haslog      )
106 #define sigterm      (G.sigterm     )
107 #define pidchanged   (G.pidchanged  )
108 #define selfpipe     (G.selfpipe    )
109 #define logpipe      (G.logpipe     )
110 #define dir          (G.dir         )
111 #define svd          (G.svd         )
112 #define INIT_G() do { \
113         pidchanged = 1; \
114 } while (0)
115
116 static void fatal2_cannot(const char *m1, const char *m2)
117 {
118         bb_perror_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
119         /* was exiting 111 */
120 }
121 static void fatal_cannot(const char *m)
122 {
123         fatal2_cannot(m, "");
124         /* was exiting 111 */
125 }
126 static void fatal2x_cannot(const char *m1, const char *m2)
127 {
128         bb_error_msg_and_die("%s: fatal: cannot %s%s", dir, m1, m2);
129         /* was exiting 111 */
130 }
131 static void warn_cannot(const char *m)
132 {
133         bb_perror_msg("%s: warning: cannot %s", dir, m);
134 }
135
136 static void s_child(int sig_no UNUSED_PARAM)
137 {
138         write(selfpipe.wr, "", 1);
139 }
140
141 static void s_term(int sig_no UNUSED_PARAM)
142 {
143         sigterm = 1;
144         write(selfpipe.wr, "", 1); /* XXX */
145 }
146
147 static int open_trunc_or_warn(const char *name)
148 {
149         /* Why O_NDELAY? */
150         int fd = open(name, O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT, 0644);
151         if (fd < 0)
152                 bb_perror_msg("%s: warning: cannot open %s",
153                                 dir, name);
154         return fd;
155 }
156
157 static void update_status(struct svdir *s)
158 {
159         ssize_t sz;
160         int fd;
161         svstatus_t status;
162
163         /* pid */
164         if (pidchanged) {
165                 fd = open_trunc_or_warn("supervise/pid.new");
166                 if (fd < 0)
167                         return;
168                 if (s->pid) {
169                         char spid[sizeof(int)*3 + 2];
170                         int size = sprintf(spid, "%u\n", (unsigned)s->pid);
171                         write(fd, spid, size);
172                 }
173                 close(fd);
174                 if (rename_or_warn("supervise/pid.new",
175                     s->islog ? "log/supervise/pid" : "log/supervise/pid"+4))
176                         return;
177                 pidchanged = 0;
178         }
179
180         /* stat */
181         fd = open_trunc_or_warn("supervise/stat.new");
182         if (fd < -1)
183                 return;
184
185         {
186                 char stat_buf[sizeof("finish, paused, got TERM, want down\n")];
187                 char *p = stat_buf;
188                 switch (s->state) {
189                 case S_DOWN:
190                         p = stpcpy(p, "down");
191                         break;
192                 case S_RUN:
193                         p = stpcpy(p, "run");
194                         break;
195                 case S_FINISH:
196                         p = stpcpy(p, "finish");
197                         break;
198                 }
199                 if (s->ctrl & C_PAUSE)
200                         p = stpcpy(p, ", paused");
201                 if (s->ctrl & C_TERM)
202                         p = stpcpy(p, ", got TERM");
203                 if (s->state != S_DOWN)
204                         switch (s->sd_want) {
205                         case W_DOWN:
206                                 p = stpcpy(p, ", want down");
207                                 break;
208                         case W_EXIT:
209                                 p = stpcpy(p, ", want exit");
210                                 break;
211                         }
212                 *p++ = '\n';
213                 write(fd, stat_buf, p - stat_buf);
214                 close(fd);
215         }
216
217         rename_or_warn("supervise/stat.new",
218                 s->islog ? "log/supervise/stat" : "log/supervise/stat"+4);
219
220         /* supervise compatibility */
221         memset(&status, 0, sizeof(status));
222         status.time_be64 = SWAP_BE64(s->start.tv_sec + 0x400000000000000aULL);
223         status.time_nsec_be32 = SWAP_BE32(s->start.tv_nsec);
224         status.pid_le32 = SWAP_LE32(s->pid);
225         if (s->ctrl & C_PAUSE)
226                 status.paused = 1;
227         if (s->sd_want == W_UP)
228                 status.want = 'u';
229         else
230                 status.want = 'd';
231         if (s->ctrl & C_TERM)
232                 status.got_term = 1;
233         status.run_or_finish = s->state;
234         fd = open_trunc_or_warn("supervise/status.new");
235         if (fd < 0)
236                 return;
237         sz = write(fd, &status, sizeof(status));
238         close(fd);
239         if (sz != sizeof(status)) {
240                 warn_cannot("write supervise/status.new");
241                 unlink("supervise/status.new");
242                 return;
243         }
244         rename_or_warn("supervise/status.new",
245                 s->islog ? "log/supervise/status" : "log/supervise/status"+4);
246 }
247
248 static unsigned custom(struct svdir *s, char c)
249 {
250         pid_t pid;
251         int w;
252         char a[10];
253         struct stat st;
254
255         if (s->islog)
256                 return 0;
257         strcpy(a, "control/?");
258         a[8] = c; /* replace '?' */
259         if (stat(a, &st) == 0) {
260                 if (st.st_mode & S_IXUSR) {
261                         pid = vfork();
262                         if (pid == -1) {
263                                 warn_cannot("vfork for control/?");
264                                 return 0;
265                         }
266                         if (pid == 0) {
267                                 /* child */
268                                 if (haslog && dup2(logpipe.wr, 1) == -1)
269                                         warn_cannot("setup stdout for control/?");
270                                 execl(a, a, (char *) NULL);
271                                 fatal_cannot("run control/?");
272                         }
273                         /* parent */
274                         if (safe_waitpid(pid, &w, 0) == -1) {
275                                 warn_cannot("wait for child control/?");
276                                 return 0;
277                         }
278                         return WEXITSTATUS(w) == 0;
279                 }
280         } else {
281                 if (errno != ENOENT)
282                         warn_cannot("stat control/?");
283         }
284         return 0;
285 }
286
287 static void stopservice(struct svdir *s)
288 {
289         if (s->pid && !custom(s, 't')) {
290                 kill(s->pid, SIGTERM);
291                 s->ctrl |= C_TERM;
292                 update_status(s);
293         }
294         if (s->sd_want == W_DOWN) {
295                 kill(s->pid, SIGCONT);
296                 custom(s, 'd');
297                 return;
298         }
299         if (s->sd_want == W_EXIT) {
300                 kill(s->pid, SIGCONT);
301                 custom(s, 'x');
302         }
303 }
304
305 static void startservice(struct svdir *s)
306 {
307         int p;
308         const char *arg[4];
309         char exitcode[sizeof(int)*3 + 2];
310
311         if (s->state == S_FINISH) {
312 /* Two arguments are given to ./finish. The first one is ./run exit code,
313  * or -1 if ./run didnt exit normally. The second one is
314  * the least significant byte of the exit status as determined by waitpid;
315  * for instance it is 0 if ./run exited normally, and the signal number
316  * if ./run was terminated by a signal. If runsv cannot start ./run
317  * for some reason, the exit code is 111 and the status is 0.
318  */
319                 arg[0] = "./finish";
320                 arg[1] = "-1";
321                 if (WIFEXITED(s->wstat)) {
322                         *utoa_to_buf(WEXITSTATUS(s->wstat), exitcode, sizeof(exitcode)) = '\0';
323                         arg[1] = exitcode;
324                 }
325                 //arg[2] = "0";
326                 //if (WIFSIGNALED(s->wstat)) {
327                         arg[2] = utoa(WTERMSIG(s->wstat));
328                 //}
329                 arg[3] = NULL;
330         } else {
331                 arg[0] = "./run";
332                 arg[1] = NULL;
333                 custom(s, 'u');
334         }
335
336         if (s->pid != 0)
337                 stopservice(s); /* should never happen */
338         while ((p = vfork()) == -1) {
339                 warn_cannot("vfork, sleeping");
340                 sleep(5);
341         }
342         if (p == 0) {
343                 /* child */
344                 if (haslog) {
345                         /* NB: bug alert! right order is close, then dup2 */
346                         if (s->islog) {
347                                 xchdir("./log");
348                                 close(logpipe.wr);
349                                 xdup2(logpipe.rd, 0);
350                         } else {
351                                 close(logpipe.rd);
352                                 xdup2(logpipe.wr, 1);
353                         }
354                 }
355                 /* Non-ignored signals revert to SIG_DFL on exec anyway */
356                 /*bb_signals(0
357                         + (1 << SIGCHLD)
358                         + (1 << SIGTERM)
359                         , SIG_DFL);*/
360                 sig_unblock(SIGCHLD);
361                 sig_unblock(SIGTERM);
362                 execv(arg[0], (char**) arg);
363                 fatal2_cannot(s->islog ? "start log/" : "start ", arg[0]);
364         }
365         /* parent */
366         if (s->state != S_FINISH) {
367                 gettimeofday_ns(&s->start);
368                 s->state = S_RUN;
369         }
370         s->pid = p;
371         pidchanged = 1;
372         s->ctrl = C_NOOP;
373         update_status(s);
374 }
375
376 static int ctrl(struct svdir *s, char c)
377 {
378         int sig;
379
380         switch (c) {
381         case 'd': /* down */
382                 s->sd_want = W_DOWN;
383                 update_status(s);
384                 if (s->pid && s->state != S_FINISH)
385                         stopservice(s);
386                 break;
387         case 'u': /* up */
388                 s->sd_want = W_UP;
389                 update_status(s);
390                 if (s->pid == 0)
391                         startservice(s);
392                 break;
393         case 'x': /* exit */
394                 if (s->islog)
395                         break;
396                 s->sd_want = W_EXIT;
397                 update_status(s);
398                 /* FALLTHROUGH */
399         case 't': /* sig term */
400                 if (s->pid && s->state != S_FINISH)
401                         stopservice(s);
402                 break;
403         case 'k': /* sig kill */
404                 if (s->pid && !custom(s, c))
405                         kill(s->pid, SIGKILL);
406                 s->state = S_DOWN;
407                 break;
408         case 'p': /* sig pause */
409                 if (s->pid && !custom(s, c))
410                         kill(s->pid, SIGSTOP);
411                 s->ctrl |= C_PAUSE;
412                 update_status(s);
413                 break;
414         case 'c': /* sig cont */
415                 if (s->pid && !custom(s, c))
416                         kill(s->pid, SIGCONT);
417                 s->ctrl &= ~C_PAUSE;
418                 update_status(s);
419                 break;
420         case 'o': /* once */
421                 s->sd_want = W_DOWN;
422                 update_status(s);
423                 if (!s->pid)
424                         startservice(s);
425                 break;
426         case 'a': /* sig alarm */
427                 sig = SIGALRM;
428                 goto sendsig;
429         case 'h': /* sig hup */
430                 sig = SIGHUP;
431                 goto sendsig;
432         case 'i': /* sig int */
433                 sig = SIGINT;
434                 goto sendsig;
435         case 'q': /* sig quit */
436                 sig = SIGQUIT;
437                 goto sendsig;
438         case '1': /* sig usr1 */
439                 sig = SIGUSR1;
440                 goto sendsig;
441         case '2': /* sig usr2 */
442                 sig = SIGUSR2;
443                 goto sendsig;
444         }
445         return 1;
446  sendsig:
447         if (s->pid && !custom(s, c))
448                 kill(s->pid, sig);
449         return 1;
450 }
451
452 int runsv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
453 int runsv_main(int argc UNUSED_PARAM, char **argv)
454 {
455         struct stat s;
456         int fd;
457         int r;
458         char buf[256];
459
460         INIT_G();
461
462         dir = single_argv(argv);
463
464         xpiped_pair(selfpipe);
465         close_on_exec_on(selfpipe.rd);
466         close_on_exec_on(selfpipe.wr);
467         ndelay_on(selfpipe.rd);
468         ndelay_on(selfpipe.wr);
469
470         sig_block(SIGCHLD);
471         bb_signals_recursive_norestart(1 << SIGCHLD, s_child);
472         sig_block(SIGTERM);
473         bb_signals_recursive_norestart(1 << SIGTERM, s_term);
474
475         xchdir(dir);
476         /* bss: svd[0].pid = 0; */
477         if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */
478         if (C_NOOP) svd[0].ctrl = C_NOOP;
479         if (W_UP) svd[0].sd_want = W_UP;
480         /* bss: svd[0].islog = 0; */
481         /* bss: svd[1].pid = 0; */
482         gettimeofday_ns(&svd[0].start);
483         if (stat("down", &s) != -1)
484                 svd[0].sd_want = W_DOWN;
485
486         if (stat("log", &s) == -1) {
487                 if (errno != ENOENT)
488                         warn_cannot("stat ./log");
489         } else {
490                 if (!S_ISDIR(s.st_mode)) {
491                         errno = 0;
492                         warn_cannot("stat log/down: log is not a directory");
493                 } else {
494                         haslog = 1;
495                         svd[1].state = S_DOWN;
496                         svd[1].ctrl = C_NOOP;
497                         svd[1].sd_want = W_UP;
498                         svd[1].islog = 1;
499                         gettimeofday_ns(&svd[1].start);
500                         if (stat("log/down", &s) != -1)
501                                 svd[1].sd_want = W_DOWN;
502                         xpiped_pair(logpipe);
503                         close_on_exec_on(logpipe.rd);
504                         close_on_exec_on(logpipe.wr);
505                 }
506         }
507
508         if (mkdir("supervise", 0700) == -1) {
509                 r = readlink("supervise", buf, sizeof(buf));
510                 if (r != -1) {
511                         if (r == sizeof(buf))
512                                 fatal2x_cannot("readlink ./supervise", ": name too long");
513                         buf[r] = 0;
514                         mkdir(buf, 0700);
515                 } else {
516                         if ((errno != ENOENT) && (errno != EINVAL))
517                                 fatal_cannot("readlink ./supervise");
518                 }
519         }
520         svd[0].fdlock = xopen3("log/supervise/lock"+4,
521                         O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
522         if (flock(svd[0].fdlock, LOCK_EX | LOCK_NB) == -1)
523                 fatal_cannot("lock supervise/lock");
524         close_on_exec_on(svd[0].fdlock);
525         if (haslog) {
526                 if (mkdir("log/supervise", 0700) == -1) {
527                         r = readlink("log/supervise", buf, 256);
528                         if (r != -1) {
529                                 if (r == 256)
530                                         fatal2x_cannot("readlink ./log/supervise", ": name too long");
531                                 buf[r] = 0;
532                                 fd = xopen(".", O_RDONLY|O_NDELAY);
533                                 xchdir("./log");
534                                 mkdir(buf, 0700);
535                                 if (fchdir(fd) == -1)
536                                         fatal_cannot("change back to service directory");
537                                 close(fd);
538                         }
539                         else {
540                                 if ((errno != ENOENT) && (errno != EINVAL))
541                                         fatal_cannot("readlink ./log/supervise");
542                         }
543                 }
544                 svd[1].fdlock = xopen3("log/supervise/lock",
545                                 O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
546                 if (flock(svd[1].fdlock, LOCK_EX) == -1)
547                         fatal_cannot("lock log/supervise/lock");
548                 close_on_exec_on(svd[1].fdlock);
549         }
550
551         mkfifo("log/supervise/control"+4, 0600);
552         svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY);
553         close_on_exec_on(svd[0].fdcontrol);
554         svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY);
555         close_on_exec_on(svd[0].fdcontrolwrite);
556         update_status(&svd[0]);
557         if (haslog) {
558                 mkfifo("log/supervise/control", 0600);
559                 svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY);
560                 close_on_exec_on(svd[1].fdcontrol);
561                 svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY);
562                 close_on_exec_on(svd[1].fdcontrolwrite);
563                 update_status(&svd[1]);
564         }
565         mkfifo("log/supervise/ok"+4, 0600);
566         fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY);
567         close_on_exec_on(fd);
568         if (haslog) {
569                 mkfifo("log/supervise/ok", 0600);
570                 fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY);
571                 close_on_exec_on(fd);
572         }
573         for (;;) {
574                 struct pollfd x[3];
575                 unsigned deadline;
576                 char ch;
577
578                 if (haslog)
579                         if (!svd[1].pid && svd[1].sd_want == W_UP)
580                                 startservice(&svd[1]);
581                 if (!svd[0].pid)
582                         if (svd[0].sd_want == W_UP || svd[0].state == S_FINISH)
583                                 startservice(&svd[0]);
584
585                 x[0].fd = selfpipe.rd;
586                 x[0].events = POLLIN;
587                 x[1].fd = svd[0].fdcontrol;
588                 x[1].events = POLLIN;
589                 /* x[2] is used only if haslog == 1 */
590                 x[2].fd = svd[1].fdcontrol;
591                 x[2].events = POLLIN;
592                 sig_unblock(SIGTERM);
593                 sig_unblock(SIGCHLD);
594                 poll(x, 2 + haslog, 3600*1000);
595                 sig_block(SIGTERM);
596                 sig_block(SIGCHLD);
597
598                 while (read(selfpipe.rd, &ch, 1) == 1)
599                         continue;
600
601                 for (;;) {
602                         pid_t child;
603                         int wstat;
604
605                         child = wait_any_nohang(&wstat);
606                         if (!child)
607                                 break;
608                         if ((child == -1) && (errno != EINTR))
609                                 break;
610                         if (child == svd[0].pid) {
611                                 svd[0].wstat = wstat;
612                                 svd[0].pid = 0;
613                                 pidchanged = 1;
614                                 svd[0].ctrl &= ~C_TERM;
615                                 if (svd[0].state != S_FINISH) {
616                                         fd = open("finish", O_RDONLY|O_NDELAY);
617                                         if (fd != -1) {
618                                                 close(fd);
619                                                 svd[0].state = S_FINISH;
620                                                 update_status(&svd[0]);
621                                                 continue;
622                                         }
623                                 }
624                                 svd[0].state = S_DOWN;
625                                 deadline = svd[0].start.tv_sec + 1;
626                                 gettimeofday_ns(&svd[0].start);
627                                 update_status(&svd[0]);
628                                 if (LESS(svd[0].start.tv_sec, deadline))
629                                         sleep(1);
630                         }
631                         if (haslog) {
632                                 if (child == svd[1].pid) {
633                                         svd[0].wstat = wstat;
634                                         svd[1].pid = 0;
635                                         pidchanged = 1;
636                                         svd[1].state = S_DOWN;
637                                         svd[1].ctrl &= ~C_TERM;
638                                         deadline = svd[1].start.tv_sec + 1;
639                                         gettimeofday_ns(&svd[1].start);
640                                         update_status(&svd[1]);
641                                         if (LESS(svd[1].start.tv_sec, deadline))
642                                                 sleep(1);
643                                 }
644                         }
645                 } /* for (;;) */
646                 if (read(svd[0].fdcontrol, &ch, 1) == 1)
647                         ctrl(&svd[0], ch);
648                 if (haslog)
649                         if (read(svd[1].fdcontrol, &ch, 1) == 1)
650                                 ctrl(&svd[1], ch);
651
652                 if (sigterm) {
653                         ctrl(&svd[0], 'x');
654                         sigterm = 0;
655                 }
656
657                 if (svd[0].sd_want == W_EXIT && svd[0].state == S_DOWN) {
658                         if (svd[1].pid == 0)
659                                 _exit(EXIT_SUCCESS);
660                         if (svd[1].sd_want != W_EXIT) {
661                                 svd[1].sd_want = W_EXIT;
662                                 /* stopservice(&svd[1]); */
663                                 update_status(&svd[1]);
664                                 close(logpipe.wr);
665                                 close(logpipe.rd);
666                         }
667                 }
668         } /* for (;;) */
669         /* not reached */
670         return 0;
671 }