*: use {i,u}toa() where appropriate
[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
315         if (s->state == S_FINISH) {
316 /* Two arguments are given to ./finish. The first one is ./run exit code,
317  * or -1 if ./run didnt exit normally. The second one is
318  * the least significant byte of the exit status as determined by waitpid;
319  * for instance it is 0 if ./run exited normally, and the signal number
320  * if ./run was terminated by a signal. If runsv cannot start ./run
321  * for some reason, the exit code is 111 and the status is 0.
322  */
323                 arg[0] = "./finish";
324                 arg[1] = "-1";
325                 if (WIFEXITED(s->wstat)) {
326                         *utoa_to_buf(WEXITSTATUS(s->wstat), exitcode, sizeof(exitcode)) = '\0';
327                         arg[1] = exitcode;
328                 }
329                 //arg[2] = "0";
330                 //if (WIFSIGNALED(s->wstat)) {
331                         arg[2] = utoa(WTERMSIG(s->wstat));
332                 //}
333                 arg[3] = NULL;
334         } else {
335                 arg[0] = "./run";
336                 arg[1] = NULL;
337                 custom(s, 'u');
338         }
339
340         if (s->pid != 0)
341                 stopservice(s); /* should never happen */
342         while ((p = vfork()) == -1) {
343                 warn_cannot("vfork, sleeping");
344                 sleep(5);
345         }
346         if (p == 0) {
347                 /* child */
348                 if (haslog) {
349                         /* NB: bug alert! right order is close, then dup2 */
350                         if (s->islog) {
351                                 xchdir("./log");
352                                 close(logpipe.wr);
353                                 xdup2(logpipe.rd, 0);
354                         } else {
355                                 close(logpipe.rd);
356                                 xdup2(logpipe.wr, 1);
357                         }
358                 }
359                 /* Non-ignored signals revert to SIG_DFL on exec anyway */
360                 /*bb_signals(0
361                         + (1 << SIGCHLD)
362                         + (1 << SIGTERM)
363                         , SIG_DFL);*/
364                 sig_unblock(SIGCHLD);
365                 sig_unblock(SIGTERM);
366                 execv(arg[0], (char**) arg);
367                 fatal2_cannot(s->islog ? "start log/" : "start ", arg[0]);
368         }
369         /* parent */
370         if (s->state != S_FINISH) {
371                 gettimeofday_ns(&s->start);
372                 s->state = S_RUN;
373         }
374         s->pid = p;
375         pidchanged = 1;
376         s->ctrl = C_NOOP;
377         update_status(s);
378 }
379
380 static int ctrl(struct svdir *s, char c)
381 {
382         int sig;
383
384         switch (c) {
385         case 'd': /* down */
386                 s->sd_want = W_DOWN;
387                 update_status(s);
388                 if (s->pid && s->state != S_FINISH)
389                         stopservice(s);
390                 break;
391         case 'u': /* up */
392                 s->sd_want = W_UP;
393                 update_status(s);
394                 if (s->pid == 0)
395                         startservice(s);
396                 break;
397         case 'x': /* exit */
398                 if (s->islog)
399                         break;
400                 s->sd_want = W_EXIT;
401                 update_status(s);
402                 /* FALLTHROUGH */
403         case 't': /* sig term */
404                 if (s->pid && s->state != S_FINISH)
405                         stopservice(s);
406                 break;
407         case 'k': /* sig kill */
408                 if (s->pid && !custom(s, c))
409                         kill(s->pid, SIGKILL);
410                 s->state = S_DOWN;
411                 break;
412         case 'p': /* sig pause */
413                 if (s->pid && !custom(s, c))
414                         kill(s->pid, SIGSTOP);
415                 s->ctrl |= C_PAUSE;
416                 update_status(s);
417                 break;
418         case 'c': /* sig cont */
419                 if (s->pid && !custom(s, c))
420                         kill(s->pid, SIGCONT);
421                 s->ctrl &= ~C_PAUSE;
422                 update_status(s);
423                 break;
424         case 'o': /* once */
425                 s->sd_want = W_DOWN;
426                 update_status(s);
427                 if (!s->pid)
428                         startservice(s);
429                 break;
430         case 'a': /* sig alarm */
431                 sig = SIGALRM;
432                 goto sendsig;
433         case 'h': /* sig hup */
434                 sig = SIGHUP;
435                 goto sendsig;
436         case 'i': /* sig int */
437                 sig = SIGINT;
438                 goto sendsig;
439         case 'q': /* sig quit */
440                 sig = SIGQUIT;
441                 goto sendsig;
442         case '1': /* sig usr1 */
443                 sig = SIGUSR1;
444                 goto sendsig;
445         case '2': /* sig usr2 */
446                 sig = SIGUSR2;
447                 goto sendsig;
448         }
449         return 1;
450  sendsig:
451         if (s->pid && !custom(s, c))
452                 kill(s->pid, sig);
453         return 1;
454 }
455
456 int runsv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
457 int runsv_main(int argc UNUSED_PARAM, char **argv)
458 {
459         struct stat s;
460         int fd;
461         int r;
462         char buf[256];
463
464         INIT_G();
465
466         if (!argv[1] || argv[2])
467                 bb_show_usage();
468         dir = argv[1];
469
470         xpiped_pair(selfpipe);
471         close_on_exec_on(selfpipe.rd);
472         close_on_exec_on(selfpipe.wr);
473         ndelay_on(selfpipe.rd);
474         ndelay_on(selfpipe.wr);
475
476         sig_block(SIGCHLD);
477         bb_signals_recursive_norestart(1 << SIGCHLD, s_child);
478         sig_block(SIGTERM);
479         bb_signals_recursive_norestart(1 << SIGTERM, s_term);
480
481         xchdir(dir);
482         /* bss: svd[0].pid = 0; */
483         if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */
484         if (C_NOOP) svd[0].ctrl = C_NOOP;
485         if (W_UP) svd[0].sd_want = W_UP;
486         /* bss: svd[0].islog = 0; */
487         /* bss: svd[1].pid = 0; */
488         gettimeofday_ns(&svd[0].start);
489         if (stat("down", &s) != -1)
490                 svd[0].sd_want = W_DOWN;
491
492         if (stat("log", &s) == -1) {
493                 if (errno != ENOENT)
494                         warn_cannot("stat ./log");
495         } else {
496                 if (!S_ISDIR(s.st_mode)) {
497                         errno = 0;
498                         warn_cannot("stat log/down: log is not a directory");
499                 } else {
500                         haslog = 1;
501                         svd[1].state = S_DOWN;
502                         svd[1].ctrl = C_NOOP;
503                         svd[1].sd_want = W_UP;
504                         svd[1].islog = 1;
505                         gettimeofday_ns(&svd[1].start);
506                         if (stat("log/down", &s) != -1)
507                                 svd[1].sd_want = W_DOWN;
508                         xpiped_pair(logpipe);
509                         close_on_exec_on(logpipe.rd);
510                         close_on_exec_on(logpipe.wr);
511                 }
512         }
513
514         if (mkdir("supervise", 0700) == -1) {
515                 r = readlink("supervise", buf, sizeof(buf));
516                 if (r != -1) {
517                         if (r == sizeof(buf))
518                                 fatal2x_cannot("readlink ./supervise", ": name too long");
519                         buf[r] = 0;
520                         mkdir(buf, 0700);
521                 } else {
522                         if ((errno != ENOENT) && (errno != EINVAL))
523                                 fatal_cannot("readlink ./supervise");
524                 }
525         }
526         svd[0].fdlock = xopen3("log/supervise/lock"+4,
527                         O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
528         if (lock_exnb(svd[0].fdlock) == -1)
529                 fatal_cannot("lock supervise/lock");
530         close_on_exec_on(svd[0].fdlock);
531         if (haslog) {
532                 if (mkdir("log/supervise", 0700) == -1) {
533                         r = readlink("log/supervise", buf, 256);
534                         if (r != -1) {
535                                 if (r == 256)
536                                         fatal2x_cannot("readlink ./log/supervise", ": name too long");
537                                 buf[r] = 0;
538                                 fd = xopen(".", O_RDONLY|O_NDELAY);
539                                 xchdir("./log");
540                                 mkdir(buf, 0700);
541                                 if (fchdir(fd) == -1)
542                                         fatal_cannot("change back to service directory");
543                                 close(fd);
544                         }
545                         else {
546                                 if ((errno != ENOENT) && (errno != EINVAL))
547                                         fatal_cannot("readlink ./log/supervise");
548                         }
549                 }
550                 svd[1].fdlock = xopen3("log/supervise/lock",
551                                 O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
552                 if (lock_ex(svd[1].fdlock) == -1)
553                         fatal_cannot("lock log/supervise/lock");
554                 close_on_exec_on(svd[1].fdlock);
555         }
556
557         mkfifo("log/supervise/control"+4, 0600);
558         svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY);
559         close_on_exec_on(svd[0].fdcontrol);
560         svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY);
561         close_on_exec_on(svd[0].fdcontrolwrite);
562         update_status(&svd[0]);
563         if (haslog) {
564                 mkfifo("log/supervise/control", 0600);
565                 svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY);
566                 close_on_exec_on(svd[1].fdcontrol);
567                 svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY);
568                 close_on_exec_on(svd[1].fdcontrolwrite);
569                 update_status(&svd[1]);
570         }
571         mkfifo("log/supervise/ok"+4, 0600);
572         fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY);
573         close_on_exec_on(fd);
574         if (haslog) {
575                 mkfifo("log/supervise/ok", 0600);
576                 fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY);
577                 close_on_exec_on(fd);
578         }
579         for (;;) {
580                 struct pollfd x[3];
581                 unsigned deadline;
582                 char ch;
583
584                 if (haslog)
585                         if (!svd[1].pid && svd[1].sd_want == W_UP)
586                                 startservice(&svd[1]);
587                 if (!svd[0].pid)
588                         if (svd[0].sd_want == W_UP || svd[0].state == S_FINISH)
589                                 startservice(&svd[0]);
590
591                 x[0].fd = selfpipe.rd;
592                 x[0].events = POLLIN;
593                 x[1].fd = svd[0].fdcontrol;
594                 x[1].events = POLLIN;
595                 /* x[2] is used only if haslog == 1 */
596                 x[2].fd = svd[1].fdcontrol;
597                 x[2].events = POLLIN;
598                 sig_unblock(SIGTERM);
599                 sig_unblock(SIGCHLD);
600                 poll(x, 2 + haslog, 3600*1000);
601                 sig_block(SIGTERM);
602                 sig_block(SIGCHLD);
603
604                 while (read(selfpipe.rd, &ch, 1) == 1)
605                         continue;
606
607                 for (;;) {
608                         pid_t child;
609                         int wstat;
610
611                         child = wait_any_nohang(&wstat);
612                         if (!child)
613                                 break;
614                         if ((child == -1) && (errno != EINTR))
615                                 break;
616                         if (child == svd[0].pid) {
617                                 svd[0].wstat = wstat;
618                                 svd[0].pid = 0;
619                                 pidchanged = 1;
620                                 svd[0].ctrl &= ~C_TERM;
621                                 if (svd[0].state != S_FINISH) {
622                                         fd = open_read("finish");
623                                         if (fd != -1) {
624                                                 close(fd);
625                                                 svd[0].state = S_FINISH;
626                                                 update_status(&svd[0]);
627                                                 continue;
628                                         }
629                                 }
630                                 svd[0].state = S_DOWN;
631                                 deadline = svd[0].start.tv_sec + 1;
632                                 gettimeofday_ns(&svd[0].start);
633                                 update_status(&svd[0]);
634                                 if (LESS(svd[0].start.tv_sec, deadline))
635                                         sleep(1);
636                         }
637                         if (haslog) {
638                                 if (child == svd[1].pid) {
639                                         svd[0].wstat = wstat;
640                                         svd[1].pid = 0;
641                                         pidchanged = 1;
642                                         svd[1].state = S_DOWN;
643                                         svd[1].ctrl &= ~C_TERM;
644                                         deadline = svd[1].start.tv_sec + 1;
645                                         gettimeofday_ns(&svd[1].start);
646                                         update_status(&svd[1]);
647                                         if (LESS(svd[1].start.tv_sec, deadline))
648                                                 sleep(1);
649                                 }
650                         }
651                 } /* for (;;) */
652                 if (read(svd[0].fdcontrol, &ch, 1) == 1)
653                         ctrl(&svd[0], ch);
654                 if (haslog)
655                         if (read(svd[1].fdcontrol, &ch, 1) == 1)
656                                 ctrl(&svd[1], ch);
657
658                 if (sigterm) {
659                         ctrl(&svd[0], 'x');
660                         sigterm = 0;
661                 }
662
663                 if (svd[0].sd_want == W_EXIT && svd[0].state == S_DOWN) {
664                         if (svd[1].pid == 0)
665                                 _exit(EXIT_SUCCESS);
666                         if (svd[1].sd_want != W_EXIT) {
667                                 svd[1].sd_want = W_EXIT;
668                                 /* stopservice(&svd[1]); */
669                                 update_status(&svd[1]);
670                                 close(logpipe.wr);
671                                 close(logpipe.rd);
672                         }
673                 }
674         } /* for (;;) */
675         /* not reached */
676         return 0;
677 }