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
222 lines
4.5 KiB
6 years ago
|
/*
|
||
|
* 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");
|
||
|
}
|