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