summaryrefslogtreecommitdiffstats
path: root/src/sighook.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sighook.c')
-rw-r--r--src/sighook.c308
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);
}