/* See LICENSE file for copyright and license details. */
#include <linux/close_range.h>
#include <sys/file.h>
#include <sys/stat.h>
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <libsimple-arg.h>
char *argv0;
static volatile sig_atomic_t caught_sighup = 0;
#define coreup() coreupdown(ULONG_MAX)
#define coredown() coreupdown(1)
static int
coreupdown(unsigned long int count)
{
char path[sizeof("/sys/devices/system/cpu/cpu/online") + 3 * sizeof(count)];
unsigned long int i;
int fd, have_success = 0, have_failure = 0;
for (i = 1; i; i++) {
sprintf(path, "/sys/devices/system/cpu/cpu%zu/online", i);
fd = open(path, O_WRONLY);
if (fd < 0) {
if (errno == ENOENT)
break;
fprintf(stderr, "%s: open %s O_WRONLY\n", argv0, path);
return -1;
}
write_again:
if (write(fd, i < count ? "1\n" : "0\n", 2) < 0) {
if (errno == EINTR)
goto write_again;
fprintf(stderr, "%s: write %s\n", argv0, path);
have_failure = 1;
} else {
have_success = 1;
}
close(fd);
}
return -(have_failure && !have_success);
}
static void
oneshot_usage(const char *command)
{
fprintf(stderr, "usage: %s [count]\n", command);
}
static void
daemon_usage(const char *command)
{
fprintf(stderr, "usage: %s "
"[-C coreup-cooldown-time] "
"[-c coredown-cooldown-time] "
"[-i check-interval] "
"[-s configuration-file] "
"[-T coreup-cpu-usage-time-consistency-threshold] "
"[-t coredown-cpu-usage-time-consistency-threshold] "
"[-U coreup-cpu-usage-threshold] "
"[-u coredown-cpu-usage-threshold] "
"[-ef]\n", command);
}
static int
oneshot_main(int argc, char **argv, unsigned long int count)
{
if (argc && !strcmp(argv[0], "--")) {
argv++;
argc--;
}
if (argc > 1) {
usage:
oneshot_usage(argv0);
return 1;
}
if (argc) {
char *end;
errno = 0;
if (!isdigit(**argv))
goto usage;
count = strtoul(*argv, &end, 10);
if (!count || errno || *end)
goto usage;
argv++;
argc--;
}
return -coreupdown(count);
}
static int
getusage(uintmax_t *total_out, uintmax_t *idle_out, size_t *cpus_out)
{
#define IDLE_FIELD 3
static uintmax_t last_total = 0, last_idle = 0;
uintmax_t cur_total, cur_idle;
char buf[512];
int fd;
size_t off, len, field;
ssize_t r;
int line_incomplete = 0, beginning_of_line = 1;
uintmax_t amount[] = {0, 0}, value;
*cpus_out = 0;
fd = open("/proc/stat", O_RDONLY);
if (fd < 0) {
fprintf(stderr, "%s: open /proc/stat O_RDONLY\n", argv0);
return -1;
}
len = 0;
for (;;) {
r = read(fd, &buf[len], sizeof(buf) - len);
if (!r) {
fprintf(stderr, "%s: /proc/stat did not contain \"cpu\" line\n", argv0);
close(fd);
return -1;
} else if (r < 0) {
if (errno == EINTR)
continue;
fprintf(stderr, "%s: read /proc/stat: %s\n", argv0, strerror(errno));
close(fd);
return -1;
}
len += (size_t)r;
if (line_incomplete)
goto skip_line;
for (off = 0; off < len;) {
if (len - off < 4) {
memmove(&buf[0], &buf[off], len -= off);
break;
} else if (!strncmp(&buf[off], "cpu", 3)) {
if (isspace(buf[off + 3]))
goto cpu_line_found;
else if (isdigit(buf[off + 3]))
*cpus_out += 1;
}
skip_line:
while (off < len && buf[off] != '\n')
off++;
if (off == len) {
line_incomplete = 0;
break;
} else {
off++;
line_incomplete = 1;
}
}
}
cpu_line_found:
off += 4;
field = 0;
value = 0;
for (;;) {
if (off == len) {
off = len = 0;
r = read(fd, buf, sizeof(buf));
if (!r) {
goto line_done;
} else if (r < 0) {
if (errno == EINTR)
continue;
fprintf(stderr, "%s: read /proc/stat: %s\n", argv0, strerror(errno));
close(fd);
return -1;
}
len = (size_t)r;
}
while (off < len && isspace(buf[off]))
off++;
if (off == len)
continue;
while (off < len) {
if (buf[off] == '\n') {
amount[field++ == IDLE_FIELD] += value;
off++;
goto line_done;
} else if (isspace(buf[off])) {
amount[field++ == IDLE_FIELD] += value;
value = 0;
off++;
break;
} else {
value *= 10;
value += buf[off] & 15;
off++;
}
}
}
line_done:
for (;;) {
if (off == len) {
off = len = 0;
read_more:
r = read(fd, &buf[len], sizeof(buf) - len);
if (!r) {
break;
} else if (r < 0) {
if (errno == EINTR)
continue;
fprintf(stderr, "%s: read /proc/stat: %s\n", argv0, strerror(errno));
close(fd);
return -1;
}
len = (size_t)r;
}
for (; off < len; off++) {
if (beginning_of_line) {
if (len - off < 5) {
memmove(&buf[0], &buf[off], len -= off);
goto read_more;
}
if (!strncmp(&buf[off], "cpu", 3) && isdigit(buf[off + 3]))
*cpus_out += 1;
beginning_of_line = 0;
}
if (buf[off] == '\n')
beginning_of_line = 1;
}
}
close(fd);
if (field < IDLE_FIELD) {
fprintf(stderr, "%s: /proc/stat did not contain CPU idle time\n", argv0);
close(fd);
return -1;
}
cur_total = amount[0] + amount[1];
cur_idle = amount[1];
*total_out = cur_total - last_total;
*idle_out = cur_idle - last_idle;
last_total = cur_total;
last_idle = cur_idle;
return 0;
#undef IDLE_FIELD
}
static void
sighup_handler(int signo)
{
(void) signo;
caught_sighup = 1;
}
static int
init_daemon(char **cwd_out)
{
int sig;
sigset_t sigmask;
char *cwd = NULL;
size_t cwd_size = 0;
for (;;) {
cwd = realloc(cwd, cwd_size += 512);
if (cwd) {
if (getcwd(cwd, cwd_size))
break;
if (errno == ERANGE)
continue;
}
fprintf(stderr, "%s: getcwd: %s\n", argv0, strerror(errno));
return -1;
}
*cwd_out = cwd;
umask(0);
if (chdir("/"))
fprintf(stderr, "%s: chdir /: %s\n", argv0, strerror(errno));
if (sigemptyset(&sigmask))
fprintf(stderr, "%s: sigemptyset: %s\n", argv0, strerror(errno));
else if (sigprocmask(SIG_SETMASK, &sigmask, NULL))
fprintf(stderr, "%s: sigprocmask SIG_SETMASK {}: %s\n", argv0, strerror(errno));
for (sig = 1; sig < _NSIG; sig++)
if (signal(sig, SIG_DFL) == SIG_ERR && errno != EINVAL)
fprintf(stderr, "%s: signal %i SIG_DFL: %s\n", argv0, sig, strerror(errno));
if (close_range(3, UINT_MAX, 0))
fprintf(stderr, "%s: close_range 3 <max> 0: %s\n", argv0, strerror(errno));
return 0;
}
static void
writefile(const char *path, const char *data, size_t len)
{
int fd;
size_t off;
ssize_t r;
fd = open(path, O_CREAT | O_WRONLY, 0644);
if (fd < 0) {
fprintf(stderr, "%s: open %s O_CREAT|O_WRONLY, 0644: %s\n", argv0, path, strerror(errno));
exit(1);
}
while (flock(fd, LOCK_EX)) {
if (errno != EINTR) {
fprintf(stderr, "%s: flock %s LOCK_EX: %s\n", argv0, path, strerror(errno));
exit(1);
}
}
if (ftruncate(fd, 0)) {
fprintf(stderr, "%s: ftruncate %s 0: %s\n", argv0, path, strerror(errno));
exit(1);
}
for (off = 0; off < len; off++) {
r = write(fd, &data[off], len - off);
if (r < 0) {
if (errno == EINTR)
continue;
write_error:
fprintf(stderr, "%s: write %s: %s\n", argv0, path, strerror(errno));
exit(1);
}
off += (size_t)r;
}
if (close(fd))
goto write_error;
}
static void
daemonise(int keep_stderr)
{
pid_t pid;
char buf[3 * sizeof(uintmax_t) + 2];
int len, pipe_rw[2], fd;
ssize_t r;
if (pipe(pipe_rw)) {
fprintf(stderr, "%s: pipe: %s\n", argv0, strerror(errno));
exit(1);
}
pid = fork();
switch (pid) {
case -1:
fork_failure:
fprintf(stderr, "%s: fork: %s\n", argv0, strerror(errno));
_exit(1);
case 0:
close(pipe_rw[0]);
if (pipe_rw[0] <= STDERR_FILENO) {
fd = fcntl(pipe_rw[0], F_DUPFD, STDERR_FILENO + 1);
if (fd <= STDERR_FILENO) {
fprintf(stderr, "%s: fcntl DUPFD: %s\n", argv0, strerror(errno));
exit(1);
}
close(pipe_rw[0]);
pipe_rw[0] = fd;
}
if (setsid() == -1) {
fprintf(stderr, "%s: setsid: %s\n", argv0, strerror(errno));
exit(1);
}
pid = fork();
switch (pid) {
case -1:
goto fork_failure;
case 0:
pid = getpid();
len = snprintf(buf, sizeof(buf), "%ju\n", (uintmax_t)pid);
if (len < 2)
abort();
writefile("/run/coreupdown.pid", buf, (size_t)len);
if (write(pipe_rw[1], &(char){0}, 1) <= 0 || close(pipe_rw[1])) {
fprintf(stderr, "%s: write <pipe>: %s\n", argv0, strerror(errno));
exit(1);
}
if (!keep_stderr) {
close(STDERR_FILENO);
fd = open("/dev/null", O_WRONLY);
if (fd >= 0 && fd != STDERR_FILENO) {
dup2(fd, STDERR_FILENO);
close(fd);
}
}
break;
default:
_exit(1);
}
break;
default:
close(pipe_rw[1]);
r = read(pipe_rw[0], buf, 1);
if (!r)
_exit(1);
if (r < 0) {
fprintf(stderr, "%s: read <pipe>: %s\n", argv0, strerror(errno));
_exit(1);
}
_exit(0);
}
}
static int
parse_nanotime(const char *s, unsigned int *sec, unsigned long int *nsec)
{
unsigned long int tmp;
int digits;
char *end;
if (!isdigit(*s))
goto invalid;
tmp = strtoul(s, &end, 10);
s = end;
if (errno)
goto invalid;
#if UINT_MAX < ULONG_MAX
if (tmp > UINT_MAX)
goto invalid;
#endif
*sec = (unsigned int)tmp;
*nsec = 0;
if (!*s)
goto done;
if (*s != '.')
goto invalid;
s++;
for (digits = 0; digits < 9 && *s; digits++, s++) {
if (!isdigit(*s))
goto invalid;
*nsec *= 10;
*nsec += (*s & 15);
}
for (; digits < 9; digits++)
*nsec *= 10;
if (!*s)
goto done;
if (!isdigit(*s))
goto invalid;
if (*s >= '5') {
if (*nsec < 999999999UL) {
*nsec += 1;
} else if (*sec < UINT_MAX) {
*sec += 1;
*nsec = 0;
}
}
for (; *s; s++)
if (!isdigit(*s))
goto invalid;
done:
return 0;
invalid:
return -1;
}
#if defined(__GNUC__)
__attribute__((pure))
#endif
static char *
rstrip(char *s)
{
char *ret = s;
while (*s) {
while (*s && !isspace(*s) && *s != '#')
s++;
ret = s;
while (isspace(*s))
s++;
if (*s == '#')
break;
}
return ret;
}
static void
load_configurations(const char *file, int optional,
unsigned int *coreup_threshold_cpu, unsigned int *coredown_threshold_cpu,
unsigned int *coreup_threshold_time, unsigned int *coredown_threshold_time,
unsigned int *coreup_cooldown, unsigned int *coredown_cooldown,
unsigned int *daemon_interval_sec, unsigned long int *daemon_interval_nsec)
{
unsigned int *uintp;
unsigned long int tmp;
int fd;
FILE *f;
ssize_t len;
char *line = NULL, *s, *p, pc;
size_t size = 0, n;
fd = open(file, O_RDONLY);
if (fd < 0) {
if (optional && errno == ENOENT)
return;
fprintf(stderr, "%s: open %s O_RDONLY: %s\n", argv0, file, strerror(errno));
exit(1);
}
f = fdopen(fd, "r");
if (!f) {
fprintf(stderr, "%s: fdopen %s r: %s\n", argv0, file, strerror(errno));
exit(1);
}
#define OPTION(OPT) (!strncmp(s, OPT, n = sizeof(OPT) - 1) && (isspace(s[n]) || s[n] == '='))
while ((len = getline(&line, &size, f)) != -1) {
if (len && line[len - 1] == '\n')
line[--len] = 0;
s = line;
while (isspace(*s))
s++;
if (!*s || *s == '#')
continue;
if (OPTION("coreup-cpu-usage-threshold"))
uintp = coreup_threshold_cpu;
else if (OPTION("coredown-cpu-usage-threshold"))
uintp = coredown_threshold_cpu;
else if (OPTION("coreup-cpu-usage-time-consistency-threshold"))
uintp = coreup_threshold_time;
else if (OPTION("coredown-cpu-usage-time-consistency-threshold"))
uintp = coredown_threshold_time;
else if (OPTION("coreup-cooldown-time"))
uintp = coreup_cooldown;
else if (OPTION("coredown-cooldown-time"))
uintp = coredown_cooldown;
else
goto not_uint;
s = &s[n];
while (isspace(*s))
s++;
if (*s != '=')
goto invalid;
s++;
while (isspace(*s))
s++;
p = rstrip(s);
pc = *p;
*p = 0;
if (!isdigit(*s))
goto invalid_restore;
errno = 0;
tmp = strtoul(s, &s, 10);
if (*s || errno)
goto invalid_restore;
#if UINT_MAX < ULONG_MAX
if (tmp > UINT_MAX)
goto invalid_restore;
#endif
if (uintp)
*uintp = (unsigned int)tmp;
continue;
not_uint:
if (!OPTION("check-interval"))
goto invalid;
s = &s[n];
while (isspace(*s))
s++;
if (*s != '=')
goto invalid;
s++;
while (isspace(*s))
s++;
p = rstrip(s);
pc = *p;
*p = 0;
if (daemon_interval_sec && daemon_interval_nsec) {
if (parse_nanotime(s, daemon_interval_sec, daemon_interval_nsec)) {
invalid_restore:
*p = pc;
goto invalid;
}
}
continue;
invalid:
fprintf(stderr, "%s: invalid line in %s: %s\n", argv0, file, line);
}
#undef OPTION
if (fclose(f)) {
fprintf(stderr, "%s: fclose %s: %s\n", argv0, file, strerror(errno));
exit(1);
}
free(line);
}
static char *
pathjoin(const char *a, const char *b)
{
const char *p;
char *r;
size_t n;
int need_slash;
p = strrchr(a, '/');
if (!p)
abort();
need_slash = p[1] ? 1 : 0;
n = strlen(a) + (size_t)need_slash + strlen(b) + 1;
r = malloc(n);
if (!r) {
fprintf(stderr, "%s: %s\n", argv0, strerror(errno));
return NULL;
}
stpcpy(stpcpy(stpcpy(r, a), need_slash ? "/" : ""), b);
return r;
}
static char *
memjoin(const char *a, size_t an, const char *b, size_t bn)
{
size_t n = an + bn;
char *r = malloc(n);
if (!r) {
fprintf(stderr, "%s: %s\n", argv0, strerror(errno));
return NULL;
}
memcpy(r, a, an);
memcpy(&r[an], b, bn);
return r;
}
static int
daemon_main(int argc, char **argv, int orig_argc, char **orig_argv, int reexeced)
{
uintmax_t total, idle;
size_t cpus, offset;
unsigned int up_time = 0, down_time = 0, cooldown = 0;
int err, foreground = 0, keep_stderr = 0, argnum, different;
struct timespec interval, rem;
uintmax_t coreup_threshold_cpu_ju, coredown_threshold_cpu_ju;
unsigned int coreup_threshold_cpu, coredown_threshold_cpu;
unsigned int coreup_threshold_time, coredown_threshold_time;
unsigned int coreup_cooldown, coredown_cooldown;
unsigned int daemon_interval_sec, *uintp;
unsigned long int daemon_interval_nsec, tmp;
int have_coreup_threshold_cpu = 0, have_coredown_threshold_cpu = 0;
int have_coreup_threshold_time = 0, have_coredown_threshold_time = 0;
int have_coreup_cooldown = 0, have_coredown_cooldown = 0;
int have_daemon_interval = 0;
char *arg, *cwd = NULL, *conffile = NULL;
char **proper_orig_argv;
if (!reexeced && init_daemon(&cwd))
return -1;
proper_orig_argv = calloc((size_t)(orig_argc + 1), sizeof(*orig_argv));
if (!proper_orig_argv) {
fprintf(stderr, "%s: %s\n", argv0, strerror(errno));
return 1;
}
memcpy(proper_orig_argv, orig_argv, (size_t)(orig_argc + 1) * sizeof(*orig_argv));
orig_argv = proper_orig_argv;
restart:
coreup_threshold_cpu = COREUP_THRESHOLD_CPU;
coreup_threshold_time = COREUP_THRESHOLD_TIME;
coreup_cooldown = COREUP_COOLDOWN;
coredown_threshold_cpu = COREDOWN_THRESHOLD_CPU;
coredown_threshold_time = COREDOWN_THRESHOLD_TIME;
coredown_cooldown = COREDOWN_COOLDOWN;
daemon_interval_sec = DAEMON_INTERVAL_SEC;
daemon_interval_nsec = DAEMON_INTERVAL_NSEC;
if (reexeced)
argv[0] = *(char **)(void *)&"coreupdownd";
#define usage() goto usage
SUBARGBEGIN {
case 'U': uintp = &coreup_threshold_cpu; have_coreup_threshold_cpu = 1; goto uint;
case 'u': uintp = &coredown_threshold_cpu; have_coredown_threshold_cpu = 1; goto uint;
case 'T': uintp = &coreup_threshold_time; have_coreup_threshold_time = 1; goto uint;
case 't': uintp = &coredown_threshold_time; have_coredown_threshold_time = 1; goto uint;
case 'C': uintp = &coreup_cooldown; have_coreup_cooldown = 1; goto uint;
case 'c': uintp = &coredown_cooldown; have_coredown_cooldown = 1; goto uint;
case 'i':
arg = ARGNULL();
if (!arg)
goto usage;
if (parse_nanotime(arg, &daemon_interval_sec, &daemon_interval_nsec))
goto invalid;
have_daemon_interval = 1;
break;
case 'e':
keep_stderr = 1;
break;
case 'f':
foreground = 1;
break;
case 's':
conffile = ARGNULL();
if (!conffile || !*conffile)
goto usage;
if (*conffile == '/')
break;
offset = (size_t)(conffile - LFLAG());
different = (conffile < LFLAG() || offset > strlen(LFLAG()));
for (argnum = 0; argnum < orig_argc; argnum++)
if (LFLAG() == orig_argv[argnum])
break;
if (argnum == orig_argc)
abort();
conffile = pathjoin(cwd, conffile);
if (different)
orig_argv[argnum + 1] = conffile;
else
orig_argv[argnum] = memjoin(LFLAG(), offset, conffile, strlen(conffile) + 1);
break;
default:
goto usage;
uint:
arg = ARGNULL();
if (!arg)
goto usage;
errno = 0;
if (!isdigit(*arg))
goto invalid;
tmp = strtoul(arg, &arg, 10);
if (*arg || errno) {
invalid:
fprintf(stderr, "%s: invalid argument for option -%c\n", argv0, FLAG());
return 1;
}
#if UINT_MAX < ULONG_MAX
if (tmp > UINT_MAX)
goto invalid;
#endif
*uintp = (unsigned int)tmp;
break;
} ARGEND;
#undef usage
if (argc > 0) {
usage:
daemon_usage(argv0);
return 1;
}
load_configurations(conffile ? conffile : "/etc/coreupdownd.conf", !conffile,
have_coreup_threshold_cpu ? NULL : &coreup_threshold_cpu,
have_coredown_threshold_cpu ? NULL : &coredown_threshold_cpu,
have_coreup_threshold_time ? NULL : &coreup_threshold_time,
have_coredown_threshold_time ? NULL : &coredown_threshold_time,
have_coreup_cooldown ? NULL : &coreup_cooldown,
have_coredown_cooldown ? NULL : &coredown_cooldown,
have_daemon_interval ? NULL : &daemon_interval_sec,
have_daemon_interval ? NULL : &daemon_interval_nsec);
if (coreup_threshold_cpu > 100) {
fprintf(stderr, "%s: CPU usage threshold for coreup was above 100, setting to 100\n", argv0);
coreup_threshold_cpu = 100;
} else if (coreup_threshold_cpu == 0) {
fprintf(stderr, "%s: CPU usage threshold for coreup was 0, setting to 1\n", argv0);
coreup_threshold_cpu = 1;
}
if (coredown_threshold_cpu > 99) {
fprintf(stderr, "%s: CPU usage threshold for coreup was above 99, setting to 99\n", argv0);
coredown_threshold_cpu = 99;
}
if (!daemon_interval_sec && !daemon_interval_nsec)
daemon_interval_nsec = 1;
interval.tv_sec = (time_t)daemon_interval_sec;
interval.tv_nsec = (long int)daemon_interval_nsec;
coreup_threshold_cpu_ju = (uintmax_t)coreup_threshold_cpu;
coredown_threshold_cpu_ju = (uintmax_t)coredown_threshold_cpu;
if (coreup_threshold_cpu_ju <= coredown_threshold_cpu_ju) {
fprintf(stderr, "%s: coreup cpu usage threshold must be above "
"coredown cpu usage threshold\n", argv0);
return 1;
}
if (!reexeced && !foreground)
daemonise(keep_stderr);
if (signal(SIGHUP, sighup_handler) == SIG_ERR)
fprintf(stderr, "%s: signal SIGHUP <function>: %s\n", argv0, strerror(errno));
if (getusage(&total, &idle, &cpus))
return 1;
if (!reexeced) {
close(STDIN_FILENO);
close(STDOUT_FILENO);
}
for (;;) {
if (caught_sighup) {
caught_sighup = 0;
orig_argv[0] = *(char **)(void *)&"coreupdownd ";
execv(DAEMON_PATH, orig_argv);
fprintf(stderr, "%s: execv %s: %s\n", argv0, DAEMON_PATH, strerror(errno));
reexeced = 1;
argv = &orig_argv[1];
argc = orig_argc - 1;
goto restart;
}
if ((err = clock_nanosleep(CLOCK_REALTIME /* = monotonic */, 0, &interval, &rem))) {
do {
if (err != EINTR) {
fprintf(stderr, "%s: clock_nanosleep CLOCK_REALTIME: %s\n",
argv0, strerror(errno));
return 1;
}
} while ((err = clock_nanosleep(CLOCK_REALTIME, 0, &rem, &rem)));
}
if (getusage(&total, &idle, &cpus))
return 1;
if (cooldown) {
cooldown--;
continue;
}
if (cpus == 1 && 100 * (total - idle) >= coreup_threshold_cpu_ju * total) {
down_time = 0;
if (up_time++ >= coreup_threshold_time) {
cooldown = coreup_cooldown;
if (coreup())
return 1;
up_time = 0;
}
} else if (cpus > 1 && 100 * (total - idle) <= coredown_threshold_cpu_ju * total * cpus) {
up_time = 0;
if (down_time++ >= coredown_threshold_time) {
cooldown = coredown_cooldown;
if (coredown())
return 1;
down_time = 0;
}
} else {
up_time = 0;
down_time = 0;
}
}
return 0;
}
int
main(int argc, char **argv)
{
const char *command;
if (argc--) {
command = strrchr(argv0 = *argv++, '/');
command = command ? &command[1] : argv0;
if (!strcmp(command, "coreup"))
return oneshot_main(argc, argv, ULONG_MAX);
if (!strcmp(command, "coredown"))
return oneshot_main(argc, argv, 1);
if (!strcmp(command, "coreupdownd"))
return daemon_main(argc, argv, argc + 1, &argv[-1], 0);
if (!strcmp(command, "coreupdownd "))
return daemon_main(argc, argv, argc + 1, &argv[-1], 1);
}
oneshot_usage("coreup");
oneshot_usage("coredown");
daemon_usage("coreupdownd");
return 1;
}