diff options
Diffstat (limited to 'src/sighook.c')
-rw-r--r-- | src/sighook.c | 308 |
1 files changed, 138 insertions, 170 deletions
diff --git a/src/sighook.c b/src/sighook.c index ff5b96f..0cc81fa 100644 --- a/src/sighook.c +++ b/src/sighook.c @@ -17,19 +17,29 @@ * drop to zero. Once it does, the old pointer can be safely freed. */ -#include "prelude.h" #include "sighook.h" + #include "alloc.h" #include "atomic.h" +#include "bfs.h" #include "bfstd.h" #include "diag.h" #include "thread.h" + #include <errno.h> +#include <pthread.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> -#if _POSIX_SEMAPHORES > 0 +// NetBSD opens a file descriptor for each sem_init() +#if defined(_POSIX_SEMAPHORES) && !__NetBSD__ +# define BFS_POSIX_SEMAPHORES _POSIX_SEMAPHORES +#else +# define BFS_POSIX_SEMAPHORES (-1) +#endif + +#if BFS_POSIX_SEMAPHORES >= 0 # include <semaphore.h> #endif @@ -42,8 +52,8 @@ struct arc { /** The reference itself. */ void *ptr; -#if _POSIX_SEMAPHORES > 0 - /** A semaphore for arc_wake(). */ +#if BFS_POSIX_SEMAPHORES >= 0 + /** A semaphore for arc_wait(). */ sem_t sem; /** sem_init() result. */ int sem_status; @@ -52,11 +62,17 @@ struct arc { /** Initialize an arc. */ static void arc_init(struct arc *arc) { + bfs_verify(atomic_is_lock_free(&arc->refs)); + atomic_init(&arc->refs, 0); arc->ptr = NULL; -#if _POSIX_SEMAPHORES > 0 - arc->sem_status = sem_init(&arc->sem, false, 0); +#if BFS_POSIX_SEMAPHORES >= 0 + if (sysoption(SEMAPHORES) > 0) { + arc->sem_status = sem_init(&arc->sem, false, 0); + } else { + arc->sem_status = -1; + } #endif } @@ -91,7 +107,7 @@ static void arc_put(struct arc *arc) { size_t refs = fetch_sub(&arc->refs, 1, release); if (refs == 1) { -#if _POSIX_SEMAPHORES > 0 +#if BFS_POSIX_SEMAPHORES >= 0 if (arc->sem_status == 0 && sem_post(&arc->sem) != 0) { abort(); } @@ -101,7 +117,7 @@ static void arc_put(struct arc *arc) { /** Wait on the semaphore. */ static int arc_sem_wait(struct arc *arc) { -#if _POSIX_SEMAPHORES > 0 +#if BFS_POSIX_SEMAPHORES >= 0 if (arc->sem_status == 0) { while (sem_wait(&arc->sem) != 0) { bfs_everify(errno == EINTR, "sem_wait()"); @@ -142,9 +158,9 @@ done:; /** Destroy an arc. */ static void arc_destroy(struct arc *arc) { - bfs_assert(arc_refs(arc) <= 1); + bfs_assert(arc_refs(arc) == 0); -#if _POSIX_SEMAPHORES > 0 +#if BFS_POSIX_SEMAPHORES >= 0 if (arc->sem_status == 0) { bfs_everify(sem_destroy(&arc->sem) == 0, "sem_destroy()"); } @@ -164,12 +180,25 @@ struct rcu { /** Sentinel value for RCU, since arc uses NULL already. */ static void *RCU_NULL = &RCU_NULL; +/** Map NULL -> RCU_NULL. */ +static void *rcu_encode(void *ptr) { + return ptr ? ptr : RCU_NULL; +} + +/** Map RCU_NULL -> NULL. */ +static void *rcu_decode(void *ptr) { + bfs_assert(ptr != NULL); + return ptr == RCU_NULL ? NULL : ptr; +} + /** Initialize an RCU block. */ -static void rcu_init(struct rcu *rcu) { +static void rcu_init(struct rcu *rcu, void *ptr) { + bfs_verify(atomic_is_lock_free(&rcu->active)); + atomic_init(&rcu->active, 0); arc_init(&rcu->slots[0]); arc_init(&rcu->slots[1]); - arc_set(&rcu->slots[0], RCU_NULL); + arc_set(&rcu->slots[0], rcu_encode(ptr)); } /** Get the active slot. */ @@ -178,15 +207,20 @@ static struct arc *rcu_active(struct rcu *rcu) { return &rcu->slots[i]; } +/** Destroy an RCU block. */ +static void rcu_destroy(struct rcu *rcu) { + arc_wait(rcu_active(rcu)); + arc_destroy(&rcu->slots[1]); + arc_destroy(&rcu->slots[0]); +} + /** Read an RCU-protected pointer. */ static void *rcu_read(struct rcu *rcu, struct arc **slot) { while (true) { *slot = rcu_active(rcu); void *ptr = arc_get(*slot); - if (ptr == RCU_NULL) { - return NULL; - } else if (ptr) { - return ptr; + if (ptr) { + return rcu_decode(ptr); } // Otherwise, the other slot became active; retry } @@ -195,12 +229,7 @@ static void *rcu_read(struct rcu *rcu, struct arc **slot) { /** Get the RCU-protected pointer without acquiring a reference. */ static void *rcu_peek(struct rcu *rcu) { struct arc *arc = rcu_active(rcu); - void *ptr = arc->ptr; - if (ptr == RCU_NULL) { - return NULL; - } else { - return ptr; - } + return rcu_decode(arc->ptr); } /** Update an RCU-protected pointer, and return the old one. */ @@ -211,129 +240,76 @@ static void *rcu_update(struct rcu *rcu, void *ptr) { size_t j = i ^ 1; struct arc *next = &rcu->slots[j]; - arc_set(next, ptr ? ptr : RCU_NULL); + arc_set(next, rcu_encode(ptr)); store(&rcu->active, j, relaxed); - return arc_wait(prev); + return rcu_decode(arc_wait(prev)); } struct sighook { + /** The signal being hooked, or 0 for atsigexit(). */ int sig; + /** Signal hook flags. */ + enum sigflags flags; + /** The function to call. */ sighook_fn *fn; + /** An argument to pass to the function. */ void *arg; - enum sigflags flags; + + /** The RCU pointer to this hook. */ + struct rcu *self; + /** The next hook in the list. */ + struct rcu next; }; /** - * A table of signal hooks. + * An RCU-protected linked list of signal hooks. */ -struct sigtable { - /** The number of filled slots. */ - size_t filled; - /** The length of the array. */ - size_t size; - /** An array of signal hooks. */ - struct arc hooks[]; +struct siglist { + /** The first hook in the list. */ + struct rcu head; + /** &last->next */ + struct rcu *tail; }; -/** Add a hook to a table. */ -static int sigtable_add(struct sigtable *table, struct sighook *hook) { - if (!table || table->filled == table->size) { - return -1; - } - - for (size_t i = 0; i < table->size; ++i) { - struct arc *arc = &table->hooks[i]; - if (arc_refs(arc) == 0) { - arc_set(arc, hook); - ++table->filled; - return 0; - } - } - - return -1; +/** Initialize a siglist. */ +static void siglist_init(struct siglist *list) { + rcu_init(&list->head, NULL); + list->tail = &list->head; } -/** Delete a hook from a table. */ -static int sigtable_del(struct sigtable *table, struct sighook *hook) { - for (size_t i = 0; i < table->size; ++i) { - struct arc *arc = &table->hooks[i]; - if (arc->ptr == hook) { - arc_wait(arc); - --table->filled; - return 0; - } - } - - return -1; +/** Append a hook to a linked list. */ +static void sigpush(struct siglist *list, struct sighook *hook) { + hook->self = list->tail; + list->tail = &hook->next; + rcu_init(&hook->next, NULL); + rcu_update(hook->self, hook); } -/** Create a bigger copy of a signal table. */ -static struct sigtable *sigtable_grow(struct sigtable *prev) { - size_t old_size = prev ? prev->size : 0; - size_t new_size = old_size ? 2 * old_size : 1; - struct sigtable *table = ALLOC_FLEX(struct sigtable, hooks, new_size); - if (!table) { - return NULL; - } - - table->filled = 0; - table->size = new_size; - for (size_t i = 0; i < new_size; ++i) { - arc_init(&table->hooks[i]); - } - - for (size_t i = 0; i < old_size; ++i) { - struct sighook *hook = prev->hooks[i].ptr; - if (hook) { - bfs_verify(sigtable_add(table, hook) == 0); - } - } - - return table; -} - -/** Free a signal table. */ -static void sigtable_free(struct sigtable *table) { - if (!table) { - return; - } - - for (size_t i = 0; i < table->size; ++i) { - struct arc *arc = &table->hooks[i]; - arc_destroy(arc); +/** Remove a hook from the linked list. */ +static void sigpop(struct siglist *list, struct sighook *hook) { + struct sighook *next = rcu_peek(&hook->next); + rcu_update(hook->self, next); + if (next) { + next->self = hook->self; + } else { + list->tail = &list->head; } - free(table); } -/** Add a hook to a signal table, growing it if necessary. */ -static int rcu_sigtable_add(struct rcu *rcu, struct sighook *hook) { - struct sigtable *prev = rcu_peek(rcu); - if (sigtable_add(prev, hook) == 0) { - return 0; - } - - struct sigtable *next = sigtable_grow(prev); - if (!next) { - return -1; - } +/** The lists of signal hooks. */ +static struct siglist sighooks[64]; - bfs_verify(sigtable_add(next, hook) == 0); - rcu_update(rcu, next); - sigtable_free(prev); - return 0; +/** Get the hook list for a particular signal. */ +static struct siglist *siglist(int sig) { + return &sighooks[sig % countof(sighooks)]; } -/** The global table of signal hooks. */ -static struct rcu rcu_sighooks; -/** The global table of atsigexit() hooks. */ -static struct rcu rcu_exithooks; - /** Mutex for initialization and RCU writer exclusion. */ static pthread_mutex_t sigmutex = PTHREAD_MUTEX_INITIALIZER; /** Check if a signal was generated by userspace. */ static bool is_user_generated(const siginfo_t *info) { - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_03 // // If si_code is SI_USER or SI_QUEUE, or any value less than or // equal to 0, then the signal was generated by a process ... @@ -351,7 +327,7 @@ static bool is_fault(const siginfo_t *info) { } } -// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html +// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html static const int FATAL_SIGNALS[] = { SIGABRT, SIGALRM, @@ -392,7 +368,7 @@ static bool is_fatal(int sig) { } #ifdef SIGRTMIN - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_01 // // The default actions for the realtime signals in the range // SIGRTMIN to SIGRTMAX shall be to terminate the process @@ -406,7 +382,8 @@ static bool is_fatal(int sig) { } /** Reraise a fatal signal. */ -static noreturn void reraise(int sig) { +_noreturn +static void reraise(int sig) { // Restore the default signal action if (signal(sig, SIG_DFL) == SIG_ERR) { goto fail; @@ -426,36 +403,29 @@ fail: } /** Find any matching hooks and run them. */ -static enum sigflags run_hooks(struct rcu *rcu, int sig, siginfo_t *info) { +static enum sigflags run_hooks(struct siglist *list, int sig, siginfo_t *info) { enum sigflags ret = 0; - struct arc *slot; - struct sigtable *table = rcu_read(rcu, &slot); - if (!table) { - goto done; - } - - for (size_t i = 0; i < table->size; ++i) { - struct arc *arc = &table->hooks[i]; - struct sighook *hook = arc_get(arc); - if (!hook) { - continue; - } + struct arc *slot = NULL; + struct sighook *hook = rcu_read(&list->head, &slot); + while (hook) { if (hook->sig == sig || hook->sig == 0) { hook->fn(sig, info, hook->arg); ret |= hook->flags; } - arc_put(arc); + + struct arc *prev = slot; + hook = rcu_read(&hook->next, &slot); + arc_put(prev); } -done: arc_put(slot); return ret; } /** Dispatches a signal to the registered handlers. */ static void sigdispatch(int sig, siginfo_t *info, void *context) { - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_03 // // The behavior of a process is undefined after it returns normally // from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or @@ -465,7 +435,7 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) { reraise(sig); } - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_04 // // After returning from a signal-catching function, the value of // errno is unspecified if the signal-catching function or any @@ -475,11 +445,13 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) { int error = errno; // Run the normal hooks - enum sigflags flags = run_hooks(&rcu_sighooks, sig, info); + struct siglist *list = siglist(sig); + enum sigflags flags = run_hooks(list, sig, info); // Run the atsigexit() hooks, if we're exiting if (!(flags & SH_CONTINUE) && is_fatal(sig)) { - run_hooks(&rcu_exithooks, sig, info); + list = siglist(0); + run_hooks(list, sig, info); reraise(sig); } @@ -501,8 +473,11 @@ static int siginit(int sig) { || sigemptyset(&action.sa_mask) != 0) { return -1; } - rcu_init(&rcu_sighooks); - rcu_init(&rcu_exithooks); + + for (size_t i = 0; i < countof(sighooks); ++i) { + siglist_init(&sighooks[i]); + } + initialized = true; } @@ -525,35 +500,32 @@ static int siginit(int sig) { } /** Shared sighook()/atsigexit() implementation. */ -static struct sighook *sighook_impl(struct rcu *rcu, int sig, sighook_fn *fn, void *arg, enum sigflags flags) { +static struct sighook *sighook_impl(int sig, sighook_fn *fn, void *arg, enum sigflags flags) { struct sighook *hook = ALLOC(struct sighook); if (!hook) { return NULL; } hook->sig = sig; + hook->flags = flags; hook->fn = fn; hook->arg = arg; - hook->flags = flags; - - if (rcu_sigtable_add(rcu, hook) != 0) { - free(hook); - return NULL; - } + struct siglist *list = siglist(sig); + sigpush(list, hook); return hook; } struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags) { + bfs_assert(sig > 0); + mutex_lock(&sigmutex); struct sighook *ret = NULL; - if (siginit(sig) != 0) { - goto done; + if (siginit(sig) == 0) { + ret = sighook_impl(sig, fn, arg, flags); } - ret = sighook_impl(&rcu_sighooks, sig, fn, arg, flags); -done: mutex_unlock(&sigmutex); return ret; } @@ -561,40 +533,36 @@ done: struct sighook *atsigexit(sighook_fn *fn, void *arg) { mutex_lock(&sigmutex); - struct sighook *ret = NULL; - for (size_t i = 0; i < countof(FATAL_SIGNALS); ++i) { - if (siginit(FATAL_SIGNALS[i]) != 0) { - goto done; - } + // Ignore errors; atsigexit() is best-effort anyway and things + // like sanitizer runtimes or valgrind may reserve signals for + // their own use + siginit(FATAL_SIGNALS[i]); } #ifdef SIGRTMIN for (int i = SIGRTMIN; i <= SIGRTMAX; ++i) { - if (siginit(i) != 0) { - goto done; - } + siginit(i); } #endif - ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0); -done: + struct sighook *ret = sighook_impl(0, fn, arg, 0); mutex_unlock(&sigmutex); return ret; } void sigunhook(struct sighook *hook) { - mutex_lock(&sigmutex); + if (!hook) { + return; + } - struct rcu *rcu = hook->sig ? &rcu_sighooks : &rcu_exithooks; - struct sigtable *table = rcu_peek(rcu); - bfs_verify(sigtable_del(table, hook) == 0); + mutex_lock(&sigmutex); - if (table->filled == 0) { - rcu_update(rcu, NULL); - sigtable_free(table); - } + struct siglist *list = siglist(hook->sig); + sigpop(list, hook); mutex_unlock(&sigmutex); + + rcu_destroy(&hook->next); free(hook); } |