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