/* See LICENSE file for copyright and license details. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include char *argv0; static volatile sig_atomic_t caught_sighup = 0; #define coreup(POSTCOMMAND) coreupdown(ULONG_MAX, (POSTCOMMAND)) #define coredown(POSTCOMMAND) coreupdown(1, (POSTCOMMAND)) static int coreupdown(unsigned long int count, const char *postcommand) { 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); } if (have_failure && !have_success) return -1; if (postcommand) system(postcommand); return 0; } 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] " "[-X post-coreup-command] " "[-x post-coredown-command] " "[-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, NULL); } 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 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 : %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 : %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, char **post_coreup_command, char **post_coredown_command) { unsigned int *uintp; char **strp; 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("post-coreup-command")) strp = post_coreup_command; else if (OPTION("post-coredown-command")) strp = post_coredown_command; else goto not_str; s = &s[n]; while (isspace(*s)) s++; if (*s != '=') goto invalid; s++; while (isspace(*s)) s++; p = rstrip(s); pc = *p; *p = 0; if (strp) { *strp = strdup(s); if (!*strp) { fprintf(stderr, "%s: strdup: %s\n", argv0, strerror(errno)); exit(1); } } continue; not_str: 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; char *post_coreup_command = NULL, *post_coredown_command = NULL; 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 'X': post_coreup_command = ARGNULL(); if (!post_coreup_command) goto usage; break; case 'x': post_coredown_command = ARGNULL(); if (!post_coredown_command) goto usage; 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, post_coreup_command ? NULL : &post_coreup_command, post_coredown_command ? NULL : &post_coredown_command); 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 : %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(post_coreup_command)) 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(post_coredown_command)) 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; }