280 lines
6.5 KiB
C
280 lines
6.5 KiB
C
#include <pthread.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <signal.h>
|
|
#include <stdlib.h>
|
|
#include <errno.h>
|
|
#include <assert.h>
|
|
#include <unistd.h>
|
|
#include "ucore/tailq.h"
|
|
#include "ucore/fd_utils.h"
|
|
#include "ucore/iomux_signal.h"
|
|
|
|
/* Integrating of unix signals (man 7 signal) with
|
|
* an IOMux event loop.
|
|
* This is done by setting up a pipe, registring a handler with
|
|
* the IOMux to handle the read end to that pipe, and writing
|
|
* the signal number to that pipe in the signal handler.
|
|
*
|
|
* Currently the signal number is treated as an uint8_t to avoid
|
|
* dealing with the extremely unlikely case of partial writes in
|
|
* a signal handler. Currently this is more than enough for the max value of
|
|
* signals on linux at least.
|
|
*
|
|
*/
|
|
|
|
struct IOMuxSignalEntry {
|
|
struct TailQ entry; //within SignalData.head
|
|
int signo;
|
|
IOMuxSignalHandler callback;
|
|
void *cookie;
|
|
struct sigaction old_sa;
|
|
};
|
|
|
|
struct SignalData {
|
|
struct TailQ head; //of IOMuxSignalEntry
|
|
struct IOMuxFD fd;
|
|
int pipes[2];
|
|
struct IOMux *mux;
|
|
int initialized;
|
|
};
|
|
|
|
static pthread_mutex_t g_signal_lock = PTHREAD_MUTEX_INITIALIZER;
|
|
|
|
//protected by g_signal_lock
|
|
struct SignalData g_signal_global;
|
|
|
|
static inline void uc_signal_lock(void)
|
|
{
|
|
int rc = pthread_mutex_lock(&g_signal_lock);
|
|
assert(rc == 0);
|
|
}
|
|
|
|
static inline void uc_signal_unlock(void)
|
|
{
|
|
int rc = pthread_mutex_unlock(&g_signal_lock);
|
|
assert(rc == 0);
|
|
}
|
|
|
|
//call with lock held
|
|
static struct IOMuxSignalEntry *uc_find_signal_entry(int signo)
|
|
{
|
|
struct IOMuxSignalEntry *it;
|
|
|
|
UC_TAILQ_FOREACH_CONTAINER(it, entry, &g_signal_global.head) {
|
|
if (it->signo == signo) {
|
|
return it;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
//call with lock held
|
|
static void uc_deinitailize_iomux_signals(void)
|
|
{
|
|
assert(g_signal_global.mux != NULL);
|
|
assert(g_signal_global.initialized == 1);
|
|
assert(uc_tailq_empty(&g_signal_global.head));
|
|
|
|
iomux_unregister_fd(g_signal_global.mux, &g_signal_global.fd);
|
|
|
|
close(g_signal_global.pipes[0]);
|
|
close(g_signal_global.pipes[1]);
|
|
|
|
g_signal_global.mux = NULL;
|
|
g_signal_global.initialized = 0;
|
|
g_signal_global.fd.callback = NULL;
|
|
g_signal_global.fd.fd = -1;
|
|
}
|
|
|
|
//IOMux fd callback, which dispatches the signal to the registered handler
|
|
static void uc_iomux_signal_cb(struct IOMux *mux, struct IOMuxFD *fd, unsigned int events)
|
|
{
|
|
|
|
uint8_t signo;
|
|
ssize_t rc;
|
|
struct IOMuxSignalEntry *entry = NULL;
|
|
|
|
uc_signal_lock();
|
|
|
|
rc = read(fd->fd, &signo, 1);
|
|
if (rc == 1) {
|
|
entry = uc_find_signal_entry(signo);
|
|
}
|
|
|
|
assert(rc != -1 || errno == EWOULDBLOCK);
|
|
|
|
//need to unlock here, otherwise we deadlock if the callback
|
|
//registers/unregisters a signal
|
|
uc_signal_unlock();
|
|
|
|
if (entry != NULL) {
|
|
entry->callback(mux, signo, entry->cookie);
|
|
//note don't touch entry after this, the callback might
|
|
//have unregistered itself
|
|
}
|
|
}
|
|
|
|
//unix signal handler
|
|
static void uc_global_signal_handler(int signo)
|
|
{
|
|
uint8_t data = signo;
|
|
int save_errno = errno;
|
|
|
|
write(g_signal_global.pipes[1], &data, 1);
|
|
|
|
errno = save_errno;
|
|
}
|
|
|
|
//call with lock held
|
|
static int uc_initailize_iomux_signals(struct IOMux *mux)
|
|
{
|
|
int rc = 0;
|
|
|
|
assert(g_signal_global.mux == NULL);
|
|
assert(g_signal_global.initialized == 0);
|
|
|
|
rc = pipe(g_signal_global.pipes);
|
|
if (rc != 0) {
|
|
return errno;
|
|
}
|
|
|
|
uc_set_nonblocking(g_signal_global.pipes[0]);
|
|
uc_set_nonblocking(g_signal_global.pipes[1]);
|
|
|
|
uc_tailq_init(&g_signal_global.head);
|
|
|
|
g_signal_global.mux = mux;
|
|
g_signal_global.fd.fd = g_signal_global.pipes[0];
|
|
g_signal_global.fd.what = MUX_EV_READ;
|
|
g_signal_global.fd.callback = uc_iomux_signal_cb;
|
|
|
|
rc = iomux_register_fd(mux, &g_signal_global.fd);
|
|
if (rc == 0) {
|
|
g_signal_global.initialized = 1;
|
|
} else {
|
|
uc_deinitailize_iomux_signals();
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
static struct IOMuxSignalEntry *uc_signal_entry_new(int signo,
|
|
IOMuxSignalHandler handler,
|
|
void *cookie)
|
|
{
|
|
struct IOMuxSignalEntry *entry;
|
|
|
|
entry = calloc(1, sizeof *entry);
|
|
if (entry == NULL) {
|
|
return NULL;
|
|
}
|
|
|
|
entry->signo = signo;
|
|
entry->callback = handler;
|
|
entry->cookie = cookie;
|
|
|
|
return entry;
|
|
}
|
|
|
|
|
|
static int iomux_register_unixsignal_internal(struct IOMux *mux,
|
|
int signo,
|
|
IOMuxSignalHandler handler,
|
|
void *cookie)
|
|
{
|
|
struct IOMuxSignalEntry *entry;
|
|
int rc;
|
|
struct sigaction sa;
|
|
|
|
if (!g_signal_global.initialized) {
|
|
rc = uc_initailize_iomux_signals(mux);
|
|
if (rc != 0) {
|
|
return rc;
|
|
}
|
|
}
|
|
|
|
//Only one mux can handle signals
|
|
if (g_signal_global.mux != mux) {
|
|
return EINVAL;
|
|
}
|
|
|
|
entry = uc_find_signal_entry(signo);
|
|
if (entry != NULL) {
|
|
return EEXIST;
|
|
}
|
|
|
|
entry = uc_signal_entry_new(signo, handler, cookie);
|
|
if (entry == NULL) {
|
|
return ENOMEM;
|
|
}
|
|
|
|
memset(&sa, 0, sizeof sa);
|
|
sa.sa_handler = uc_global_signal_handler;
|
|
sa.sa_flags = SA_RESTART;
|
|
sigfillset(&sa.sa_mask);
|
|
|
|
rc = sigaction(signo, &sa, &entry->old_sa);
|
|
if (rc == 0) {
|
|
uc_tailq_insert_tail(&g_signal_global.head, &entry->entry);
|
|
} else {
|
|
rc = errno;
|
|
free(entry);
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int iomux_register_unixsignal(struct IOMux *mux,
|
|
int signo,
|
|
IOMuxSignalHandler handler,
|
|
void *cookie)
|
|
{
|
|
int rc;
|
|
|
|
uc_signal_lock();
|
|
|
|
rc = iomux_register_unixsignal_internal(mux, signo, handler, cookie);
|
|
|
|
if (g_signal_global.initialized && uc_tailq_empty(&g_signal_global.head)) {
|
|
//we might have initialized the iomux but otherwise failed
|
|
uc_deinitailize_iomux_signals();
|
|
}
|
|
|
|
uc_signal_unlock();
|
|
|
|
return rc;
|
|
}
|
|
|
|
int iomux_unregister_unixsignal(struct IOMux *mux, int signo)
|
|
{
|
|
struct IOMuxSignalEntry *entry;
|
|
int rc = ENOENT;
|
|
|
|
uc_signal_lock();
|
|
|
|
if (!g_signal_global.initialized || g_signal_global.mux != mux) {
|
|
uc_signal_unlock();
|
|
return EINVAL;
|
|
}
|
|
|
|
|
|
entry = uc_find_signal_entry(signo);
|
|
if (entry != NULL) {
|
|
uc_tailq_remove(&entry->entry);
|
|
rc = sigaction(entry->signo, &entry->old_sa, NULL);
|
|
assert(rc == 0);
|
|
free(entry);
|
|
}
|
|
|
|
if (uc_tailq_empty(&g_signal_global.head)) {
|
|
uc_deinitailize_iomux_signals();
|
|
}
|
|
|
|
uc_signal_unlock();
|
|
|
|
return rc;
|
|
}
|
|
|