aboutsummaryrefslogtreecommitdiffstats
path: root/coreupdown.c
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2024-02-25 11:19:34 +0100
committerMattias Andrée <maandree@kth.se>2024-02-25 11:19:34 +0100
commit1c222c0991ab816f0c8dca32407801ce2525e885 (patch)
treef865f8feef05ad9a85875c09a875ed52696d2e1e /coreupdown.c
parentPrepare for configurability (diff)
downloadcoreupdown-1c222c0991ab816f0c8dca32407801ce2525e885.tar.gz
coreupdown-1c222c0991ab816f0c8dca32407801ce2525e885.tar.bz2
coreupdown-1c222c0991ab816f0c8dca32407801ce2525e885.tar.xz
Add coreupdownd: and configurations and daemonisation1.0
Signed-off-by: Mattias Andrée <maandree@kth.se>
Diffstat (limited to '')
-rw-r--r--coreupdown.c612
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;
}