aboutsummaryrefslogblamecommitdiffstats
path: root/coreupdown.c
blob: 283a1de07df22f7ffa1463cecec304ccc8d140af (plain) (tree)
1
2
3
4
5
6
7
8
9
                                                         


                              



                   
                   
                   


                   
                 

                   


                          
                                               
 

                                                                

          
                                                            
 

                                                                                    
                                                   
 








                                                                               
                    
                                                                  

                                                 
                                                                       


                                         

                          
         







                                          



















                                                                             

                                                     
                                            
 
 


                                                            






                                             
                                     


                         
                   










                                                 
                                        













































                                                                                                
                                      
















                                                                   
                 



















                                                                                                     
                 

























































                                                                                                     
                          
                          

         










                                            






                          
          














































































































































































































































                                                                                                          

                                                                                               

                            
                    








































































                                                                                             
 
                 





                                                         
 






















                                                                                            










































































                                                                                           
                                                                                 

                              


                                                                    
                                      





                                                                     
                                                                        
















                                                                                          
 
        








                                                          
                     



















                                                                                                











                                                  



















































                                                                                                     

                       

                                    


                         







                                                                                           


                                                                                   






















                                                                                                             
         
 

                                       
 

                                                                                              
 


                                           



                                     

                  

                                          
                                                                         








                                                                                                    
                            
                                                   



                                                                                                   
                                                                                         
                 






                                                   
                                                                                           
                                      

                                                                 
                                                                
                                                 

                                            
                                                                                                          
                                    

                                                                     
                                                                    
                                                 























                                                                   


                                                                               

         


                                    
                 
 
/* 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(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 <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,
                    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 <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(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;
}