5b221e90ae4e51617af8198937d85aeeed29040f
[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         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         dir = single_argv(argv);
467
468         xpiped_pair(selfpipe);
469         close_on_exec_on(selfpipe.rd);
470         close_on_exec_on(selfpipe.wr);
471         ndelay_on(selfpipe.rd);
472         ndelay_on(selfpipe.wr);
473
474         sig_block(SIGCHLD);
475         bb_signals_recursive_norestart(1 << SIGCHLD, s_child);
476         sig_block(SIGTERM);
477         bb_signals_recursive_norestart(1 << SIGTERM, s_term);
478
479         xchdir(dir);
480         /* bss: svd[0].pid = 0; */
481         if (S_DOWN) svd[0].state = S_DOWN; /* otherwise already 0 (bss) */
482         if (C_NOOP) svd[0].ctrl = C_NOOP;
483         if (W_UP) svd[0].sd_want = W_UP;
484         /* bss: svd[0].islog = 0; */
485         /* bss: svd[1].pid = 0; */
486         gettimeofday_ns(&svd[0].start);
487         if (stat("down", &s) != -1)
488                 svd[0].sd_want = W_DOWN;
489
490         if (stat("log", &s) == -1) {
491                 if (errno != ENOENT)
492                         warn_cannot("stat ./log");
493         } else {
494                 if (!S_ISDIR(s.st_mode)) {
495                         errno = 0;
496                         warn_cannot("stat log/down: log is not a directory");
497                 } else {
498                         haslog = 1;
499                         svd[1].state = S_DOWN;
500                         svd[1].ctrl = C_NOOP;
501                         svd[1].sd_want = W_UP;
502                         svd[1].islog = 1;
503                         gettimeofday_ns(&svd[1].start);
504                         if (stat("log/down", &s) != -1)
505                                 svd[1].sd_want = W_DOWN;
506                         xpiped_pair(logpipe);
507                         close_on_exec_on(logpipe.rd);
508                         close_on_exec_on(logpipe.wr);
509                 }
510         }
511
512         if (mkdir("supervise", 0700) == -1) {
513                 r = readlink("supervise", buf, sizeof(buf));
514                 if (r != -1) {
515                         if (r == sizeof(buf))
516                                 fatal2x_cannot("readlink ./supervise", ": name too long");
517                         buf[r] = 0;
518                         mkdir(buf, 0700);
519                 } else {
520                         if ((errno != ENOENT) && (errno != EINVAL))
521                                 fatal_cannot("readlink ./supervise");
522                 }
523         }
524         svd[0].fdlock = xopen3("log/supervise/lock"+4,
525                         O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
526         if (lock_exnb(svd[0].fdlock) == -1)
527                 fatal_cannot("lock supervise/lock");
528         close_on_exec_on(svd[0].fdlock);
529         if (haslog) {
530                 if (mkdir("log/supervise", 0700) == -1) {
531                         r = readlink("log/supervise", buf, 256);
532                         if (r != -1) {
533                                 if (r == 256)
534                                         fatal2x_cannot("readlink ./log/supervise", ": name too long");
535                                 buf[r] = 0;
536                                 fd = xopen(".", O_RDONLY|O_NDELAY);
537                                 xchdir("./log");
538                                 mkdir(buf, 0700);
539                                 if (fchdir(fd) == -1)
540                                         fatal_cannot("change back to service directory");
541                                 close(fd);
542                         }
543                         else {
544                                 if ((errno != ENOENT) && (errno != EINVAL))
545                                         fatal_cannot("readlink ./log/supervise");
546                         }
547                 }
548                 svd[1].fdlock = xopen3("log/supervise/lock",
549                                 O_WRONLY|O_NDELAY|O_APPEND|O_CREAT, 0600);
550                 if (lock_ex(svd[1].fdlock) == -1)
551                         fatal_cannot("lock log/supervise/lock");
552                 close_on_exec_on(svd[1].fdlock);
553         }
554
555         mkfifo("log/supervise/control"+4, 0600);
556         svd[0].fdcontrol = xopen("log/supervise/control"+4, O_RDONLY|O_NDELAY);
557         close_on_exec_on(svd[0].fdcontrol);
558         svd[0].fdcontrolwrite = xopen("log/supervise/control"+4, O_WRONLY|O_NDELAY);
559         close_on_exec_on(svd[0].fdcontrolwrite);
560         update_status(&svd[0]);
561         if (haslog) {
562                 mkfifo("log/supervise/control", 0600);
563                 svd[1].fdcontrol = xopen("log/supervise/control", O_RDONLY|O_NDELAY);
564                 close_on_exec_on(svd[1].fdcontrol);
565                 svd[1].fdcontrolwrite = xopen("log/supervise/control", O_WRONLY|O_NDELAY);
566                 close_on_exec_on(svd[1].fdcontrolwrite);
567                 update_status(&svd[1]);
568         }
569         mkfifo("log/supervise/ok"+4, 0600);
570         fd = xopen("log/supervise/ok"+4, O_RDONLY|O_NDELAY);
571         close_on_exec_on(fd);
572         if (haslog) {
573                 mkfifo("log/supervise/ok", 0600);
574                 fd = xopen("log/supervise/ok", O_RDONLY|O_NDELAY);
575                 close_on_exec_on(fd);
576         }
577         for (;;) {
578                 struct pollfd x[3];
579                 unsigned deadline;
580                 char ch;
581
582                 if (haslog)
583                         if (!svd[1].pid && svd[1].sd_want == W_UP)
584                                 startservice(&svd[1]);
585                 if (!svd[0].pid)
586                         if (svd[0].sd_want == W_UP || svd[0].state == S_FINISH)
587                                 startservice(&svd[0]);
588
589                 x[0].fd = selfpipe.rd;
590                 x[0].events = POLLIN;
591                 x[1].fd = svd[0].fdcontrol;
592                 x[1].events = POLLIN;
593                 /* x[2] is used only if haslog == 1 */
594                 x[2].fd = svd[1].fdcontrol;
595                 x[2].events = POLLIN;
596                 sig_unblock(SIGTERM);
597                 sig_unblock(SIGCHLD);
598                 poll(x, 2 + haslog, 3600*1000);
599                 sig_block(SIGTERM);
600                 sig_block(SIGCHLD);
601
602                 while (read(selfpipe.rd, &ch, 1) == 1)
603                         continue;
604
605                 for (;;) {
606                         pid_t child;
607                         int wstat;
608
609                         child = wait_any_nohang(&wstat);
610                         if (!child)
611                                 break;
612                         if ((child == -1) && (errno != EINTR))
613                                 break;
614                         if (child == svd[0].pid) {
615                                 svd[0].wstat = wstat;
616                                 svd[0].pid = 0;
617                                 pidchanged = 1;
618                                 svd[0].ctrl &= ~C_TERM;
619                                 if (svd[0].state != S_FINISH) {
620                                         fd = open_read("finish");
621                                         if (fd != -1) {
622                                                 close(fd);
623                                                 svd[0].state = S_FINISH;
624                                                 update_status(&svd[0]);
625                                                 continue;
626                                         }
627                                 }
628                                 svd[0].state = S_DOWN;
629                                 deadline = svd[0].start.tv_sec + 1;
630                                 gettimeofday_ns(&svd[0].start);
631                                 update_status(&svd[0]);
632                                 if (LESS(svd[0].start.tv_sec, deadline))
633                                         sleep(1);
634                         }
635                         if (haslog) {
636                                 if (child == svd[1].pid) {
637                                         svd[0].wstat = wstat;
638                                         svd[1].pid = 0;
639                                         pidchanged = 1;
640                                         svd[1].state = S_DOWN;
641                                         svd[1].ctrl &= ~C_TERM;
642                                         deadline = svd[1].start.tv_sec + 1;
643                                         gettimeofday_ns(&svd[1].start);
644                                         update_status(&svd[1]);
645                                         if (LESS(svd[1].start.tv_sec, deadline))
646                                                 sleep(1);
647                                 }
648                         }
649                 } /* for (;;) */
650                 if (read(svd[0].fdcontrol, &ch, 1) == 1)
651                         ctrl(&svd[0], ch);
652                 if (haslog)
653                         if (read(svd[1].fdcontrol, &ch, 1) == 1)
654                                 ctrl(&svd[1], ch);
655
656                 if (sigterm) {
657                         ctrl(&svd[0], 'x');
658                         sigterm = 0;
659                 }
660
661                 if (svd[0].sd_want == W_EXIT && svd[0].state == S_DOWN) {
662                         if (svd[1].pid == 0)
663                                 _exit(EXIT_SUCCESS);
664                         if (svd[1].sd_want != W_EXIT) {
665                                 svd[1].sd_want = W_EXIT;
666                                 /* stopservice(&svd[1]); */
667                                 update_status(&svd[1]);
668                                 close(logpipe.wr);
669                                 close(logpipe.rd);
670                         }
671                 }
672         } /* for (;;) */
673         /* not reached */
674         return 0;
675 }