remove runit/runit_lib.c
[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 } FIX_ALIASING;
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         /* Why O_NDELAY? */
155         int fd = open(name, O_WRONLY | O_NDELAY | O_TRUNC | O_CREAT, 0644);
156         if (fd < 0)
157                 bb_perror_msg("%s: warning: cannot open %s",
158                                 dir, name);
159         return fd;
160 }
161
162 static void update_status(struct svdir *s)
163 {
164         ssize_t sz;
165         int fd;
166         svstatus_t status;
167
168         /* pid */
169         if (pidchanged) {
170                 fd = open_trunc_or_warn("supervise/pid.new");
171                 if (fd < 0)
172                         return;
173                 if (s->pid) {
174                         char spid[sizeof(int)*3 + 2];
175                         int size = sprintf(spid, "%u\n", (unsigned)s->pid);
176                         write(fd, spid, size);
177                 }
178                 close(fd);
179                 if (rename_or_warn("supervise/pid.new",
180                     s->islog ? "log/supervise/pid" : "log/supervise/pid"+4))
181                         return;
182                 pidchanged = 0;
183         }
184
185         /* stat */
186         fd = open_trunc_or_warn("supervise/stat.new");
187         if (fd < -1)
188                 return;
189
190         {
191                 char stat_buf[sizeof("finish, paused, got TERM, want down\n")];
192                 char *p = stat_buf;
193                 switch (s->state) {
194                 case S_DOWN:
195                         p = bb_stpcpy(p, "down");
196                         break;
197                 case S_RUN:
198                         p = bb_stpcpy(p, "run");
199                         break;
200                 case S_FINISH:
201                         p = bb_stpcpy(p, "finish");
202                         break;
203                 }
204                 if (s->ctrl & C_PAUSE)
205                         p = bb_stpcpy(p, ", paused");
206                 if (s->ctrl & C_TERM)
207                         p = bb_stpcpy(p, ", got TERM");
208                 if (s->state != S_DOWN)
209                         switch (s->sd_want) {
210                         case W_DOWN:
211                                 p = bb_stpcpy(p, ", want down");
212                                 break;
213                         case W_EXIT:
214                                 p = bb_stpcpy(p, ", want exit");
215                                 break;
216                         }
217                 *p++ = '\n';
218                 write(fd, stat_buf, p - stat_buf);
219                 close(fd);
220         }
221
222         rename_or_warn("supervise/stat.new",
223                 s->islog ? "log/supervise/stat" : "log/supervise/stat"+4);
224
225         /* supervise compatibility */
226         memset(&status, 0, sizeof(status));
227         status.time_be64 = SWAP_BE64(s->start.tv_sec + 0x400000000000000aULL);
228         status.time_nsec_be32 = SWAP_BE32(s->start.tv_nsec);
229         status.pid_le32 = SWAP_LE32(s->pid);
230         if (s->ctrl & C_PAUSE)
231                 status.paused = 1;
232         if (s->sd_want == W_UP)
233                 status.want = 'u';
234         else
235                 status.want = 'd';
236         if (s->ctrl & C_TERM)
237                 status.got_term = 1;
238         status.run_or_finish = s->state;
239         fd = open_trunc_or_warn("supervise/status.new");
240         if (fd < 0)
241                 return;
242         sz = write(fd, &status, sizeof(status));
243         close(fd);
244         if (sz != sizeof(status)) {
245                 warn_cannot("write supervise/status.new");
246                 unlink("supervise/status.new");
247                 return;
248         }
249         rename_or_warn("supervise/status.new",
250                 s->islog ? "log/supervise/status" : "log/supervise/status"+4);
251 }
252
253 static unsigned custom(struct svdir *s, char c)
254 {
255         pid_t pid;
256         int w;
257         char a[10];
258         struct stat st;
259
260         if (s->islog)
261                 return 0;
262         strcpy(a, "control/?");
263         a[8] = c; /* replace '?' */
264         if (stat(a, &st) == 0) {
265                 if (st.st_mode & S_IXUSR) {
266                         pid = vfork();
267                         if (pid == -1) {
268                                 warn_cannot("vfork for control/?");
269                                 return 0;
270                         }
271                         if (pid == 0) {
272                                 /* child */
273                                 if (haslog && dup2(logpipe.wr, 1) == -1)
274                                         warn_cannot("setup stdout for control/?");
275                                 execl(a, a, (char *) NULL);
276                                 fatal_cannot("run control/?");
277                         }
278                         /* parent */
279                         if (safe_waitpid(pid, &w, 0) == -1) {
280                                 warn_cannot("wait for child control/?");
281                                 return 0;
282                         }
283                         return WEXITSTATUS(w) == 0;
284                 }
285         } else {
286                 if (errno != ENOENT)
287                         warn_cannot("stat control/?");
288         }
289         return 0;
290 }
291
292 static void stopservice(struct svdir *s)
293 {
294         if (s->pid && !custom(s, 't')) {
295                 kill(s->pid, SIGTERM);
296                 s->ctrl |= C_TERM;
297                 update_status(s);
298         }
299         if (s->sd_want == W_DOWN) {
300                 kill(s->pid, SIGCONT);
301                 custom(s, 'd');
302                 return;
303         }
304         if (s->sd_want == W_EXIT) {
305                 kill(s->pid, SIGCONT);
306                 custom(s, 'x');
307         }
308 }
309
310 static void startservice(struct svdir *s)
311 {
312         int p;
313         const char *arg[4];
314         char exitcode[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                         *utoa_to_buf(WEXITSTATUS(s->wstat), exitcode, sizeof(exitcode)) = '\0';
328                         arg[1] = exitcode;
329                 }
330                 //arg[2] = "0";
331                 //if (WIFSIGNALED(s->wstat)) {
332                         arg[2] = utoa(WTERMSIG(s->wstat));
333                 //}
334                 arg[3] = NULL;
335         } else {
336                 arg[0] = "./run";
337                 arg[1] = NULL;
338                 custom(s, 'u');
339         }
340
341         if (s->pid != 0)
342                 stopservice(s); /* should never happen */
343         while ((p = vfork()) == -1) {
344                 warn_cannot("vfork, sleeping");
345                 sleep(5);
346         }
347         if (p == 0) {
348                 /* child */
349                 if (haslog) {
350                         /* NB: bug alert! right order is close, then dup2 */
351                         if (s->islog) {
352                                 xchdir("./log");
353                                 close(logpipe.wr);
354                                 xdup2(logpipe.rd, 0);
355                         } else {
356                                 close(logpipe.rd);
357                                 xdup2(logpipe.wr, 1);
358                         }
359                 }
360                 /* Non-ignored signals revert to SIG_DFL on exec anyway */
361                 /*bb_signals(0
362                         + (1 << SIGCHLD)
363                         + (1 << SIGTERM)
364                         , SIG_DFL);*/
365                 sig_unblock(SIGCHLD);
366                 sig_unblock(SIGTERM);
367                 execv(arg[0], (char**) arg);
368                 fatal2_cannot(s->islog ? "start log/" : "start ", arg[0]);
369         }
370         /* parent */
371         if (s->state != S_FINISH) {
372                 gettimeofday_ns(&s->start);
373                 s->state = S_RUN;
374         }
375         s->pid = p;
376         pidchanged = 1;
377         s->ctrl = C_NOOP;
378         update_status(s);
379 }
380
381 static int ctrl(struct svdir *s, char c)
382 {
383         int sig;
384
385         switch (c) {
386         case 'd': /* down */
387                 s->sd_want = W_DOWN;
388                 update_status(s);
389                 if (s->pid && s->state != S_FINISH)
390                         stopservice(s);
391                 break;
392         case 'u': /* up */
393                 s->sd_want = W_UP;
394                 update_status(s);
395                 if (s->pid == 0)
396                         startservice(s);
397                 break;
398         case 'x': /* exit */
399                 if (s->islog)
400                         break;
401                 s->sd_want = W_EXIT;
402                 update_status(s);
403                 /* FALLTHROUGH */
404         case 't': /* sig term */
405                 if (s->pid && s->state != S_FINISH)
406                         stopservice(s);
407                 break;
408         case 'k': /* sig kill */
409                 if (s->pid && !custom(s, c))
410                         kill(s->pid, SIGKILL);
411                 s->state = S_DOWN;
412                 break;
413         case 'p': /* sig pause */
414                 if (s->pid && !custom(s, c))
415                         kill(s->pid, SIGSTOP);
416                 s->ctrl |= C_PAUSE;
417                 update_status(s);
418                 break;
419         case 'c': /* sig cont */
420                 if (s->pid && !custom(s, c))
421                         kill(s->pid, SIGCONT);
422                 s->ctrl &= ~C_PAUSE;
423                 update_status(s);
424                 break;
425         case 'o': /* once */
426                 s->sd_want = W_DOWN;
427                 update_status(s);
428                 if (!s->pid)
429                         startservice(s);
430                 break;
431         case 'a': /* sig alarm */
432                 sig = SIGALRM;
433                 goto sendsig;
434         case 'h': /* sig hup */
435                 sig = SIGHUP;
436                 goto sendsig;
437         case 'i': /* sig int */
438                 sig = SIGINT;
439                 goto sendsig;
440         case 'q': /* sig quit */
441                 sig = SIGQUIT;
442                 goto sendsig;
443         case '1': /* sig usr1 */
444                 sig = SIGUSR1;
445                 goto sendsig;
446         case '2': /* sig usr2 */
447                 sig = SIGUSR2;
448                 goto sendsig;
449         }
450         return 1;
451  sendsig:
452         if (s->pid && !custom(s, c))
453                 kill(s->pid, sig);
454         return 1;
455 }
456
457 int runsv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
458 int runsv_main(int argc UNUSED_PARAM, char **argv)
459 {
460         struct stat s;
461         int fd;
462         int r;
463         char buf[256];
464
465         INIT_G();
466
467         dir = single_argv(argv);
468
469         xpiped_pair(selfpipe);
470         close_on_exec_on(selfpipe.rd);
471         close_on_exec_on(selfpipe.wr);
472         ndelay_on(selfpipe.rd);
473         ndelay_on(selfpipe.wr);
474
475         sig_block(SIGCHLD);
476         bb_signals_recursive_norestart(1 << SIGCHLD, s_child);
477         sig_block(SIGTERM);
478         bb_signals_recursive_norestart(1 << SIGTERM, s_term);
479
480         xchdir(dir);
481         /* bss: svd[0].pid = 0; */
482         if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */
483         if (C_NOOP) svd[0].ctrl = C_NOOP;
484         if (W_UP) svd[0].sd_want = W_UP;
485         /* bss: svd[0].islog = 0; */
486         /* bss: svd[1].pid = 0; */
487         gettimeofday_ns(&svd[0].start);
488         if (stat("down", &s) != -1)
489                 svd[0].sd_want = W_DOWN;
490
491         if (stat("log", &s) == -1) {
492                 if (errno != ENOENT)
493                         warn_cannot("stat ./log");
494         } else {
495                 if (!S_ISDIR(s.st_mode)) {
496                         errno = 0;
497                         warn_cannot("stat log/down: log is not a directory");
498                 } else {
499                         haslog = 1;
500                         svd[1].state = S_DOWN;
501                         svd[1].ctrl = C_NOOP;
502                         svd[1].sd_want = W_UP;
503                         svd[1].islog = 1;
504                         gettimeofday_ns(&svd[1].start);
505                         if (stat("log/down", &s) != -1)
506                                 svd[1].sd_want = W_DOWN;
507                         xpiped_pair(logpipe);
508                         close_on_exec_on(logpipe.rd);
509                         close_on_exec_on(logpipe.wr);
510                 }
511         }
512
513         if (mkdir("supervise", 0700) == -1) {
514                 r = readlink("supervise", buf, sizeof(buf));
515                 if (r != -1) {
516                         if (r == sizeof(buf))
517                                 fatal2x_cannot("readlink ./supervise", ": name too long");
518                         buf[r] = 0;
519                         mkdir(buf, 0700);
520                 } else {
521                         if ((errno != ENOENT) && (errno != EINVAL))
522                                 fatal_cannot("readlink ./supervise");
523                 }
524         }
525         svd[0].fdlock = xopen3("log/supervise/lock"+4,
526                         O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
527         if (flock(svd[0].fdlock, LOCK_EX | LOCK_NB) == -1)
528                 fatal_cannot("lock supervise/lock");
529         close_on_exec_on(svd[0].fdlock);
530         if (haslog) {
531                 if (mkdir("log/supervise", 0700) == -1) {
532                         r = readlink("log/supervise", buf, 256);
533                         if (r != -1) {
534                                 if (r == 256)
535                                         fatal2x_cannot("readlink ./log/supervise", ": name too long");
536                                 buf[r] = 0;
537                                 fd = xopen(".", O_RDONLY|O_NDELAY);
538                                 xchdir("./log");
539                                 mkdir(buf, 0700);
540                                 if (fchdir(fd) == -1)
541                                         fatal_cannot("change back to service directory");
542                                 close(fd);
543                         }
544                         else {
545                                 if ((errno != ENOENT) && (errno != EINVAL))
546                                         fatal_cannot("readlink ./log/supervise");
547                         }
548                 }
549                 svd[1].fdlock = xopen3("log/supervise/lock",
550                                 O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
551                 if (flock(svd[1].fdlock, LOCK_EX) == -1)
552                         fatal_cannot("lock log/supervise/lock");
553                 close_on_exec_on(svd[1].fdlock);
554         }
555
556         mkfifo("log/supervise/control"+4, 0600);
557         svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY);
558         close_on_exec_on(svd[0].fdcontrol);
559         svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY);
560         close_on_exec_on(svd[0].fdcontrolwrite);
561         update_status(&svd[0]);
562         if (haslog) {
563                 mkfifo("log/supervise/control", 0600);
564                 svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY);
565                 close_on_exec_on(svd[1].fdcontrol);
566                 svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY);
567                 close_on_exec_on(svd[1].fdcontrolwrite);
568                 update_status(&svd[1]);
569         }
570         mkfifo("log/supervise/ok"+4, 0600);
571         fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY);
572         close_on_exec_on(fd);
573         if (haslog) {
574                 mkfifo("log/supervise/ok", 0600);
575                 fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY);
576                 close_on_exec_on(fd);
577         }
578         for (;;) {
579                 struct pollfd x[3];
580                 unsigned deadline;
581                 char ch;
582
583                 if (haslog)
584                         if (!svd[1].pid && svd[1].sd_want == W_UP)
585                                 startservice(&svd[1]);
586                 if (!svd[0].pid)
587                         if (svd[0].sd_want == W_UP || svd[0].state == S_FINISH)
588                                 startservice(&svd[0]);
589
590                 x[0].fd = selfpipe.rd;
591                 x[0].events = POLLIN;
592                 x[1].fd = svd[0].fdcontrol;
593                 x[1].events = POLLIN;
594                 /* x[2] is used only if haslog == 1 */
595                 x[2].fd = svd[1].fdcontrol;
596                 x[2].events = POLLIN;
597                 sig_unblock(SIGTERM);
598                 sig_unblock(SIGCHLD);
599                 poll(x, 2 + haslog, 3600*1000);
600                 sig_block(SIGTERM);
601                 sig_block(SIGCHLD);
602
603                 while (read(selfpipe.rd, &ch, 1) == 1)
604                         continue;
605
606                 for (;;) {
607                         pid_t child;
608                         int wstat;
609
610                         child = wait_any_nohang(&wstat);
611                         if (!child)
612                                 break;
613                         if ((child == -1) && (errno != EINTR))
614                                 break;
615                         if (child == svd[0].pid) {
616                                 svd[0].wstat = wstat;
617                                 svd[0].pid = 0;
618                                 pidchanged = 1;
619                                 svd[0].ctrl &= ~C_TERM;
620                                 if (svd[0].state != S_FINISH) {
621                                         fd = open("finish", O_RDONLY|O_NDELAY);
622                                         if (fd != -1) {
623                                                 close(fd);
624                                                 svd[0].state = S_FINISH;
625                                                 update_status(&svd[0]);
626                                                 continue;
627                                         }
628                                 }
629                                 svd[0].state = S_DOWN;
630                                 deadline = svd[0].start.tv_sec + 1;
631                                 gettimeofday_ns(&svd[0].start);
632                                 update_status(&svd[0]);
633                                 if (LESS(svd[0].start.tv_sec, deadline))
634                                         sleep(1);
635                         }
636                         if (haslog) {
637                                 if (child == svd[1].pid) {
638                                         svd[0].wstat = wstat;
639                                         svd[1].pid = 0;
640                                         pidchanged = 1;
641                                         svd[1].state = S_DOWN;
642                                         svd[1].ctrl &= ~C_TERM;
643                                         deadline = svd[1].start.tv_sec + 1;
644                                         gettimeofday_ns(&svd[1].start);
645                                         update_status(&svd[1]);
646                                         if (LESS(svd[1].start.tv_sec, deadline))
647                                                 sleep(1);
648                                 }
649                         }
650                 } /* for (;;) */
651                 if (read(svd[0].fdcontrol, &ch, 1) == 1)
652                         ctrl(&svd[0], ch);
653                 if (haslog)
654                         if (read(svd[1].fdcontrol, &ch, 1) == 1)
655                                 ctrl(&svd[1], ch);
656
657                 if (sigterm) {
658                         ctrl(&svd[0], 'x');
659                         sigterm = 0;
660                 }
661
662                 if (svd[0].sd_want == W_EXIT && svd[0].state == S_DOWN) {
663                         if (svd[1].pid == 0)
664                                 _exit(EXIT_SUCCESS);
665                         if (svd[1].sd_want != W_EXIT) {
666                                 svd[1].sd_want = W_EXIT;
667                                 /* stopservice(&svd[1]); */
668                                 update_status(&svd[1]);
669                                 close(logpipe.wr);
670                                 close(logpipe.rd);
671                         }
672                 }
673         } /* for (;;) */
674         /* not reached */
675         return 0;
676 }