sv,svc: fix NOEXEC fallout
[oweals/busybox.git] / runit / sv.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 /* Taken from http://smarden.org/runit/sv.8.html:
29
30 sv - control and manage services monitored by runsv
31
32 sv [-v] [-w sec] command services
33 /etc/init.d/service [-w sec] command
34
35 The sv program reports the current status and controls the state of services
36 monitored by the runsv(8) supervisor.
37
38 services consists of one or more arguments, each argument naming a directory
39 service used by runsv(8). If service doesn't start with a dot or slash and
40 doesn't end with a slash, it is searched in the default services directory
41 /var/service/, otherwise relative to the current directory.
42
43 command is one of up, down, status, once, pause, cont, hup, alarm, interrupt,
44 1, 2, term, kill, or exit, or start, stop, reload, restart, shutdown,
45 force-stop, force-reload, force-restart, force-shutdown, try-restart.
46
47 status
48     Report the current status of the service, and the appendant log service
49     if available, to standard output.
50 up
51     If the service is not running, start it. If the service stops, restart it.
52 down
53     If the service is running, send it the TERM signal, and the CONT signal.
54     If ./run exits, start ./finish if it exists. After it stops, do not
55     restart service.
56 once
57     If the service is not running, start it. Do not restart it if it stops.
58 pause cont hup alarm interrupt quit 1 2 term kill
59     If the service is running, send it the STOP, CONT, HUP, ALRM, INT, QUIT,
60     USR1, USR2, TERM, or KILL signal respectively.
61 exit
62     If the service is running, send it the TERM signal, and the CONT signal.
63     Do not restart the service. If the service is down, and no log service
64     exists, runsv(8) exits. If the service is down and a log service exists,
65     runsv(8) closes the standard input of the log service and waits for it to
66     terminate. If the log service is down, runsv(8) exits. This command is
67     ignored if it is given to an appendant log service.
68
69 sv actually looks only at the first character of above commands.
70
71 Commands compatible to LSB init script actions:
72
73 status
74     Same as status.
75 start
76     Same as up, but wait up to 7 seconds for the command to take effect.
77     Then report the status or timeout. If the script ./check exists in
78     the service directory, sv runs this script to check whether the service
79     is up and available; it's considered to be available if ./check exits
80     with 0.
81 stop
82     Same as down, but wait up to 7 seconds for the service to become down.
83     Then report the status or timeout.
84 reload
85     Same as hup, and additionally report the status afterwards.
86 restart
87     Send the commands term, cont, and up to the service, and wait up to
88     7 seconds for the service to restart. Then report the status or timeout.
89     If the script ./check exists in the service directory, sv runs this script
90     to check whether the service is up and available again; it's considered
91     to be available if ./check exits with 0.
92 shutdown
93     Same as exit, but wait up to 7 seconds for the runsv(8) process
94     to terminate. Then report the status or timeout.
95 force-stop
96     Same as down, but wait up to 7 seconds for the service to become down.
97     Then report the status, and on timeout send the service the kill command.
98 force-reload
99     Send the service the term and cont commands, and wait up to
100     7 seconds for the service to restart. Then report the status,
101     and on timeout send the service the kill command.
102 force-restart
103     Send the service the term, cont and up commands, and wait up to
104     7 seconds for the service to restart. Then report the status, and
105     on timeout send the service the kill command. If the script ./check
106     exists in the service directory, sv runs this script to check whether
107     the service is up and available again; it's considered to be available
108     if ./check exits with 0.
109 force-shutdown
110     Same as exit, but wait up to 7 seconds for the runsv(8) process to
111     terminate. Then report the status, and on timeout send the service
112     the kill command.
113 try-restart
114     if the service is running, send it the term and cont commands, and wait up to
115     7 seconds for the service to restart. Then report the status or timeout.
116
117 Additional Commands
118
119 check
120     Check for the service to be in the state that's been requested. Wait up to
121     7 seconds for the service to reach the requested state, then report
122     the status or timeout. If the requested state of the service is up,
123     and the script ./check exists in the service directory, sv runs
124     this script to check whether the service is up and running;
125     it's considered to be up if ./check exits with 0.
126
127 Options
128
129 -v
130     If the command is up, down, term, once, cont, or exit, then wait up to 7
131     seconds for the command to take effect. Then report the status or timeout.
132 -w sec
133     Override the default timeout of 7 seconds with sec seconds. Implies -v.
134
135 Environment
136
137 SVDIR
138     The environment variable $SVDIR overrides the default services directory
139     /var/service.
140 SVWAIT
141     The environment variable $SVWAIT overrides the default 7 seconds to wait
142     for a command to take effect. It is overridden by the -w option.
143
144 Exit Codes
145     sv exits 0, if the command was successfully sent to all services, and,
146     if it was told to wait, the command has taken effect to all services.
147
148     For each service that caused an error (e.g. the directory is not
149     controlled by a runsv(8) process, or sv timed out while waiting),
150     sv increases the exit code by one and exits non zero. The maximum
151     is 99. sv exits 100 on error.
152 */
153
154 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
155
156 //config:config SV
157 //config:       bool "sv (7.8 kb)"
158 //config:       default y
159 //config:       help
160 //config:       sv reports the current status and controls the state of services
161 //config:       monitored by the runsv supervisor.
162 //config:
163 //config:config SV_DEFAULT_SERVICE_DIR
164 //config:       string "Default directory for services"
165 //config:       default "/var/service"
166 //config:       depends on SV
167 //config:       help
168 //config:       Default directory for services.
169 //config:       Defaults to "/var/service"
170 //config:
171 //config:config SVC
172 //config:       bool "svc (7.8 kb)"
173 //config:       default y
174 //config:       help
175 //config:       svc controls the state of services monitored by the runsv supervisor.
176 //config:       It is comaptible with daemontools command with the same name.
177
178 //applet:IF_SV( APPLET_NOEXEC(sv,  sv,  BB_DIR_USR_BIN, BB_SUID_DROP, sv ))
179 //applet:IF_SVC(APPLET_NOEXEC(svc, svc, BB_DIR_USR_BIN, BB_SUID_DROP, svc))
180
181 //kbuild:lib-$(CONFIG_SV) += sv.o
182 //kbuild:lib-$(CONFIG_SVC) += sv.o
183
184 #include <sys/file.h>
185 #include "libbb.h"
186 #include "common_bufsiz.h"
187 #include "runit_lib.h"
188
189 struct globals {
190         const char *acts;
191         char **service;
192         unsigned rc;
193 /* "Bernstein" time format: unix + 0x400000000000000aULL */
194         uint64_t tstart, tnow;
195         svstatus_t svstatus;
196         smallint islog;
197 } FIX_ALIASING;
198 #define G (*(struct globals*)bb_common_bufsiz1)
199 #define acts         (G.acts        )
200 #define service      (G.service     )
201 #define rc           (G.rc          )
202 #define tstart       (G.tstart      )
203 #define tnow         (G.tnow        )
204 #define svstatus     (G.svstatus    )
205 #define islog        (G.islog       )
206 #define INIT_G() do { \
207         setup_common_bufsiz(); \
208         /* need to zero out, we are NOEXEC */ \
209         rc = EXIT_SUCCESS; \
210         islog = 0; \
211         /* other fields need not be zero */ \
212 } while (0)
213
214
215 #define str_equal(s,t) (strcmp((s), (t)) == 0)
216
217
218 static void fatal_cannot(const char *m1) NORETURN;
219 static void fatal_cannot(const char *m1)
220 {
221         bb_perror_msg("fatal: can't %s", m1);
222         _exit(151);
223 }
224
225 static void out(const char *p, const char *m1)
226 {
227         printf("%s%s%s: %s", p, *service, islog ? "/log" : "", m1);
228         if (errno) {
229                 printf(": %s", strerror(errno));
230         }
231         bb_putchar('\n'); /* will also flush the output */
232 }
233
234 #define WARN    "warning: "
235 #define OK      "ok: "
236
237 static void fail(const char *m1)
238 {
239         ++rc;
240         out("fail: ", m1);
241 }
242 static void failx(const char *m1)
243 {
244         errno = 0;
245         fail(m1);
246 }
247 static void warn(const char *m1)
248 {
249         ++rc;
250         /* "warning: <service>: <m1>\n" */
251         out("warning: ", m1);
252 }
253 static void ok(const char *m1)
254 {
255         errno = 0;
256         out(OK, m1);
257 }
258
259 static int svstatus_get(void)
260 {
261         int fd, r;
262
263         fd = open("supervise/ok", O_WRONLY|O_NDELAY);
264         if (fd == -1) {
265                 if (errno == ENODEV) {
266                         *acts == 'x' ? ok("runsv not running")
267                                      : failx("runsv not running");
268                         return 0;
269                 }
270                 warn("can't open supervise/ok");
271                 return -1;
272         }
273         close(fd);
274         fd = open("supervise/status", O_RDONLY|O_NDELAY);
275         if (fd == -1) {
276                 warn("can't open supervise/status");
277                 return -1;
278         }
279         r = read(fd, &svstatus, 20);
280         close(fd);
281         switch (r) {
282         case 20:
283                 break;
284         case -1:
285                 warn("can't read supervise/status");
286                 return -1;
287         default:
288                 errno = 0;
289                 warn("can't read supervise/status: bad format");
290                 return -1;
291         }
292         return 1;
293 }
294
295 static unsigned svstatus_print(const char *m)
296 {
297         int diff;
298         int pid;
299         int normallyup = 0;
300         struct stat s;
301         uint64_t timestamp;
302
303         if (stat("down", &s) == -1) {
304                 if (errno != ENOENT) {
305                         bb_perror_msg(WARN"can't stat %s/down", *service);
306                         return 0;
307                 }
308                 normallyup = 1;
309         }
310         pid = SWAP_LE32(svstatus.pid_le32);
311         timestamp = SWAP_BE64(svstatus.time_be64);
312         switch (svstatus.run_or_finish) {
313                 case 0: printf("down: "); break;
314                 case 1: printf("run: "); break;
315                 case 2: printf("finish: "); break;
316         }
317         printf("%s: ", m);
318         if (svstatus.run_or_finish)
319                 printf("(pid %d) ", pid);
320         diff = tnow - timestamp;
321         printf("%us", (diff < 0 ? 0 : diff));
322         if (pid) {
323                 if (!normallyup) printf(", normally down");
324                 if (svstatus.paused) printf(", paused");
325                 if (svstatus.want == 'd') printf(", want down");
326                 if (svstatus.got_term) printf(", got TERM");
327         } else {
328                 if (normallyup) printf(", normally up");
329                 if (svstatus.want == 'u') printf(", want up");
330         }
331         return pid ? 1 : 2;
332 }
333
334 static int status(const char *unused UNUSED_PARAM)
335 {
336         int r;
337
338         if (svstatus_get() <= 0)
339                 return 0;
340
341         r = svstatus_print(*service);
342         islog = 1;
343         if (chdir("log") == -1) {
344                 if (errno != ENOENT) {
345                         printf("; ");
346                         warn("can't change directory");
347                 } else
348                         bb_putchar('\n');
349         } else {
350                 printf("; ");
351                 if (svstatus_get()) {
352                         r = svstatus_print("log");
353                         bb_putchar('\n');
354                 }
355         }
356         islog = 0;
357         return r;
358 }
359
360 static int checkscript(void)
361 {
362         char *prog[2];
363         struct stat s;
364         int pid, w;
365
366         if (stat("check", &s) == -1) {
367                 if (errno == ENOENT) return 1;
368                 bb_perror_msg(WARN"can't stat %s/check", *service);
369                 return 0;
370         }
371         /* if (!(s.st_mode & S_IXUSR)) return 1; */
372         prog[0] = (char*)"./check";
373         prog[1] = NULL;
374         pid = spawn(prog);
375         if (pid <= 0) {
376                 bb_perror_msg(WARN"can't %s child %s/check", "run", *service);
377                 return 0;
378         }
379         while (safe_waitpid(pid, &w, 0) == -1) {
380                 bb_perror_msg(WARN"can't %s child %s/check", "wait for", *service);
381                 return 0;
382         }
383         return WEXITSTATUS(w) == 0;
384 }
385
386 static int check(const char *a)
387 {
388         int r;
389         unsigned pid_le32;
390         uint64_t timestamp;
391
392         r = svstatus_get();
393         if (r == -1)
394                 return -1;
395         while (*a) {
396                 if (r == 0) {
397                         if (*a == 'x')
398                                 return 1;
399                         return -1;
400                 }
401                 pid_le32 = svstatus.pid_le32;
402                 switch (*a) {
403                 case 'x':
404                         return 0;
405                 case 'u':
406                         if (!pid_le32 || svstatus.run_or_finish != 1)
407                                 return 0;
408                         if (!checkscript())
409                                 return 0;
410                         break;
411                 case 'd':
412                         if (pid_le32 || svstatus.run_or_finish != 0)
413                                 return 0;
414                         break;
415                 case 'C':
416                         if (pid_le32 && !checkscript())
417                                 return 0;
418                         break;
419                 case 't':
420                 case 'k':
421                         if (!pid_le32 && svstatus.want == 'd')
422                                 break;
423                         timestamp = SWAP_BE64(svstatus.time_be64);
424                         if ((tstart > timestamp) || !pid_le32 || svstatus.got_term || !checkscript())
425                                 return 0;
426                         break;
427                 case 'o':
428                         timestamp = SWAP_BE64(svstatus.time_be64);
429                         if ((!pid_le32 && tstart > timestamp) || (pid_le32 && svstatus.want != 'd'))
430                                 return 0;
431                         break;
432                 case 'p':
433                         if (pid_le32 && !svstatus.paused)
434                                 return 0;
435                         break;
436                 case 'c':
437                         if (pid_le32 && svstatus.paused)
438                                 return 0;
439                         break;
440                 }
441                 ++a;
442         }
443         printf(OK);
444         svstatus_print(*service);
445         bb_putchar('\n'); /* will also flush the output */
446         return 1;
447 }
448
449 static int control(const char *a)
450 {
451         int fd, r, l;
452
453         if (svstatus_get() <= 0)
454                 return -1;
455         if (svstatus.want == *a && (*a != 'd' || svstatus.got_term == 1))
456                 return 0;
457         fd = open("supervise/control", O_WRONLY|O_NDELAY);
458         if (fd == -1) {
459                 if (errno != ENODEV)
460                         warn("can't open supervise/control");
461                 else
462                         *a == 'x' ? ok("runsv not running") : failx("runsv not running");
463                 return -1;
464         }
465         l = strlen(a);
466         r = write(fd, a, l);
467         close(fd);
468         if (r != l) {
469                 warn("can't write to supervise/control");
470                 return -1;
471         }
472         return 1;
473 }
474
475 //usage:#define sv_trivial_usage
476 //usage:       "[-v] [-w SEC] CMD SERVICE_DIR..."
477 //usage:#define sv_full_usage "\n\n"
478 //usage:       "Control services monitored by runsv supervisor.\n"
479 //usage:       "Commands (only first character is enough):\n"
480 //usage:       "\n"
481 //usage:       "status: query service status\n"
482 //usage:       "up: if service isn't running, start it. If service stops, restart it\n"
483 //usage:       "once: like 'up', but if service stops, don't restart it\n"
484 //usage:       "down: send TERM and CONT signals. If ./run exits, start ./finish\n"
485 //usage:       "        if it exists. After it stops, don't restart service\n"
486 //usage:       "exit: send TERM and CONT signals to service and log service. If they exit,\n"
487 //usage:       "        runsv exits too\n"
488 //usage:       "pause, cont, hup, alarm, interrupt, quit, 1, 2, term, kill: send\n"
489 //usage:       "STOP, CONT, HUP, ALRM, INT, QUIT, USR1, USR2, TERM, KILL signal to service"
490 static int sv(char **argv)
491 {
492         char *x;
493         char *action;
494         const char *varservice = CONFIG_SV_DEFAULT_SERVICE_DIR;
495         unsigned waitsec = 7;
496         smallint kll = 0;
497         int verbose = 0;
498         int (*act)(const char*);
499         int (*cbk)(const char*);
500         int curdir;
501
502         INIT_G();
503
504         xfunc_error_retval = 100;
505
506         x = getenv("SVDIR");
507         if (x) varservice = x;
508         x = getenv("SVWAIT");
509         if (x) waitsec = xatou(x);
510
511         opt_complementary = "vv"; /* -w N, -v is a counter */
512         getopt32(argv, "w:+v", &waitsec, &verbose);
513         argv += optind;
514         action = *argv++;
515         if (!action || !*argv) bb_show_usage();
516
517         tnow = time(NULL) + 0x400000000000000aULL;
518         tstart = tnow;
519         curdir = open(".", O_RDONLY|O_NDELAY);
520         if (curdir == -1)
521                 fatal_cannot("open current directory");
522
523         act = &control;
524         acts = "s";
525         cbk = &check;
526
527         switch (*action) {
528         case 'x':
529         case 'e':
530                 acts = "x";
531                 if (!verbose) cbk = NULL;
532                 break;
533         case 'X':
534         case 'E':
535                 acts = "x";
536                 kll = 1;
537                 break;
538         case 'D':
539                 acts = "d";
540                 kll = 1;
541                 break;
542         case 'T':
543                 acts = "tc";
544                 kll = 1;
545                 break;
546         case 't':
547                 if (str_equal(action, "try-restart")) {
548                         acts = "tc";
549                         break;
550                 }
551         case 'c':
552                 if (str_equal(action, "check")) {
553                         act = NULL;
554                         acts = "C";
555                         break;
556                 }
557         case 'u': case 'd': case 'o': case 'p': case 'h':
558         case 'a': case 'i': case 'k': case 'q': case '1': case '2':
559                 action[1] = '\0';
560                 acts = action;
561                 if (!verbose)
562                         cbk = NULL;
563                 break;
564         case 's':
565                 if (str_equal(action, "shutdown")) {
566                         acts = "x";
567                         break;
568                 }
569                 if (str_equal(action, "start")) {
570                         acts = "u";
571                         break;
572                 }
573                 if (str_equal(action, "stop")) {
574                         acts = "d";
575                         break;
576                 }
577                 /* "status" */
578                 act = &status;
579                 cbk = NULL;
580                 break;
581         case 'r':
582                 if (str_equal(action, "restart")) {
583                         acts = "tcu";
584                         break;
585                 }
586                 if (str_equal(action, "reload")) {
587                         acts = "h";
588                         break;
589                 }
590                 bb_show_usage();
591         case 'f':
592                 if (str_equal(action, "force-reload")) {
593                         acts = "tc";
594                         kll = 1;
595                         break;
596                 }
597                 if (str_equal(action, "force-restart")) {
598                         acts = "tcu";
599                         kll = 1;
600                         break;
601                 }
602                 if (str_equal(action, "force-shutdown")) {
603                         acts = "x";
604                         kll = 1;
605                         break;
606                 }
607                 if (str_equal(action, "force-stop")) {
608                         acts = "d";
609                         kll = 1;
610                         break;
611                 }
612         default:
613                 bb_show_usage();
614         }
615
616         service = argv;
617         while ((x = *service) != NULL) {
618                 if (x[0] != '/' && x[0] != '.'
619                  && x[0] != '\0' && x[strlen(x) - 1] != '/'
620                 ) {
621                         if (chdir(varservice) == -1)
622                                 goto chdir_failed_0;
623                 }
624                 if (chdir(x) == -1) {
625  chdir_failed_0:
626                         fail("can't change to service directory");
627                         goto nullify_service_0;
628                 }
629                 if (act && (act(acts) == -1)) {
630  nullify_service_0:
631                         *service = (char*) -1L; /* "dead" */
632                 }
633                 if (fchdir(curdir) == -1)
634                         fatal_cannot("change to original directory");
635                 service++;
636         }
637
638         if (cbk) while (1) {
639                 int want_exit;
640                 int diff;
641
642                 diff = tnow - tstart;
643                 service = argv;
644                 want_exit = 1;
645                 while ((x = *service) != NULL) {
646                         if (x == (char*) -1L) /* "dead" */
647                                 goto next;
648                         if (x[0] != '/' && x[0] != '.') {
649                                 if (chdir(varservice) == -1)
650                                         goto chdir_failed;
651                         }
652                         if (chdir(x) == -1) {
653  chdir_failed:
654                                 fail("can't change to service directory");
655                                 goto nullify_service;
656                         }
657                         if (cbk(acts) != 0)
658                                 goto nullify_service;
659                         want_exit = 0;
660                         if (diff >= waitsec) {
661                                 printf(kll ? "kill: " : "timeout: ");
662                                 if (svstatus_get() > 0) {
663                                         svstatus_print(x);
664                                         ++rc;
665                                 }
666                                 bb_putchar('\n'); /* will also flush the output */
667                                 if (kll)
668                                         control("k");
669  nullify_service:
670                                 *service = (char*) -1L; /* "dead" */
671                         }
672                         if (fchdir(curdir) == -1)
673                                 fatal_cannot("change to original directory");
674  next:
675                         service++;
676                 }
677                 if (want_exit) break;
678                 usleep(420000);
679                 tnow = time(NULL) + 0x400000000000000aULL;
680         }
681         return rc > 99 ? 99 : rc;
682 }
683
684 #if ENABLE_SV
685 int sv_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
686 int sv_main(int argc UNUSED_PARAM, char **argv)
687 {
688         return sv(argv);
689 }
690 #endif
691
692 //usage:#define svc_trivial_usage
693 //usage:       "[-udopchaitkx] SERVICE_DIR..."
694 //usage:#define svc_full_usage "\n\n"
695 //usage:       "Control services monitored by runsv supervisor"
696 //usage:   "\n"
697 //usage:   "\n""        -u      If service is not running, start it; restart if it stops"
698 //usage:   "\n""        -d      If service is running, send TERM+CONT signals; do not restart it"
699 //usage:   "\n""        -o      Once: if service is not running, start it; do not restart it"
700 //usage:   "\n""        -pchaitk Send STOP, CONT, HUP, ALRM, INT, TERM, KILL signal to service"
701 //usage:   "\n""        -x      Exit: runsv will exit as soon as the service is down"
702 #if ENABLE_SVC
703 int svc_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
704 int svc_main(int argc UNUSED_PARAM, char **argv)
705 {
706         char command[2];
707         const char *optstring;
708         unsigned opts;
709
710         INIT_G();
711
712         optstring = "udopchaitkx";
713         opts = getopt32(argv, optstring);
714         argv += optind;
715         if (!argv[0] || !opts)
716                 bb_show_usage();
717
718         argv -= 2;
719         if (optind > 2) {
720                 argv--;
721                 argv[2] = (char*)"--";
722         }
723         argv[0] = (char*)"sv";
724         argv[1] = command;
725         command[1] = '\0';
726
727         do {
728                 if (opts & 1) {
729                         int r;
730
731                         command[0] = *optstring;
732
733                         /* getopt() was already called by getopt32():
734                          * reset the libc getopt() function's internal state.
735                          */
736                         GETOPT_RESET();
737                         r = sv(argv);
738                         if (r)
739                                 return 1;
740                 }
741                 optstring++;
742                 opts >>= 1;
743         } while (opts);
744
745         return 0;
746 }
747 #endif