diff options
Diffstat (limited to '')
-rw-r--r-- | coreupdown.c | 612 |
1 files changed, 576 insertions, 36 deletions
diff --git a/coreupdown.c b/coreupdown.c index ab3d4e9..500839f 100644 --- a/coreupdown.c +++ b/coreupdown.c @@ -1,4 +1,7 @@ /* 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> @@ -11,7 +14,9 @@ #include <time.h> #include <unistd.h> -static const char *argv0; +#include <libsimple-arg.h> + +char *argv0; static volatile sig_atomic_t caught_sighup = 0; #define coreup() coreupdown(ULONG_MAX) @@ -22,7 +27,7 @@ coreupdown(unsigned long int count) { char path[sizeof("/sys/devices/system/cpu/cpu/online") + 3 * sizeof(count)]; unsigned long int i; - int fd, ret = 0; + int fd, have_success = 0, have_failure = 0; for (i = 1; i; i++) { sprintf(path, "/sys/devices/system/cpu/cpu%zu/online", i); @@ -33,13 +38,39 @@ coreupdown(unsigned long int count) 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); - ret = -1; + have_failure = 1; + } else { + have_success = 1; } close(fd); } - return ret; + 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 @@ -52,7 +83,7 @@ oneshot_main(int argc, char **argv, unsigned long int count) if (argc > 1) { usage: - fprintf(stderr, "usage: %s [count]\n", argv0); + oneshot_usage(argv0); return 1; } @@ -236,17 +267,428 @@ sighup_handler(int signo) } 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; - int up_time = 0, down_time = 0, cooldown = 0, err; + 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, coredown_threshold_cpu; - int coreup_threshold_time, coredown_threshold_time; - int coreup_cooldown, coredown_cooldown; - time_t daemon_interval_sec; - long int daemon_interval_nsec; + 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; @@ -259,40 +701,137 @@ restart: daemon_interval_nsec = DAEMON_INTERVAL_NSEC; if (reexeced) - argv[0] = "coreupdownd"; - - if (argc && !strcmp(argv[0], "--")) { - argv++; - argc--; - } + 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) { - fprintf(stderr, "usage: %s\n", argv0); + usage: + daemon_usage(argv0); return 1; } - if (!reexeced) { - /* TODO add proper daemonisation */ + 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; } - /* TODO configurations should be configurable */ + if (!reexeced && !foreground) + daemonise(keep_stderr); if (signal(SIGHUP, sighup_handler) == SIG_ERR) fprintf(stderr, "%s: signal SIGHUP <function>: %s\n", argv0, strerror(errno)); - interval.tv_sec = daemon_interval_sec; - interval.tv_nsec = daemon_interval_nsec; - if (getusage(&total, &idle, &cpus)) return 1; - close(STDIN_FILENO); - close(STDOUT_FILENO); + if (!reexeced) { + close(STDIN_FILENO); + close(STDOUT_FILENO); + } for (;;) { if (caught_sighup) { caught_sighup = 0; - orig_argv[0] = "coreupdownd "; + 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; @@ -317,18 +856,20 @@ restart: continue; } - if (cpus == 1 && 100 * (total - idle) > coreup_threshold_cpu * total) { + if (cpus == 1 && 100 * (total - idle) >= coreup_threshold_cpu_ju * total) { down_time = 0; if (up_time++ >= coreup_threshold_time) { cooldown = coreup_cooldown; - coreup(); + if (coreup()) + return 1; up_time = 0; } - } else if (cpus > 1 && 100 * (total - idle) < coredown_threshold_cpu * total * cpus) { + } 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; - coredown(); + if (coredown()) + return 1; down_time = 0; } } else { @@ -358,9 +899,8 @@ main(int argc, char **argv) return daemon_main(argc, argv, argc + 1, &argv[-1], 1); } - fprintf(stderr, "usage: coreup [count]\n" - "usage: coredown [count]\n" - "usage: coreupdownd\n"); - + oneshot_usage("coreup"); + oneshot_usage("coredown"); + daemon_usage("coreupdownd"); return 1; } |