You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

222 lines
4.5 KiB

/*
* sched.c - scheduling of block updates (timeout, signal or click)
* Copyright (C) 2014 Vivien Didelot
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include "bar.h"
#include "block.h"
#include "io.h"
#include "json.h"
#include "log.h"
static sigset_t set;
static int
gcd(int a, int b)
{
while (b != 0)
a %= b, a ^= b, b ^= a, a ^= b;
return a;
}
static unsigned int
longest_sleep(struct bar *bar)
{
unsigned int time = 0;
if (bar->num > 0 && bar->blocks->interval > 0)
time = bar->blocks->interval; /* first block's interval */
if (bar->num < 2)
return time;
/* The maximum sleep time is actually the GCD between all block intervals */
for (int i = 1; i < bar->num; ++i)
if ((bar->blocks + i)->interval > 0)
time = gcd(time, (bar->blocks + i)->interval);
return time;
}
static int
setup_timer(struct bar *bar)
{
const unsigned sleeptime = longest_sleep(bar);
if (!sleeptime) {
debug("no timer needed");
return 0;
}
struct itimerval itv = {
.it_value.tv_sec = sleeptime,
.it_interval.tv_sec = sleeptime,
};
if (setitimer(ITIMER_REAL, &itv, NULL) == -1) {
errorx("setitimer");
return 1;
}
debug("starting timer with interval of %d seconds", sleeptime);
return 0;
}
static int
setup_signals(void)
{
if (sigemptyset(&set) == -1) {
errorx("sigemptyset");
return 1;
}
#define ADD_SIG(_sig) \
if (sigaddset(&set, _sig) == -1) { errorx("sigaddset(%d)", _sig); return 1; }
/* Control signals */
ADD_SIG(SIGTERM);
ADD_SIG(SIGINT);
/* Timer signal */
ADD_SIG(SIGALRM);
/* Block updates (forks) */
ADD_SIG(SIGCHLD);
/* Deprecated signals */
ADD_SIG(SIGUSR1);
ADD_SIG(SIGUSR2);
/* Click signal */
ADD_SIG(SIGIO);
/* I/O Possible signal for persistent blocks */
ADD_SIG(SIGRTMIN);
/* Real-time signals for blocks */
for (int sig = SIGRTMIN + 1; sig <= SIGRTMAX; ++sig) {
debug("provide signal %d (%s)", sig, strsignal(sig));
ADD_SIG(sig);
}
#undef ADD_SIG
/* Block signals for which we are interested in waiting */
if (sigprocmask(SIG_SETMASK, &set, NULL) == -1) {
errorx("sigprocmask");
return 1;
}
return 0;
}
int
sched_init(struct bar *bar)
{
if (setup_signals())
return 1;
if (setup_timer(bar))
return 1;
/* Setup event I/O for stdin (clicks) */
if (!isatty(STDIN_FILENO))
if (io_signal(STDIN_FILENO, SIGIO))
return 1;
return 0;
}
void
sched_start(struct bar *bar)
{
siginfo_t siginfo;
int sig;
/*
* Initial display (for static blocks and loading labels),
* and first forks (for commands with an interval).
*/
json_print_bar(bar);
bar_poll_timed(bar);
while (1) {
sig = sigwaitinfo(&set, &siginfo);
if (sig == -1) {
/* Hiding the bar may interrupt this system call */
if (errno == EINTR)
continue;
errorx("sigwaitinfo");
break;
}
debug("received signal %d (%s)", sig, strsignal(sig));
if (sig == SIGTERM || sig == SIGINT)
break;
/* Interval tick? */
if (sig == SIGALRM) {
bar_poll_outdated(bar);
/* Child(ren) dead? */
} else if (sig == SIGCHLD) {
bar_poll_exited(bar);
json_print_bar(bar);
/* Block clicked? */
} else if (sig == SIGIO) {
bar_poll_clicked(bar);
/* Persistent block ready to be read? */
} else if (sig == SIGRTMIN) {
bar_poll_readable(bar, siginfo.si_fd);
json_print_bar(bar);
/* Blocks signaled? */
} else if (sig > SIGRTMIN && sig <= SIGRTMAX) {
bar_poll_signaled(bar, sig - SIGRTMIN);
/* Deprecated signals? */
} else if (sig == SIGUSR1 || sig == SIGUSR2) {
error("SIGUSR{1,2} are deprecated, ignoring.");
} else debug("unhandled signal %d", sig);
}
/*
* Unblock signals (so subsequent syscall can be interrupted)
* and wait for child processes termination.
*/
if (sigprocmask(SIG_UNBLOCK, &set, NULL) == -1)
errorx("sigprocmask");
while (waitpid(-1, NULL, 0) > 0)
continue;
debug("quit scheduling");
}