mirror of
https://codeberg.org/dnkl/foot.git
synced 2026-04-02 07:15:31 -04:00
Merge branch 'fdm-signal-handling'
This commit is contained in:
commit
e5edf7a52b
9 changed files with 254 additions and 172 deletions
162
fdm.c
162
fdm.c
|
|
@ -6,6 +6,7 @@
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
|
|
||||||
|
|
@ -15,15 +16,22 @@
|
||||||
#define LOG_ENABLE_DBG 0
|
#define LOG_ENABLE_DBG 0
|
||||||
#include "log.h"
|
#include "log.h"
|
||||||
#include "debug.h"
|
#include "debug.h"
|
||||||
|
#include "xmalloc.h"
|
||||||
|
|
||||||
struct handler {
|
struct fd_handler {
|
||||||
int fd;
|
int fd;
|
||||||
int events;
|
int events;
|
||||||
fdm_handler_t callback;
|
fdm_fd_handler_t callback;
|
||||||
void *callback_data;
|
void *callback_data;
|
||||||
bool deleted;
|
bool deleted;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct sig_handler {
|
||||||
|
int signo;
|
||||||
|
fdm_signal_handler_t callback;
|
||||||
|
void *callback_data;
|
||||||
|
};
|
||||||
|
|
||||||
struct hook {
|
struct hook {
|
||||||
fdm_hook_t callback;
|
fdm_hook_t callback;
|
||||||
void *callback_data;
|
void *callback_data;
|
||||||
|
|
@ -34,33 +42,58 @@ typedef tll(struct hook) hooks_t;
|
||||||
struct fdm {
|
struct fdm {
|
||||||
int epoll_fd;
|
int epoll_fd;
|
||||||
bool is_polling;
|
bool is_polling;
|
||||||
tll(struct handler *) fds;
|
tll(struct fd_handler *) fds;
|
||||||
tll(struct handler *) deferred_delete;
|
tll(struct fd_handler *) deferred_delete;
|
||||||
|
|
||||||
|
sigset_t sigmask;
|
||||||
|
struct sig_handler *signal_handlers;
|
||||||
|
|
||||||
hooks_t hooks_low;
|
hooks_t hooks_low;
|
||||||
hooks_t hooks_normal;
|
hooks_t hooks_normal;
|
||||||
hooks_t hooks_high;
|
hooks_t hooks_high;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static volatile sig_atomic_t *received_signals = NULL;
|
||||||
|
|
||||||
struct fdm *
|
struct fdm *
|
||||||
fdm_init(void)
|
fdm_init(void)
|
||||||
{
|
{
|
||||||
|
sigset_t sigmask;
|
||||||
|
if (sigprocmask(0, NULL, &sigmask) < 0) {
|
||||||
|
LOG_ERRNO("failed to get process signal mask");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
int epoll_fd = epoll_create1(EPOLL_CLOEXEC);
|
||||||
if (epoll_fd == -1) {
|
if (epoll_fd == -1) {
|
||||||
LOG_ERRNO("failed to create epoll FD");
|
LOG_ERRNO("failed to create epoll FD");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xassert(received_signals == NULL); /* Only one FDM instance supported */
|
||||||
|
received_signals = xcalloc(SIGRTMAX, sizeof(received_signals[0]));
|
||||||
|
|
||||||
struct fdm *fdm = malloc(sizeof(*fdm));
|
struct fdm *fdm = malloc(sizeof(*fdm));
|
||||||
if (unlikely(fdm == NULL)) {
|
if (unlikely(fdm == NULL)) {
|
||||||
LOG_ERRNO("malloc() failed");
|
LOG_ERRNO("malloc() failed");
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct sig_handler *sig_handlers = calloc(SIGRTMAX, sizeof(sig_handlers[0]));
|
||||||
|
|
||||||
|
if (sig_handlers == NULL) {
|
||||||
|
LOG_ERRNO("failed to allocate signal handler array");
|
||||||
|
free(fdm);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
*fdm = (struct fdm){
|
*fdm = (struct fdm){
|
||||||
.epoll_fd = epoll_fd,
|
.epoll_fd = epoll_fd,
|
||||||
.is_polling = false,
|
.is_polling = false,
|
||||||
.fds = tll_init(),
|
.fds = tll_init(),
|
||||||
.deferred_delete = tll_init(),
|
.deferred_delete = tll_init(),
|
||||||
|
.sigmask = sigmask,
|
||||||
|
.signal_handlers = sig_handlers,
|
||||||
.hooks_low = tll_init(),
|
.hooks_low = tll_init(),
|
||||||
.hooks_normal = tll_init(),
|
.hooks_normal = tll_init(),
|
||||||
.hooks_high = tll_init(),
|
.hooks_high = tll_init(),
|
||||||
|
|
@ -77,6 +110,11 @@ fdm_destroy(struct fdm *fdm)
|
||||||
if (tll_length(fdm->fds) > 0)
|
if (tll_length(fdm->fds) > 0)
|
||||||
LOG_WARN("FD list not empty");
|
LOG_WARN("FD list not empty");
|
||||||
|
|
||||||
|
for (int i = 0; i < SIGRTMAX; i++) {
|
||||||
|
if (fdm->signal_handlers[i].callback != NULL)
|
||||||
|
LOG_WARN("handler for signal %d not removed", i);
|
||||||
|
}
|
||||||
|
|
||||||
if (tll_length(fdm->hooks_low) > 0 ||
|
if (tll_length(fdm->hooks_low) > 0 ||
|
||||||
tll_length(fdm->hooks_normal) > 0 ||
|
tll_length(fdm->hooks_normal) > 0 ||
|
||||||
tll_length(fdm->hooks_high) > 0)
|
tll_length(fdm->hooks_high) > 0)
|
||||||
|
|
@ -90,6 +128,9 @@ fdm_destroy(struct fdm *fdm)
|
||||||
xassert(tll_length(fdm->hooks_normal) == 0);
|
xassert(tll_length(fdm->hooks_normal) == 0);
|
||||||
xassert(tll_length(fdm->hooks_high) == 0);
|
xassert(tll_length(fdm->hooks_high) == 0);
|
||||||
|
|
||||||
|
sigprocmask(SIG_SETMASK, &fdm->sigmask, NULL);
|
||||||
|
free(fdm->signal_handlers);
|
||||||
|
|
||||||
tll_free(fdm->fds);
|
tll_free(fdm->fds);
|
||||||
tll_free(fdm->deferred_delete);
|
tll_free(fdm->deferred_delete);
|
||||||
tll_free(fdm->hooks_low);
|
tll_free(fdm->hooks_low);
|
||||||
|
|
@ -97,17 +138,15 @@ fdm_destroy(struct fdm *fdm)
|
||||||
tll_free(fdm->hooks_high);
|
tll_free(fdm->hooks_high);
|
||||||
close(fdm->epoll_fd);
|
close(fdm->epoll_fd);
|
||||||
free(fdm);
|
free(fdm);
|
||||||
|
|
||||||
|
free((void *)received_signals);
|
||||||
|
received_signals = NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data)
|
fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t cb, void *data)
|
||||||
{
|
{
|
||||||
#if defined(_DEBUG)
|
#if defined(_DEBUG)
|
||||||
int flags = fcntl(fd, F_GETFL);
|
|
||||||
if (!(flags & O_NONBLOCK)) {
|
|
||||||
BUG("FD=%d is in blocking mode", fd);
|
|
||||||
}
|
|
||||||
|
|
||||||
tll_foreach(fdm->fds, it) {
|
tll_foreach(fdm->fds, it) {
|
||||||
if (it->item->fd == fd) {
|
if (it->item->fd == fd) {
|
||||||
BUG("FD=%d already registered", fd);
|
BUG("FD=%d already registered", fd);
|
||||||
|
|
@ -115,30 +154,30 @@ fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct handler *fd_data = malloc(sizeof(*fd_data));
|
struct fd_handler *handler = malloc(sizeof(*handler));
|
||||||
if (unlikely(fd_data == NULL)) {
|
if (unlikely(handler == NULL)) {
|
||||||
LOG_ERRNO("malloc() failed");
|
LOG_ERRNO("malloc() failed");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
*fd_data = (struct handler) {
|
*handler = (struct fd_handler) {
|
||||||
.fd = fd,
|
.fd = fd,
|
||||||
.events = events,
|
.events = events,
|
||||||
.callback = handler,
|
.callback = cb,
|
||||||
.callback_data = data,
|
.callback_data = data,
|
||||||
.deleted = false,
|
.deleted = false,
|
||||||
};
|
};
|
||||||
|
|
||||||
tll_push_back(fdm->fds, fd_data);
|
tll_push_back(fdm->fds, handler);
|
||||||
|
|
||||||
struct epoll_event ev = {
|
struct epoll_event ev = {
|
||||||
.events = events,
|
.events = events,
|
||||||
.data = {.ptr = fd_data},
|
.data = {.ptr = handler},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
if (epoll_ctl(fdm->epoll_fd, EPOLL_CTL_ADD, fd, &ev) < 0) {
|
||||||
LOG_ERRNO("failed to register FD=%d with epoll", fd);
|
LOG_ERRNO("failed to register FD=%d with epoll", fd);
|
||||||
free(fd_data);
|
free(handler);
|
||||||
tll_pop_back(fdm->fds);
|
tll_pop_back(fdm->fds);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -190,7 +229,7 @@ fdm_del_no_close(struct fdm *fdm, int fd)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
event_modify(struct fdm *fdm, struct handler *fd, int new_events)
|
event_modify(struct fdm *fdm, struct fd_handler *fd, int new_events)
|
||||||
{
|
{
|
||||||
if (new_events == fd->events)
|
if (new_events == fd->events)
|
||||||
return true;
|
return true;
|
||||||
|
|
@ -287,6 +326,69 @@ fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority)
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void
|
||||||
|
signal_handler(int signo)
|
||||||
|
{
|
||||||
|
received_signals[signo] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data)
|
||||||
|
{
|
||||||
|
if (fdm->signal_handlers[signo].callback != NULL) {
|
||||||
|
LOG_ERR("signal %d already has a handler", signo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
sigset_t mask, original;
|
||||||
|
sigemptyset(&mask);
|
||||||
|
sigaddset(&mask, signo);
|
||||||
|
|
||||||
|
if (sigprocmask(SIG_BLOCK, &mask, &original) < 0) {
|
||||||
|
LOG_ERRNO("failed to block signal %d", signo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct sigaction action = {.sa_handler = &signal_handler};
|
||||||
|
if (sigaction(signo, &action, NULL) < 0) {
|
||||||
|
LOG_ERRNO("failed to set signal handler for signal %d", signo);
|
||||||
|
sigprocmask(SIG_SETMASK, &original, NULL);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
received_signals[signo] = false;
|
||||||
|
fdm->signal_handlers[signo].callback = handler;
|
||||||
|
fdm->signal_handlers[signo].callback_data = data;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
fdm_signal_del(struct fdm *fdm, int signo)
|
||||||
|
{
|
||||||
|
if (fdm->signal_handlers[signo].callback == NULL)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
struct sigaction action = {.sa_handler = SIG_DFL};
|
||||||
|
if (sigaction(signo, &action, NULL) < 0) {
|
||||||
|
LOG_ERRNO("failed to restore signal handler for signal %d", signo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
received_signals[signo] = false;
|
||||||
|
fdm->signal_handlers[signo].callback = NULL;
|
||||||
|
fdm->signal_handlers[signo].callback_data = NULL;
|
||||||
|
|
||||||
|
sigset_t mask;
|
||||||
|
sigemptyset(&mask);
|
||||||
|
sigaddset(&mask, signo);
|
||||||
|
if (sigprocmask(SIG_UNBLOCK, &mask, NULL) < 0) {
|
||||||
|
LOG_ERRNO("failed to unblock signal %d", signo);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
fdm_poll(struct fdm *fdm)
|
fdm_poll(struct fdm *fdm)
|
||||||
{
|
{
|
||||||
|
|
@ -320,10 +422,28 @@ fdm_poll(struct fdm *fdm)
|
||||||
|
|
||||||
struct epoll_event events[tll_length(fdm->fds)];
|
struct epoll_event events[tll_length(fdm->fds)];
|
||||||
|
|
||||||
int r = epoll_wait(fdm->epoll_fd, events, tll_length(fdm->fds), -1);
|
int r = epoll_pwait(
|
||||||
|
fdm->epoll_fd, events, tll_length(fdm->fds), -1, &fdm->sigmask);
|
||||||
|
|
||||||
if (unlikely(r < 0)) {
|
if (unlikely(r < 0)) {
|
||||||
if (errno == EINTR)
|
if (errno == EINTR) {
|
||||||
|
/* TODO: is it possible to receive a signal without
|
||||||
|
* getting EINTR here? */
|
||||||
|
|
||||||
|
for (int i = 0; i < SIGRTMAX; i++) {
|
||||||
|
if (received_signals[i]) {
|
||||||
|
|
||||||
|
received_signals[i] = false;
|
||||||
|
struct sig_handler *handler = &fdm->signal_handlers[i];
|
||||||
|
|
||||||
|
xassert(handler->callback != NULL);
|
||||||
|
if (!handler->callback(fdm, i, handler->callback_data))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
LOG_ERRNO("failed to epoll");
|
LOG_ERRNO("failed to epoll");
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -333,7 +453,7 @@ fdm_poll(struct fdm *fdm)
|
||||||
|
|
||||||
fdm->is_polling = true;
|
fdm->is_polling = true;
|
||||||
for (int i = 0; i < r; i++) {
|
for (int i = 0; i < r; i++) {
|
||||||
struct handler *fd = events[i].data.ptr;
|
struct fd_handler *fd = events[i].data.ptr;
|
||||||
if (fd->deleted)
|
if (fd->deleted)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
|
|
|
||||||
8
fdm.h
8
fdm.h
|
|
@ -4,7 +4,8 @@
|
||||||
|
|
||||||
struct fdm;
|
struct fdm;
|
||||||
|
|
||||||
typedef bool (*fdm_handler_t)(struct fdm *fdm, int fd, int events, void *data);
|
typedef bool (*fdm_fd_handler_t)(struct fdm *fdm, int fd, int events, void *data);
|
||||||
|
typedef bool (*fdm_signal_handler_t)(struct fdm *fdm, int signo, void *data);
|
||||||
typedef void (*fdm_hook_t)(struct fdm *fdm, void *data);
|
typedef void (*fdm_hook_t)(struct fdm *fdm, void *data);
|
||||||
|
|
||||||
enum fdm_hook_priority {
|
enum fdm_hook_priority {
|
||||||
|
|
@ -16,7 +17,7 @@ enum fdm_hook_priority {
|
||||||
struct fdm *fdm_init(void);
|
struct fdm *fdm_init(void);
|
||||||
void fdm_destroy(struct fdm *fdm);
|
void fdm_destroy(struct fdm *fdm);
|
||||||
|
|
||||||
bool fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data);
|
bool fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t handler, void *data);
|
||||||
bool fdm_del(struct fdm *fdm, int fd);
|
bool fdm_del(struct fdm *fdm, int fd);
|
||||||
bool fdm_del_no_close(struct fdm *fdm, int fd);
|
bool fdm_del_no_close(struct fdm *fdm, int fd);
|
||||||
|
|
||||||
|
|
@ -27,4 +28,7 @@ bool fdm_hook_add(struct fdm *fdm, fdm_hook_t hook, void *data,
|
||||||
enum fdm_hook_priority priority);
|
enum fdm_hook_priority priority);
|
||||||
bool fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority);
|
bool fdm_hook_del(struct fdm *fdm, fdm_hook_t hook, enum fdm_hook_priority priority);
|
||||||
|
|
||||||
|
bool fdm_signal_add(struct fdm *fdm, int signo, fdm_signal_handler_t handler, void *data);
|
||||||
|
bool fdm_signal_del(struct fdm *fdm, int signo);
|
||||||
|
|
||||||
bool fdm_poll(struct fdm *fdm);
|
bool fdm_poll(struct fdm *fdm);
|
||||||
|
|
|
||||||
19
main.c
19
main.c
|
|
@ -33,12 +33,11 @@
|
||||||
#include "xmalloc.h"
|
#include "xmalloc.h"
|
||||||
#include "xsnprintf.h"
|
#include "xsnprintf.h"
|
||||||
|
|
||||||
static volatile sig_atomic_t aborted = 0;
|
static bool
|
||||||
|
fdm_sigint(struct fdm *fdm, int signo, void *data)
|
||||||
static void
|
|
||||||
sig_handler(int signo)
|
|
||||||
{
|
{
|
||||||
aborted = 1;
|
*(volatile sig_atomic_t *)data = true;
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
static const char *
|
static const char *
|
||||||
|
|
@ -465,10 +464,10 @@ main(int argc, char *const *argv)
|
||||||
if (as_server && (server = server_init(&conf, fdm, reaper, wayl)) == NULL)
|
if (as_server && (server = server_init(&conf, fdm, reaper, wayl)) == NULL)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
/* Remember to restore signals in slave */
|
volatile sig_atomic_t aborted = false;
|
||||||
const struct sigaction sa = {.sa_handler = &sig_handler};
|
if (!fdm_signal_add(fdm, SIGINT, &fdm_sigint, (void *)&aborted) ||
|
||||||
if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) {
|
!fdm_signal_add(fdm, SIGTERM, &fdm_sigint, (void *)&aborted))
|
||||||
LOG_ERRNO("failed to register signal handlers");
|
{
|
||||||
goto out;
|
goto out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -501,6 +500,8 @@ out:
|
||||||
render_destroy(renderer);
|
render_destroy(renderer);
|
||||||
wayl_destroy(wayl);
|
wayl_destroy(wayl);
|
||||||
reaper_destroy(reaper);
|
reaper_destroy(reaper);
|
||||||
|
fdm_signal_del(fdm, SIGTERM);
|
||||||
|
fdm_signal_del(fdm, SIGINT);
|
||||||
fdm_destroy(fdm);
|
fdm_destroy(fdm);
|
||||||
|
|
||||||
config_free(conf);
|
config_free(conf);
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ async_write(int fd, const void *data, size_t len, size_t *idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
fdm_add(struct fdm *fdm, int fd, int events, fdm_handler_t handler, void *data)
|
fdm_add(struct fdm *fdm, int fd, int events, fdm_fd_handler_t handler, void *data)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
114
reaper.c
114
reaper.c
|
|
@ -3,8 +3,8 @@
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <sys/epoll.h>
|
#include <sys/epoll.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <sys/wait.h>
|
#include <sys/wait.h>
|
||||||
#include <sys/signalfd.h>
|
|
||||||
#include <tllist.h>
|
#include <tllist.h>
|
||||||
|
|
||||||
#define LOG_MODULE "reaper"
|
#define LOG_MODULE "reaper"
|
||||||
|
|
@ -20,56 +20,32 @@ struct child {
|
||||||
|
|
||||||
struct reaper {
|
struct reaper {
|
||||||
struct fdm *fdm;
|
struct fdm *fdm;
|
||||||
int fd;
|
|
||||||
tll(struct child) children;
|
tll(struct child) children;
|
||||||
};
|
};
|
||||||
|
|
||||||
static bool fdm_reap(struct fdm *fdm, int fd, int events, void *data);
|
static bool fdm_reap(struct fdm *fdm, int signo, void *data);
|
||||||
|
|
||||||
struct reaper *
|
struct reaper *
|
||||||
reaper_init(struct fdm *fdm)
|
reaper_init(struct fdm *fdm)
|
||||||
{
|
{
|
||||||
sigset_t mask;
|
|
||||||
sigemptyset(&mask);
|
|
||||||
sigaddset(&mask, SIGCHLD);
|
|
||||||
|
|
||||||
/* Block normal signal handling - we're using a signalfd instead */
|
|
||||||
if (sigprocmask(SIG_BLOCK, &mask, NULL) < 0) {
|
|
||||||
LOG_ERRNO("failed to block SIGCHLD");
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
int fd = signalfd(-1, &mask, SFD_NONBLOCK | SFD_CLOEXEC);
|
|
||||||
if (fd < 0) {
|
|
||||||
LOG_ERRNO("failed to create signal FD");
|
|
||||||
sigprocmask(SIG_UNBLOCK, &mask, NULL);
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct reaper *reaper = malloc(sizeof(*reaper));
|
struct reaper *reaper = malloc(sizeof(*reaper));
|
||||||
if (unlikely(reaper == NULL)) {
|
if (unlikely(reaper == NULL)) {
|
||||||
LOG_ERRNO("malloc() failed");
|
LOG_ERRNO("malloc() failed");
|
||||||
close(fd);
|
|
||||||
sigprocmask(SIG_UNBLOCK, &mask, NULL);
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
*reaper = (struct reaper){
|
*reaper = (struct reaper){
|
||||||
.fdm = fdm,
|
.fdm = fdm,
|
||||||
.fd = fd,
|
|
||||||
.children = tll_init(),
|
.children = tll_init(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!fdm_add(fdm, fd, EPOLLIN, &fdm_reap, reaper)){
|
if (!fdm_signal_add(fdm, SIGCHLD, &fdm_reap, reaper))
|
||||||
LOG_ERR("failed to register with the FDM");
|
|
||||||
goto err;
|
goto err;
|
||||||
}
|
|
||||||
|
|
||||||
return reaper;
|
return reaper;
|
||||||
|
|
||||||
err:
|
err:
|
||||||
tll_free(reaper->children);
|
tll_free(reaper->children);
|
||||||
close(fd);
|
|
||||||
free(reaper);
|
free(reaper);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
@ -80,14 +56,9 @@ reaper_destroy(struct reaper *reaper)
|
||||||
if (reaper == NULL)
|
if (reaper == NULL)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
fdm_del(reaper->fdm, reaper->fd);
|
fdm_signal_del(reaper->fdm, SIGCHLD);
|
||||||
tll_free(reaper->children);
|
tll_free(reaper->children);
|
||||||
free(reaper);
|
free(reaper);
|
||||||
|
|
||||||
sigset_t mask;
|
|
||||||
sigemptyset(&mask);
|
|
||||||
sigaddset(&mask, SIGCHLD);
|
|
||||||
sigprocmask(SIG_UNBLOCK, &mask, NULL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
@ -111,69 +82,40 @@ reaper_del(struct reaper *reaper, pid_t pid)
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
fdm_reap(struct fdm *fdm, int fd, int events, void *data)
|
fdm_reap(struct fdm *fdm, int signo, void *data)
|
||||||
{
|
{
|
||||||
struct reaper *reaper = data;
|
struct reaper *reaper = data;
|
||||||
|
|
||||||
bool pollin = events & EPOLLIN;
|
while (true) {
|
||||||
bool hup = events & EPOLLHUP;
|
int status;
|
||||||
|
pid_t pid = waitpid(-1, &status, WNOHANG);
|
||||||
|
if (pid <= 0)
|
||||||
|
break;
|
||||||
|
|
||||||
if (hup && !pollin)
|
if (WIFEXITED(status))
|
||||||
return false;
|
LOG_DBG("pid=%d: exited with status=%d", pid, WEXITSTATUS(status));
|
||||||
|
else if (WIFSIGNALED(status))
|
||||||
|
LOG_DBG("pid=%d: killed by signal=%d", pid, WTERMSIG(status));
|
||||||
|
else
|
||||||
|
LOG_DBG("pid=%d: died of unknown resason", pid);
|
||||||
|
|
||||||
xassert(pollin);
|
tll_foreach(reaper->children, it) {
|
||||||
|
struct child *_child = &it->item;
|
||||||
|
|
||||||
struct signalfd_siginfo info;
|
if (_child->pid != pid)
|
||||||
ssize_t amount = read(reaper->fd, &info, sizeof(info));
|
|
||||||
|
|
||||||
if (amount < 0) {
|
|
||||||
LOG_ERRNO("failed to read");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
xassert((size_t)amount >= sizeof(info));
|
|
||||||
|
|
||||||
if (info.ssi_signo != SIGCHLD) {
|
|
||||||
LOG_WARN("got non-SIGCHLD signal: %d", info.ssi_signo);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
tll_foreach(reaper->children, it) {
|
|
||||||
struct child *_child = &it->item;
|
|
||||||
|
|
||||||
if (_child->pid != (pid_t)info.ssi_pid)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
/* Make sure we remove it *before* the callback, since it too
|
|
||||||
* may remove it */
|
|
||||||
struct child child = it->item;
|
|
||||||
tll_remove(reaper->children, it);
|
|
||||||
|
|
||||||
bool reap_ourselves = true;
|
|
||||||
if (child.cb != NULL)
|
|
||||||
reap_ourselves = !child.cb(reaper, child.pid, child.cb_data);
|
|
||||||
|
|
||||||
if (reap_ourselves) {
|
|
||||||
int result;
|
|
||||||
int res = waitpid(child.pid, &result, WNOHANG);
|
|
||||||
|
|
||||||
if (res <= 0) {
|
|
||||||
if (res < 0)
|
|
||||||
LOG_ERRNO("waitpid failed for pid=%d", child.pid);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
else if (WIFEXITED(result))
|
/* Make sure we remove it *before* the callback, since it too
|
||||||
LOG_DBG("pid=%d: exited with status=%d", pid, WEXITSTATUS(result));
|
* may remove it */
|
||||||
else if (WIFSIGNALED(result))
|
struct child child = it->item;
|
||||||
LOG_DBG("pid=%d: killed by signal=%d", pid, WTERMSIG(result));
|
tll_remove(reaper->children, it);
|
||||||
else
|
|
||||||
LOG_DBG("pid=%d: died of unknown resason", pid);
|
if (child.cb != NULL)
|
||||||
|
child.cb(reaper, child.pid, status, child.cb_data);
|
||||||
|
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hup)
|
|
||||||
return false;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
3
reaper.h
3
reaper.h
|
|
@ -10,7 +10,8 @@ struct reaper;
|
||||||
struct reaper *reaper_init(struct fdm *fdm);
|
struct reaper *reaper_init(struct fdm *fdm);
|
||||||
void reaper_destroy(struct reaper *reaper);
|
void reaper_destroy(struct reaper *reaper);
|
||||||
|
|
||||||
typedef bool (*reaper_cb)(struct reaper *reaper, pid_t pid, void *data);
|
typedef void (*reaper_cb)(
|
||||||
|
struct reaper *reaper, pid_t pid, int status, void *data);
|
||||||
|
|
||||||
void reaper_add(struct reaper *reaper, pid_t pid, reaper_cb cb, void *cb_data);
|
void reaper_add(struct reaper *reaper, pid_t pid, reaper_cb cb, void *cb_data);
|
||||||
void reaper_del(struct reaper *reaper, pid_t pid);
|
void reaper_del(struct reaper *reaper, pid_t pid);
|
||||||
|
|
|
||||||
5
render.c
5
render.c
|
|
@ -3,6 +3,7 @@
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
#include <wctype.h>
|
#include <wctype.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
#include <signal.h>
|
||||||
|
|
||||||
#include <sys/ioctl.h>
|
#include <sys/ioctl.h>
|
||||||
#include <sys/time.h>
|
#include <sys/time.h>
|
||||||
|
|
@ -1265,6 +1266,10 @@ render_worker_thread(void *_ctx)
|
||||||
const int my_id = ctx->my_id;
|
const int my_id = ctx->my_id;
|
||||||
free(ctx);
|
free(ctx);
|
||||||
|
|
||||||
|
sigset_t mask;
|
||||||
|
sigfillset(&mask);
|
||||||
|
pthread_sigmask(SIG_SETMASK, &mask, NULL);
|
||||||
|
|
||||||
char proc_title[16];
|
char proc_title[16];
|
||||||
snprintf(proc_title, sizeof(proc_title), "foot:render:%d", my_id);
|
snprintf(proc_title, sizeof(proc_title), "foot:render:%d", my_id);
|
||||||
|
|
||||||
|
|
|
||||||
110
terminal.c
110
terminal.c
|
|
@ -984,22 +984,25 @@ load_fonts_from_conf(struct terminal *term)
|
||||||
return reload_fonts(term);
|
return reload_fonts(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool
|
static void
|
||||||
slave_died(struct reaper *reaper, pid_t pid, void *data)
|
slave_died(struct reaper *reaper, pid_t pid, int status, void *data)
|
||||||
{
|
{
|
||||||
struct terminal *term = data;
|
struct terminal *term = data;
|
||||||
LOG_DBG("slave (PID=%u) died", pid);
|
LOG_DBG("slave (PID=%u) died", pid);
|
||||||
|
|
||||||
|
term->slave_has_been_reaped = true;
|
||||||
|
term->exit_status = status;
|
||||||
|
|
||||||
if (term->conf->hold_at_exit) {
|
if (term->conf->hold_at_exit) {
|
||||||
/* The PTMX FDM handler may already have closed our end */
|
/* The PTMX FDM handler may already have closed our end */
|
||||||
if (term->ptmx >= 0) {
|
if (term->ptmx >= 0) {
|
||||||
fdm_del(term->fdm, term->ptmx);
|
fdm_del(term->fdm, term->ptmx);
|
||||||
term->ptmx = -1;
|
term->ptmx = -1;
|
||||||
}
|
}
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
return term_shutdown(term);
|
term_shutdown(term);
|
||||||
}
|
}
|
||||||
|
|
||||||
struct terminal *
|
struct terminal *
|
||||||
|
|
@ -1075,7 +1078,6 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper,
|
||||||
.fdm = fdm,
|
.fdm = fdm,
|
||||||
.reaper = reaper,
|
.reaper = reaper,
|
||||||
.conf = conf,
|
.conf = conf,
|
||||||
.quit = false,
|
|
||||||
.ptmx = ptmx,
|
.ptmx = ptmx,
|
||||||
.ptmx_buffers = tll_init(),
|
.ptmx_buffers = tll_init(),
|
||||||
.ptmx_paste_buffers = tll_init(),
|
.ptmx_paste_buffers = tll_init(),
|
||||||
|
|
@ -1503,70 +1505,76 @@ term_destroy(struct terminal *term)
|
||||||
int ret = EXIT_SUCCESS;
|
int ret = EXIT_SUCCESS;
|
||||||
|
|
||||||
if (term->slave > 0) {
|
if (term->slave > 0) {
|
||||||
LOG_DBG("waiting for slave (PID=%u) to die", term->slave);
|
int exit_status;
|
||||||
|
|
||||||
/*
|
if (term->slave_has_been_reaped)
|
||||||
* Note: we've closed ptmx, so the slave *should* exit...
|
exit_status = term->exit_status;
|
||||||
*
|
else {
|
||||||
* But, since it is possible to write clients that ignore
|
LOG_DBG("waiting for slave (PID=%u) to die", term->slave);
|
||||||
* this, we need to handle it in *some* way.
|
|
||||||
*
|
|
||||||
* So, what we do is register a SIGALRM handler, and configure
|
|
||||||
* a 2 second alarm. If the slave hasn't died after this time,
|
|
||||||
* we send it a SIGTERM, then wait another 2 seconds (using
|
|
||||||
* the same alarm mechanism). If it still hasn't died, we send
|
|
||||||
* it a SIGKILL.
|
|
||||||
*
|
|
||||||
* Note that this solution is *not* asynchronous, and any
|
|
||||||
* other events etc will be ignored during this time. This of
|
|
||||||
* course only applies to a 'foot --server' instance, where
|
|
||||||
* there might be other terminals running.
|
|
||||||
*/
|
|
||||||
sigaction(SIGALRM, &(const struct sigaction){.sa_handler = &sig_alarm}, NULL);
|
|
||||||
alarm(2);
|
|
||||||
|
|
||||||
int status;
|
/*
|
||||||
int kill_signal = SIGTERM;
|
* Note: we've closed ptmx, so the slave *should* exit...
|
||||||
|
*
|
||||||
|
* But, since it is possible to write clients that ignore
|
||||||
|
* this, we need to handle it in *some* way.
|
||||||
|
*
|
||||||
|
* So, what we do is register a SIGALRM handler, and configure
|
||||||
|
* a 2 second alarm. If the slave hasn't died after this time,
|
||||||
|
* we send it a SIGTERM, then wait another 2 seconds (using
|
||||||
|
* the same alarm mechanism). If it still hasn't died, we send
|
||||||
|
* it a SIGKILL.
|
||||||
|
*
|
||||||
|
* Note that this solution is *not* asynchronous, and any
|
||||||
|
* other events etc will be ignored during this time. This of
|
||||||
|
* course only applies to a 'foot --server' instance, where
|
||||||
|
* there might be other terminals running.
|
||||||
|
*/
|
||||||
|
sigaction(SIGALRM, &(const struct sigaction){.sa_handler = &sig_alarm}, NULL);
|
||||||
|
alarm(2);
|
||||||
|
|
||||||
while (true) {
|
int kill_signal = SIGTERM;
|
||||||
int r = waitpid(term->slave, &status, 0);
|
|
||||||
|
|
||||||
if (r == term->slave)
|
while (true) {
|
||||||
break;
|
int r = waitpid(term->slave, &exit_status, 0);
|
||||||
|
|
||||||
if (r == -1) {
|
if (r == term->slave)
|
||||||
xassert(errno == EINTR);
|
break;
|
||||||
|
|
||||||
if (alarm_raised) {
|
if (r == -1) {
|
||||||
LOG_DBG("slave hasn't died yet, sending: %s (%d)",
|
xassert(errno == EINTR);
|
||||||
kill_signal == SIGTERM ? "SIGTERM" : "SIGKILL",
|
|
||||||
kill_signal);
|
|
||||||
|
|
||||||
kill(term->slave, kill_signal);
|
if (alarm_raised) {
|
||||||
|
LOG_DBG("slave hasn't died yet, sending: %s (%d)",
|
||||||
|
kill_signal == SIGTERM ? "SIGTERM" : "SIGKILL",
|
||||||
|
kill_signal);
|
||||||
|
|
||||||
alarm_raised = 0;
|
kill(term->slave, kill_signal);
|
||||||
if (kill_signal != SIGKILL)
|
|
||||||
alarm(2);
|
|
||||||
|
|
||||||
kill_signal = SIGKILL;
|
alarm_raised = 0;
|
||||||
|
if (kill_signal != SIGKILL)
|
||||||
|
alarm(2);
|
||||||
|
|
||||||
|
kill_signal = SIGKILL;
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Cancel alarm */
|
||||||
|
alarm(0);
|
||||||
|
sigaction(SIGALRM, &(const struct sigaction){.sa_handler = SIG_DFL}, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Cancel alarm */
|
|
||||||
alarm(0);
|
|
||||||
sigaction(SIGALRM, &(const struct sigaction){.sa_handler = SIG_DFL}, NULL);
|
|
||||||
|
|
||||||
ret = EXIT_FAILURE;
|
ret = EXIT_FAILURE;
|
||||||
if (WIFEXITED(status)) {
|
if (WIFEXITED(exit_status)) {
|
||||||
ret = WEXITSTATUS(status);
|
ret = WEXITSTATUS(exit_status);
|
||||||
LOG_DBG("slave exited with code %d", ret);
|
LOG_DBG("slave exited with code %d", ret);
|
||||||
} else if (WIFSIGNALED(status)) {
|
} else if (WIFSIGNALED(exit_status)) {
|
||||||
ret = WTERMSIG(status);
|
ret = WTERMSIG(exit_status);
|
||||||
LOG_WARN("slave exited with signal %d (%s)", ret, strsignal(ret));
|
LOG_WARN("slave exited with signal %d (%s)", ret, strsignal(ret));
|
||||||
} else {
|
} else {
|
||||||
LOG_WARN("slave exited for unknown reason (status = 0x%08x)", status);
|
LOG_WARN("slave exited for unknown reason (status = 0x%08x)",
|
||||||
|
exit_status);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -534,8 +534,9 @@ struct terminal {
|
||||||
} ime;
|
} ime;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
bool quit;
|
|
||||||
bool is_shutting_down;
|
bool is_shutting_down;
|
||||||
|
bool slave_has_been_reaped;
|
||||||
|
int exit_status;
|
||||||
void (*shutdown_cb)(void *data, int exit_code);
|
void (*shutdown_cb)(void *data, int exit_code);
|
||||||
void *shutdown_data;
|
void *shutdown_data;
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue