diff options
| author | Laurent Bercot <ska-skaware@skarnet.org> | 2025-06-06 13:48:20 +0000 |
|---|---|---|
| committer | Laurent Bercot <ska@appnovation.com> | 2025-06-06 13:48:20 +0000 |
| commit | 3f75ffee7c0c7d6b70cf305d60435ac862ea9ab5 (patch) | |
| tree | 45d86cdc414e6534305d2b723666c1291d74b940 | |
| parent | 73988ba1cfa375582c86302116192fb8ec567f48 (diff) | |
| download | s6-frontend-3f75ffee7c0c7d6b70cf305d60435ac862ea9ab5.tar.gz | |
Implement "s6 process"
Signed-off-by: Laurent Bercot <ska@appnovation.com>
| -rw-r--r-- | package/deps.mak | 10 | ||||
| -rw-r--r-- | src/s6/deps-exe/s6 | 9 | ||||
| -rw-r--r-- | src/s6/process.c | 33 | ||||
| -rw-r--r-- | src/s6/process_help.c | 16 | ||||
| -rw-r--r-- | src/s6/process_kill.c | 94 | ||||
| -rw-r--r-- | src/s6/process_restart.c | 38 | ||||
| -rw-r--r-- | src/s6/process_start.c | 38 | ||||
| -rw-r--r-- | src/s6/process_status.c | 108 | ||||
| -rw-r--r-- | src/s6/process_stop.c | 38 | ||||
| -rw-r--r-- | src/s6/process_util.c | 63 | ||||
| -rw-r--r-- | src/s6/s6-internal.h | 20 | ||||
| -rw-r--r-- | src/s6/s6.c | 27 |
12 files changed, 491 insertions, 3 deletions
diff --git a/package/deps.mak b/package/deps.mak index 19ddac1..e2fa383 100644 --- a/package/deps.mak +++ b/package/deps.mak @@ -8,6 +8,14 @@ src/alias/s6-frontend-alias-sv.o src/alias/s6-frontend-alias-sv.lo: src/alias/s6 src/alias/s6-frontend-alias.o src/alias/s6-frontend-alias.lo: src/alias/s6-frontend-alias.c src/include/s6-frontend/config.h src/config/s6-frontend-config-preprocess.o src/config/s6-frontend-config-preprocess.lo: src/config/s6-frontend-config-preprocess.c src/s6/help.o src/s6/help.lo: src/s6/help.c src/s6/s6-internal.h +src/s6/process.o src/s6/process.lo: src/s6/process.c src/s6/s6-internal.h +src/s6/process_help.o src/s6/process_help.lo: src/s6/process_help.c src/s6/s6-internal.h +src/s6/process_kill.o src/s6/process_kill.lo: src/s6/process_kill.c src/s6/s6-internal.h +src/s6/process_restart.o src/s6/process_restart.lo: src/s6/process_restart.c src/s6/s6-internal.h +src/s6/process_start.o src/s6/process_start.lo: src/s6/process_start.c src/s6/s6-internal.h +src/s6/process_status.o src/s6/process_status.lo: src/s6/process_status.c src/s6/s6-internal.h +src/s6/process_stop.o src/s6/process_stop.lo: src/s6/process_stop.c src/s6/s6-internal.h +src/s6/process_util.o src/s6/process_util.lo: src/s6/process_util.c src/s6/s6-internal.h src/s6/s6.o src/s6/s6.lo: src/s6/s6.c src/include/s6-frontend/config.h src/s6/s6-internal.h src/s6/util.o src/s6/util.lo: src/s6/util.c src/s6/s6-internal.h src/s6/version.o src/s6/version.lo: src/s6/version.c src/s6/s6-internal.h @@ -21,5 +29,5 @@ s6-frontend-alias-sv: src/alias/s6-frontend-alias-sv.o -ls6 -lskarnet s6-frontend-config-preprocess: EXTRA_LIBS := s6-frontend-config-preprocess: src/config/s6-frontend-config-preprocess.o -lskarnet s6: EXTRA_LIBS := -s6: src/s6/s6.o src/s6/help.o src/s6/util.o src/s6/version.o -lskarnet +s6: src/s6/s6.o src/s6/help.o src/s6/util.o src/s6/process.o src/s6/process_help.o src/s6/process_kill.o src/s6/process_restart.o src/s6/process_start.o src/s6/process_status.o src/s6/process_stop.o src/s6/process_util.o src/s6/version.o -ls6 -lskarnet INTERNAL_LIBS := diff --git a/src/s6/deps-exe/s6 b/src/s6/deps-exe/s6 index 92e3455..6bfee46 100644 --- a/src/s6/deps-exe/s6 +++ b/src/s6/deps-exe/s6 @@ -1,4 +1,13 @@ help.o util.o +process.o +process_help.o +process_kill.o +process_restart.o +process_start.o +process_status.o +process_stop.o +process_util.o version.o +-ls6 -lskarnet diff --git a/src/s6/process.c b/src/s6/process.c new file mode 100644 index 0000000..bd14c67 --- /dev/null +++ b/src/s6/process.c @@ -0,0 +1,33 @@ +/* ISC license. */ + +#include <skalibs/posixplz.h> +#include <skalibs/strerr.h> +#include <skalibs/cspawn.h> +#include <skalibs/djbunix.h> +#include <s6/config.h> + +#include "s6-internal.h" + +#define USAGE "s6 process [ process options ] subcommand [ subcommand options ] services... Type \"s6 process help\" for details." +#define dieusage() strerr_dieusage(100, USAGE) + +static struct command_s const process_commands[] = +{ + { .s = "help", .f = &process_help }, + { .s = "kill", .f = &process_kill }, + { .s = "restart", .f = &process_restart }, + { .s = "start", .f = &process_start }, + { .s = "status", .f = &process_status }, + { .s = "stop", .f = &process_stop }, +} ; + +int process (char const *const *argv) +{ + struct command_s *cmd ; + + PROG = "s6 process" ; + if (!*argv) dieusage() ; + cmd = BSEARCH(struct command_s, *argv, process_commands) ; + if (!cmd) dieusage() ; + return (*cmd->f)(++argv) ; +} diff --git a/src/s6/process_help.c b/src/s6/process_help.c new file mode 100644 index 0000000..1e51ebe --- /dev/null +++ b/src/s6/process_help.c @@ -0,0 +1,16 @@ +/* ISC license. */ + +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> + +#include "s6-internal.h" + +#define HELP_MESSAGE "This is the \"s6 process\" help message.\n" + +int process_help (char const *const *argv) +{ + (void)argv ; + if (!buffer_putsflush(buffer_1, HELP_MESSAGE)) + strerr_diefu1sys(111, "write to stdout") ; + return 0 ; +} diff --git a/src/s6/process_kill.c b/src/s6/process_kill.c new file mode 100644 index 0000000..fdad688 --- /dev/null +++ b/src/s6/process_kill.c @@ -0,0 +1,94 @@ +/* ISC license. */ + +#include <string.h> +#include <signal.h> +#include <unistd.h> + +#include <skalibs/uint64.h> +#include <skalibs/posixplz.h> +#include <skalibs/types.h> +#include <skalibs/strerr.h> +#include <skalibs/gol.h> +#include <skalibs/env.h> +#include <skalibs/sig.h> + +#include <s6/supervise.h> + +#include "s6-internal.h" + +#define USAGE "s6 process kill [ --signal=sig ] services..." +#define dieusage() strerr_dieusage(100, USAGE) + +static int process_kill_hack_kill (int sig, char const *const *argv) +{ + size_t scandirlen = strlen(g->scandir) ; + if (g->verbosity) + { + char fmt[INT_FMT] ; + fmt[int_fmt(fmt, sig)] = 0 ; + strerr_warnw3x("signal ", fmt, " is not natively supported by s6-svc, results may be unreliable") ; + } + + for (; *argv ; argv++) + { + s6_svstatus_t status ; + size_t arglen = strlen(*argv) ; + char path[scandirlen + arglen + 2] ; + memcpy(path, g->scandir, scandirlen) ; + path[scandirlen] = '/' ; + memcpy(path + scandirlen + 1, *argv, arglen) ; + path[scandirlen + 1 + arglen] = 0 ; + if (!s6_svstatus_read(path, &status)) + strerr_diefu2sys(111, "read status file for service ", path) ; + if (status.pid && !status.flagfinishing) kill(status.pid, sig) ; + } + return 0 ; +} + + +enum process_kill_gola_e +{ + PROCESS_KILL_GOLA_SIGNAL, + PROCESS_KILL_GOLA_N +} ; + +static gol_arg const process_kill_gola[PROCESS_KILL_GOLA_N] = +{ + { .so = 's', .lo = "signal", .i = PROCESS_KILL_GOLA_SIGNAL }, +} ; + +int process_kill (char const *const *argv) +{ + uint64_t golb = 0 ; + char const *gola[PROCESS_KILL_GOLA_N] = { 0 } ; + char const *svcopt = 0 ; + size_t argc ; + int sig = SIGTERM ; + PROG = "s6 process kill" ; + + argv += gol_argv(argv, 0, 0, process_kill_gola, PROCESS_KILL_GOLA_N, &golb, gola) ; + if (!argv) dieusage() ; + if (gola[PROCESS_KILL_GOLA_SIGNAL]) + { + if (!sig0_scan(gola[PROCESS_KILL_GOLA_SIGNAL], &sig)) + strerr_dief1x(100, "--signal= argument must be the name or number of a signal") ; + } + argc = env_len(argv) ; + process_check_services(argv, argc) ; + switch (sig) + { + case SIGALRM : svcopt = "-a" ; break ; + case SIGABRT : svcopt = "-b" ; break ; + case SIGQUIT : svcopt = "-q" ; break ; + case SIGHUP : svcopt = "-h" ; break ; + case SIGKILL : svcopt = "-k" ; break ; + case SIGTERM : svcopt = "-t" ; break ; + case SIGINT : svcopt = "-i" ; break ; + case SIGUSR1 : svcopt = "-1" ; break ; + case SIGUSR2 : svcopt = "-2" ; break ; + case SIGSTOP : svcopt = "-p" ; break ; + case SIGCONT : svcopt = "-c" ; break ; + case SIGWINCH: svcopt = "-y" ; break ; + } + return svcopt ? process_send_svc(svcopt, argv, argc) : process_kill_hack_kill(sig, argv) ; +} diff --git a/src/s6/process_restart.c b/src/s6/process_restart.c new file mode 100644 index 0000000..9e8c083 --- /dev/null +++ b/src/s6/process_restart.c @@ -0,0 +1,38 @@ +/* ISC license. */ + +#include <stddef.h> + +#include <skalibs/uint64.h> +#include <skalibs/env.h> +#include <skalibs/strerr.h> +#include <skalibs/gol.h> + +#include "s6-internal.h" + +#define USAGE "s6 process restart [ -W|--nowait ] services..." +#define dieusage() strerr_dieusage(100, USAGE) + +enum process_restart_golb_e +{ + PROCESS_RESTART_GOLB_WAIT, + PROCESS_RESTART_GOLB_N +} ; + +static gol_bool const process_restart_golb[2] = +{ + { .so = 'W', .lo = "nowait", .set = 0, .mask = 1 << PROCESS_RESTART_GOLB_WAIT }, + { .so = 'w', .lo = "wait", .set = 1, .mask = 1 << PROCESS_RESTART_GOLB_WAIT } +} ; + +int process_restart (char const *const *argv) +{ + uint64_t golb = 1 << PROCESS_RESTART_GOLB_WAIT ; + size_t argc ; + PROG = "s6 process restart" ; + + argv += gol_argv(argv, process_restart_golb, 2, 0, 0, &golb, 0) ; + if (!argv) dieusage() ; + argc = env_len(argv) ; + process_check_services(argv, argc) ; + return process_send_svc(golb & 1 << PROCESS_RESTART_GOLB_WAIT ? "-rwR" : "-r", argv, argc) ; +} diff --git a/src/s6/process_start.c b/src/s6/process_start.c new file mode 100644 index 0000000..a2186cf --- /dev/null +++ b/src/s6/process_start.c @@ -0,0 +1,38 @@ +/* ISC license. */ + +#include <stddef.h> + +#include <skalibs/uint64.h> +#include <skalibs/env.h> +#include <skalibs/strerr.h> +#include <skalibs/gol.h> + +#include "s6-internal.h" + +#define USAGE "s6 process start [ -W|--nowait | -w|--wait ] services..." +#define dieusage() strerr_dieusage(100, USAGE) + +enum process_start_golb_e +{ + PROCESS_START_GOLB_WAIT, + PROCESS_START_GOLB_N +} ; + +static gol_bool const process_start_golb[2] = +{ + { .so = 'W', .lo = "nowait", .set = 0, .mask = 1 << PROCESS_START_GOLB_WAIT }, + { .so = 'w', .lo = "wait", .set = 1, .mask = 1 << PROCESS_START_GOLB_WAIT } +} ; + +int process_start (char const *const *argv) +{ + uint64_t golb = 1 << PROCESS_START_GOLB_WAIT ; + size_t argc ; + PROG = "s6 process start" ; + + argv += gol_argv(argv, process_start_golb, 2, 0, 0, &golb, 0) ; + if (!argv) dieusage() ; + argc = env_len(argv) ; + process_check_services(argv, argc) ; + return process_send_svc(golb & 1 << PROCESS_START_GOLB_WAIT ? "-uwU" : "-u", argv, argc) ; +} diff --git a/src/s6/process_status.c b/src/s6/process_status.c new file mode 100644 index 0000000..2240c85 --- /dev/null +++ b/src/s6/process_status.c @@ -0,0 +1,108 @@ +/* ISC license. */ + +#include <string.h> +#include <sys/wait.h> +#include <errno.h> + +#include <skalibs/posixplz.h> +#include <skalibs/uint64.h> +#include <skalibs/env.h> +#include <skalibs/buffer.h> +#include <skalibs/strerr.h> +#include <skalibs/gol.h> +#include <skalibs/cspawn.h> +#include <skalibs/djbunix.h> + +#include <s6/config.h> + +#include "s6-internal.h" + +#define USAGE "s6 process status [ -l|--with-logs ] services..." +#define dieusage() strerr_dieusage(100, USAGE) + +static int spawn_and_wait (char const *const *argv) +{ + int wstat ; + pid_t r ; + pid_t pid = cspawn(argv[0], argv, (char const *const *)environ, 0, 0, 0) ; + if (!pid) + { + if (g->verbosity) strerr_warnwu2sys("spawn ", argv[0]) ; + return 1 ; + } + r = wait_pid(pid, &wstat) ; + if (r != pid) + { + if (g->verbosity) strerr_warnwu2sys("wait for ", argv[0]) ; + return 1 ; + } + return !!WIFSIGNALED(wstat) || !!WEXITSTATUS(wstat) ; +} + +static int do_status (char const *dir, int withlog) +{ + int e ; + char const *argv[4] = { S6_EXTBINPREFIX "s6-svstat", "--", dir, 0 } ; + size_t dirlen = strlen(dir) ; + buffer_puts(buffer_1, dir) ; + buffer_putsflush(buffer_1, ": ") ; + e = spawn_and_wait(argv) ; + if (withlog && (dirlen < 5 || strcmp(dir + dirlen - 4, "/log"))) + { + struct stat st ; + char log[dirlen + 5] ; + memcpy(log, dir, dirlen) ; + memcpy(log + dirlen, "/log", 5) ; + if (stat(log, &st) < 0) + { + if (errno != ENOENT) + { + if (g->verbosity) strerr_warnwu2sys("stat", log) ; + e = 1 ; + } + } + else if (S_ISDIR(st.st_mode)) + { + argv[2] = log ; + buffer_puts(buffer_1, log) ; + buffer_putsflush(buffer_1, ": ") ; + e |= spawn_and_wait(argv) ; + } + } + return e ; +} + + +enum process_status_golb_e +{ + PROCESS_STATUS_GOLB_WITHLOGS, + PROCESS_STATUS_GOLB_N +} ; + +static gol_bool const process_status_golb[1] = +{ + { .so = 'l', .lo = "with-logs", .set = 1, .mask = 1 << PROCESS_STATUS_GOLB_WITHLOGS } +} ; + + +int process_status (char const *const *argv) +{ + size_t scandirlen = strlen(g->scandir) ; + uint64_t golb = 0 ; + int e = 0 ; + PROG = "s6 process status" ; + + argv += gol_argv(argv, process_status_golb, 1, 0, 0, &golb, 0) ; + if (!argv) dieusage() ; + process_check_services(argv, env_len(argv)) ; + for (; *argv ; argv++) + { + size_t len = strlen(*argv) ; + char path[scandirlen + len + 2] ; + memcpy(path, g->scandir, scandirlen) ; + path[scandirlen] = '/' ; + memcpy(path + scandirlen + 1, *argv, len+1) ; + if (do_status(path, !!(golb & 1 << PROCESS_STATUS_GOLB_WITHLOGS))) e = 1 ; + } + return e ; +} diff --git a/src/s6/process_stop.c b/src/s6/process_stop.c new file mode 100644 index 0000000..6f11b02 --- /dev/null +++ b/src/s6/process_stop.c @@ -0,0 +1,38 @@ +/* ISC license. */ + +#include <stddef.h> + +#include <skalibs/uint64.h> +#include <skalibs/env.h> +#include <skalibs/strerr.h> +#include <skalibs/gol.h> + +#include "s6-internal.h" + +#define USAGE "s6 process stop [ -W|--nowait | -w|--wait ] services..." +#define dieusage() strerr_dieusage(100, USAGE) + +enum process_stop_golb_e +{ + PROCESS_STOP_GOLB_WAIT, + PROCESS_STOP_GOLB_N +} ; + +static gol_bool const process_stop_golb[2] = +{ + { .so = 'W', .lo = "nowait", .set = 0, .mask = 1 << PROCESS_STOP_GOLB_WAIT }, + { .so = 'w', .lo = "wait", .set = 1, .mask = 1 << PROCESS_STOP_GOLB_WAIT } +} ; + +int process_stop (char const *const *argv) +{ + uint64_t golb = 1 << PROCESS_STOP_GOLB_WAIT ; + size_t argc ; + PROG = "s6 process stop" ; + + argv += gol_argv(argv, process_stop_golb, 2, 0, 0, &golb, 0) ; + if (!argv) dieusage() ; + argc = env_len(argv) ; + process_check_services(argv, argc) ; + return process_send_svc(golb & 1 << PROCESS_STOP_GOLB_WAIT ? "-dwD" : "-d", argv, argc) ; +} diff --git a/src/s6/process_util.c b/src/s6/process_util.c new file mode 100644 index 0000000..34b9196 --- /dev/null +++ b/src/s6/process_util.c @@ -0,0 +1,63 @@ +/* ISC license. */ + +#include <string.h> +#include <sys/stat.h> +#include <errno.h> + +#include <skalibs/posixplz.h> +#include <skalibs/strerr.h> +#include <skalibs/cspawn.h> +#include <skalibs/djbunix.h> + +#include <s6/config.h> + +#include "s6-internal.h" + +static int check_service (char const *name, size_t scandirlen) +{ + struct stat st ; + size_t namelen = strlen(name) ; + char path[scandirlen + namelen + 2] ; + memcpy(path, g->scandir, scandirlen) ; + path[scandirlen] = '/' ; + memcpy(path + scandirlen + 1, name, namelen) ; + path[scandirlen + 1 + namelen] = 0 ; + return stat(path, &st) == -1 ? errno == ENOENT ? 0 : -1 : !!S_ISDIR(st.st_mode) ; +} + +void process_check_services (char const *const *argv, size_t argc) +{ + size_t scandirlen = strlen(g->scandir) ; + for (size_t i = 0 ; i < argc ; i++) + { + int r = check_service(argv[i], scandirlen) ; + if (r == -1) + strerr_diefu4sys(111, "stat ", g->scandir, "/", argv[i]) ; + else if (!r) + strerr_dief3x(100, argv[i], "is not registered as a supervised service in ", g->scandir) ; + } +} + +int process_send_svc (char const *svcopt, char const *const *argv, size_t argc) +{ + char const *newargv[5] = { S6_EXTBINPREFIX "s6-svc", svcopt, "--", 0, 0 } ; + size_t scandirlen = strlen(g->scandir) ; + int wstat ; + pid_t pids[argc] ; + + for (size_t i = 0 ; i < argc ; i++) + { + size_t arglen = strlen(argv[i]) ; + char path[scandirlen + arglen + 2] ; + memcpy(path, g->scandir, scandirlen) ; + path[scandirlen] = '/' ; + memcpy(path + scandirlen + 1, argv[i], arglen) ; + path[scandirlen + 1 + arglen] = 0 ; + newargv[3] = path ; + pids[i] = cspawn(newargv[0], newargv, (char const *const *)environ, 0, 0, 0) ; + if (!pids[i]) + strerr_diefu4sys(111, "spawn ", newargv[0], " command for service ", argv[i]) ; + } + waitn_posix(pids, argc, &wstat) ; + return 0 ; +} diff --git a/src/s6/s6-internal.h b/src/s6/s6-internal.h index 216d666..7d08d18 100644 --- a/src/s6/s6-internal.h +++ b/src/s6/s6-internal.h @@ -3,6 +3,8 @@ #ifndef S6_INTERNAL_H #define S6_INTERNAL_H +#include <stddef.h> +#include <stdint.h> #include <stdlib.h> #include <skalibs/functypes.h> @@ -25,15 +27,33 @@ extern int help (char const *const *) ; extern int version (char const *const *) ; + /* process */ + +extern void process_check_services (char const *const *, size_t) ; +extern int process_send_svc (char const *, char const *const *, size_t) ; + +extern int process (char const *const *) ; +extern int process_help (char const *const *) ; +extern int process_kill (char const *const *) ; +extern int process_restart (char const *const *) ; +extern int process_start (char const *const *) ; +extern int process_status (char const *const *) ; +extern int process_stop (char const *const *) ; + + /* main */ struct global_s { unsigned int verbosity ; + char const *scandir ; + uint8_t color : 1 ; } ; #define GLOBAL_ZERO \ { \ .verbosity = 1, \ + .scandir = "/run/service", \ + .color = 0 \ } extern struct global_s *g ; diff --git a/src/s6/s6.c b/src/s6/s6.c index b9bc61f..c7d950c 100644 --- a/src/s6/s6.c +++ b/src/s6/s6.c @@ -14,7 +14,7 @@ #include <s6-frontend/config.h> #include "s6-internal.h" -#define USAGE "s6 [ generic options ] subcommand [ command options ] command_arguments... Type \"s6 help\" for details." +#define USAGE "s6 [ generic options ] command [ command options ] command_arguments... Type \"s6 help\" for details." #define dieusage() strerr_dieusage(100, USAGE) enum main_golb_e @@ -30,6 +30,7 @@ enum main_gola_e MAIN_GOLA_LIVEDIR, MAIN_GOLA_REPODIR, MAIN_GOLA_VERBOSITY, + MAIN_GOLA_COLOR, MAIN_GOLA_N } ; @@ -44,7 +45,8 @@ static gol_arg const main_gola[MAIN_GOLA_N] = { .so = 's', .lo = "scandir", .i = MAIN_GOLA_SCANDIR }, { .so = 'l', .lo = "livedir", .i = MAIN_GOLA_LIVEDIR }, { .so = 'r', .lo = "repodir", .i = MAIN_GOLA_REPODIR }, - { .so = 'v', .lo = "verbosity", .i = MAIN_GOLA_VERBOSITY } + { .so = 'v', .lo = "verbosity", .i = MAIN_GOLA_VERBOSITY }, + { .so = 0, .lo = "color", .i = MAIN_GOLA_COLOR } } ; struct global_s *g ; @@ -52,6 +54,7 @@ struct global_s *g ; static struct command_s const main_commands[] = { { .s = "help", .f = &help }, + { .s = "process", .f = &process }, { .s = "version", .f = &version }, } ; @@ -79,6 +82,26 @@ int main (int argc, char const *const *argv, char const *const *envp) if (gola[MAIN_GOLA_LIVEDIR]) strerr_warni("livedir is ", gola[MAIN_GOLA_LIVEDIR]) ; if (gola[MAIN_GOLA_REPODIR]) strerr_warni("repodir is ", gola[MAIN_GOLA_REPODIR]) ; + { + int force_color = 0 ; + if (gola[MAIN_GOLA_COLOR]) + { + if (!strcmp(gola[MAIN_GOLA_COLOR], "yes")) + { + force_color = 1 ; + g->color = 1 ; + } + else if (!strcmp(gola[MAIN_GOLA_COLOR], "no")) + { + force_color = 1 ; + g->color = 0 ; + } + else if (strcmp(gola[MAIN_GOLA_COLOR], "auto")) + strerr_dief1x(100, "--color value must be yes, no, or auto") ; + } + if (!force_color) g->color = isatty(1) ; + } + if (!*argv) dieusage() ; cmd = BSEARCH(struct command_s, *argv, main_commands) ; if (!cmd) dieusage() ; |
