summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/atomic.h33
-rw-r--r--src/bar.c137
-rw-r--r--src/bfstd.c31
-rw-r--r--src/bfstd.h5
-rw-r--r--src/bftw.c6
-rw-r--r--src/color.c50
-rw-r--r--src/color.h7
-rw-r--r--src/ctx.c104
-rw-r--r--src/diag.c4
-rw-r--r--src/diag.h43
-rw-r--r--src/dir.c16
-rw-r--r--src/dir.h8
-rw-r--r--src/dstring.c4
-rw-r--r--src/dstring.h4
-rw-r--r--src/eval.c2
-rw-r--r--src/exec.c6
-rw-r--r--src/fsade.c2
-rw-r--r--src/main.c1
-rw-r--r--src/mtab.c9
-rw-r--r--src/prelude.h15
-rw-r--r--src/printf.c2
-rw-r--r--src/sighook.c600
-rw-r--r--src/sighook.h73
-rw-r--r--src/stat.c37
-rw-r--r--src/xspawn.c37
-rw-r--r--src/xtime.c40
26 files changed, 1065 insertions, 211 deletions
diff --git a/src/atomic.h b/src/atomic.h
index f1a6bea..360de20 100644
--- a/src/atomic.h
+++ b/src/atomic.h
@@ -8,6 +8,8 @@
#ifndef BFS_ATOMIC_H
#define BFS_ATOMIC_H
+#include "prelude.h"
+#include "sanity.h"
#include <stdatomic.h>
/**
@@ -82,4 +84,35 @@
#define fetch_and(obj, arg, order) \
atomic_fetch_and_explicit(obj, arg, memory_order_##order)
+/**
+ * Shorthand for atomic_thread_fence().
+ */
+#if SANITIZE_THREAD
+// TSan doesn't support fences: https://github.com/google/sanitizers/issues/1415
+# define thread_fence(obj, order) \
+ fetch_add(obj, 0, order)
+#else
+# define thread_fence(obj, order) \
+ atomic_thread_fence(memory_order_##order)
+#endif
+
+/**
+ * Shorthand for atomic_signal_fence().
+ */
+#define signal_fence(order) \
+ atomic_signal_fence(memory_order_##order)
+
+/**
+ * A hint to the CPU to relax while it spins.
+ */
+#if __has_builtin(__builtin_ia32_pause)
+# define spin_loop() __builtin_ia32_pause()
+#elif __has_builtin(__builtin_arm_yield)
+# define spin_loop() __builtin_arm_yield()
+#elif __has_builtin(__builtin_riscv_pause)
+# define spin_loop() __builtin_riscv_pause()
+#else
+# define spin_loop() ((void)0)
+#endif
+
#endif // BFS_ATOMIC_H
diff --git a/src/bar.c b/src/bar.c
index 184d9a0..b928373 100644
--- a/src/bar.c
+++ b/src/bar.c
@@ -3,10 +3,12 @@
#include "prelude.h"
#include "bar.h"
+#include "alloc.h"
#include "atomic.h"
#include "bfstd.h"
#include "bit.h"
#include "dstring.h"
+#include "sighook.h"
#include <errno.h>
#include <fcntl.h>
#include <signal.h>
@@ -19,11 +21,9 @@ struct bfs_bar {
int fd;
atomic unsigned int width;
atomic unsigned int height;
-};
-/** The global status bar instance. */
-static struct bfs_bar the_bar = {
- .fd = -1,
+ struct sighook *exit_hook;
+ struct sighook *winch_hook;
};
/** Get the terminal size, if possible. */
@@ -43,10 +43,14 @@ static int bfs_bar_getsize(struct bfs_bar *bar) {
#endif
}
-/** Async Signal Safe puts(). */
-static int ass_puts(int fd, const char *str) {
- size_t len = strlen(str);
- return xwrite(fd, str, len) == len ? 0 : -1;
+/** Write a string to the status bar (async-signal-safe). */
+static int bfs_bar_write(struct bfs_bar *bar, const char *str, size_t len) {
+ return xwrite(bar->fd, str, len) == len ? 0 : -1;
+}
+
+/** Write a string to the status bar (async-signal-safe). */
+static int bfs_bar_puts(struct bfs_bar *bar, const char *str) {
+ return bfs_bar_write(bar, str, strlen(str));
}
/** Number of decimal digits needed for terminal sizes. */
@@ -70,39 +74,40 @@ static char *ass_itoa(char *str, unsigned int n) {
/** Update the size of the scrollable region. */
static int bfs_bar_resize(struct bfs_bar *bar) {
- char esc_seq[12 + ITOA_DIGITS] =
+ static const char PREFIX[] =
+ "\033D" // IND: Line feed, possibly scrolling
+ "\033[1A" // CUU: Move cursor up 1 row
"\0337" // DECSC: Save cursor
"\033[;"; // DECSTBM: Set scrollable region
+ static const char SUFFIX[] =
+ "r" // (end of DECSTBM)
+ "\0338" // DECRC: Restore the cursor
+ "\033[J"; // ED: Erase display from cursor to end
+
+ char esc_seq[sizeof(PREFIX) + ITOA_DIGITS + sizeof(SUFFIX)];
// DECSTBM takes the height as the second argument
- unsigned int height = load(&bar->height, relaxed);
- char *ptr = esc_seq + strlen(esc_seq);
- ptr = ass_itoa(ptr, height - 1);
+ unsigned int height = load(&bar->height, relaxed) - 1;
- strcpy(ptr,
- "r" // DECSTBM
- "\0338" // DECRC: Restore the cursor
- "\033[J" // ED: Erase display from cursor to end
- );
+ char *cur = stpcpy(esc_seq, PREFIX);
+ cur = ass_itoa(cur, height);
+ cur = stpcpy(cur, SUFFIX);
- return ass_puts(bar->fd, esc_seq);
+ return bfs_bar_write(bar, esc_seq, cur - esc_seq);
}
#ifdef SIGWINCH
/** SIGWINCH handler. */
-static void sighand_winch(int sig) {
- int error = errno;
-
- bfs_bar_getsize(&the_bar);
- bfs_bar_resize(&the_bar);
-
- errno = error;
+static void bfs_bar_sigwinch(int sig, siginfo_t *info, void *arg) {
+ struct bfs_bar *bar = arg;
+ bfs_bar_getsize(bar);
+ bfs_bar_resize(bar);
}
#endif
/** Reset the scrollable region and hide the bar. */
static int bfs_bar_reset(struct bfs_bar *bar) {
- return ass_puts(bar->fd,
+ return bfs_bar_puts(bar,
"\0337" // DECSC: Save cursor
"\033[r" // DECSTBM: Reset scrollable region
"\0338" // DECRC: Restore cursor
@@ -111,19 +116,9 @@ static int bfs_bar_reset(struct bfs_bar *bar) {
}
/** Signal handler for process-terminating signals. */
-static void sighand_reset(int sig) {
- bfs_bar_reset(&the_bar);
- raise(sig);
-}
-
-/** Register sighand_reset() for a signal. */
-static void reset_before_death_by(int sig) {
- struct sigaction sa = {
- .sa_handler = sighand_reset,
- .sa_flags = SA_RESETHAND,
- };
- sigemptyset(&sa.sa_mask);
- sigaction(sig, &sa, NULL);
+static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) {
+ struct bfs_bar *bar = arg;
+ bfs_bar_reset(bar);
}
/** printf() to the status bar with a single write(). */
@@ -138,15 +133,15 @@ static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) {
return -1;
}
- int ret = ass_puts(bar->fd, str);
+ int ret = bfs_bar_write(bar, str, dstrlen(str));
dstrfree(str);
return ret;
}
struct bfs_bar *bfs_bar_show(void) {
- if (the_bar.fd >= 0) {
- errno = EBUSY;
- goto fail;
+ struct bfs_bar *bar = ALLOC(struct bfs_bar);
+ if (!bar) {
+ return NULL;
}
char term[L_ctermid];
@@ -156,46 +151,36 @@ struct bfs_bar *bfs_bar_show(void) {
goto fail;
}
- the_bar.fd = open(term, O_RDWR | O_CLOEXEC);
- if (the_bar.fd < 0) {
+ bar->fd = open(term, O_RDWR | O_CLOEXEC);
+ if (bar->fd < 0) {
goto fail;
}
- if (bfs_bar_getsize(&the_bar) != 0) {
+ if (bfs_bar_getsize(bar) != 0) {
goto fail_close;
}
- reset_before_death_by(SIGABRT);
- reset_before_death_by(SIGINT);
- reset_before_death_by(SIGPIPE);
- reset_before_death_by(SIGQUIT);
- reset_before_death_by(SIGTERM);
+ bar->exit_hook = atsigexit(bfs_bar_sigexit, bar);
+ if (!bar->exit_hook) {
+ goto fail_close;
+ }
#ifdef SIGWINCH
- struct sigaction sa = {
- .sa_handler = sighand_winch,
- .sa_flags = SA_RESTART,
- };
- sigemptyset(&sa.sa_mask);
- sigaction(SIGWINCH, &sa, NULL);
+ bar->winch_hook = sighook(SIGWINCH, bfs_bar_sigwinch, bar, 0);
+ if (!bar->winch_hook) {
+ goto fail_hook;
+ }
#endif
- unsigned int height = load(&the_bar.height, relaxed);
- bfs_bar_printf(&the_bar,
- "\n" // Make space for the bar
- "\0337" // DECSC: Save cursor
- "\033[;%ur" // DECSTBM: Set scrollable region
- "\0338" // DECRC: Restore cursor
- "\033[1A", // CUU: Move cursor up 1 row
- height - 1
- );
-
- return &the_bar;
+ bfs_bar_resize(bar);
+ return bar;
+fail_hook:
+ sigunhook(bar->exit_hook);
fail_close:
- close_quietly(the_bar.fd);
- the_bar.fd = -1;
+ close_quietly(bar->fd);
fail:
+ free(bar);
return NULL;
}
@@ -223,17 +208,11 @@ void bfs_bar_hide(struct bfs_bar *bar) {
return;
}
- signal(SIGABRT, SIG_DFL);
- signal(SIGINT, SIG_DFL);
- signal(SIGPIPE, SIG_DFL);
- signal(SIGQUIT, SIG_DFL);
- signal(SIGTERM, SIG_DFL);
-#ifdef SIGWINCH
- signal(SIGWINCH, SIG_DFL);
-#endif
+ sigunhook(bar->winch_hook);
+ sigunhook(bar->exit_hook);
bfs_bar_reset(bar);
xclose(bar->fd);
- bar->fd = -1;
+ free(bar);
}
diff --git a/src/bfstd.c b/src/bfstd.c
index f8ce871..7680f17 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -637,8 +637,14 @@ error:
return NULL;
}
+#if BFS_HAS_STRTOFFLAGS
+# define BFS_STRTOFFLAGS strtofflags
+#elif BFS_HAS_STRING_TO_FLAGS
+# define BFS_STRTOFFLAGS string_to_flags
+#endif
+
int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear) {
-#if BSD && !__GNU__
+#ifdef BFS_STRTOFFLAGS
char *str_arg = (char *)*str;
#if __OpenBSD__
@@ -649,11 +655,7 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *
bfs_fflags_t set_arg = 0;
bfs_fflags_t clear_arg = 0;
-#if __NetBSD__
- int ret = string_to_flags(&str_arg, &set_arg, &clear_arg);
-#else
- int ret = strtofflags(&str_arg, &set_arg, &clear_arg);
-#endif
+ int ret = BFS_STRTOFFLAGS(&str_arg, &set_arg, &clear_arg);
*str = str_arg;
*set = set_arg;
@@ -663,12 +665,27 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *
errno = EINVAL;
}
return ret;
-#else // !BSD
+#else // !BFS_STRTOFFLAGS
errno = ENOTSUP;
return -1;
#endif
}
+long xsysconf(int name) {
+#if __FreeBSD__ && SANITIZE_MEMORY
+ // Work around https://github.com/llvm/llvm-project/issues/88163
+ __msan_scoped_disable_interceptor_checks();
+#endif
+
+ long ret = sysconf(name);
+
+#if __FreeBSD__ && SANITIZE_MEMORY
+ __msan_scoped_enable_interceptor_checks();
+#endif
+
+ return ret;
+}
+
size_t asciilen(const char *str) {
return asciinlen(str, strlen(str));
}
diff --git a/src/bfstd.h b/src/bfstd.h
index f91e380..d06bbd9 100644
--- a/src/bfstd.h
+++ b/src/bfstd.h
@@ -410,6 +410,11 @@ char *xconfstr(int name);
*/
int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear);
+/**
+ * Wrapper for sysconf() that works around an MSan bug.
+ */
+long xsysconf(int name);
+
#include <wchar.h>
/**
diff --git a/src/bftw.c b/src/bftw.c
index c4d3c17..5322181 100644
--- a/src/bftw.c
+++ b/src/bftw.c
@@ -1281,7 +1281,7 @@ static int bftw_pin_parent(struct bftw_state *state, struct bftw_file *file) {
int fd = parent->fd;
if (fd < 0) {
- bfs_static_assert(AT_FDCWD != -1);
+ bfs_static_assert((int)AT_FDCWD != -1);
return -1;
}
@@ -1298,7 +1298,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) {
}
int dfd = bftw_pin_parent(state, file);
- if (dfd < 0 && dfd != AT_FDCWD) {
+ if (dfd < 0 && dfd != (int)AT_FDCWD) {
goto fail;
}
@@ -1450,7 +1450,7 @@ static int bftw_ioq_stat(struct bftw_state *state, struct bftw_file *file) {
}
int dfd = bftw_pin_parent(state, file);
- if (dfd < 0 && dfd != AT_FDCWD) {
+ if (dfd < 0 && dfd != (int)AT_FDCWD) {
goto fail;
}
diff --git a/src/color.c b/src/color.c
index f004bf2..a0e553f 100644
--- a/src/color.c
+++ b/src/color.c
@@ -161,8 +161,13 @@ static struct esc_seq **get_esc(const struct colors *colors, const char *name) {
return leaf ? leaf->value : NULL;
}
+/** Append an escape sequence to a string. */
+static int cat_esc(dchar **dstr, const struct esc_seq *seq) {
+ return dstrxcat(dstr, seq->seq, seq->len);
+}
+
/** Set a named escape sequence. */
-static int set_esc(struct colors *colors, const char *name, char *value) {
+static int set_esc(struct colors *colors, const char *name, dchar *value) {
struct esc_seq **field = get_esc(colors, name);
if (!field) {
return 0;
@@ -587,8 +592,8 @@ static int parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) {
// All-zero values should be treated like NULL, to fall
// back on any other relevant coloring for that file
- char *esc = value;
- if (strspn(value, "0") == strlen(value)
+ dchar *esc = value;
+ if (strspn(value, "0") == dstrlen(value)
&& strcmp(key, "rs") != 0
&& strcmp(key, "lc") != 0
&& strcmp(key, "rc") != 0
@@ -693,6 +698,20 @@ struct colors *parse_colors(void) {
colors->link->len = 0;
}
+ // Pre-compute the reset escape sequence
+ if (!colors->endcode) {
+ dchar *ec = dstralloc(0);
+ if (!ec
+ || cat_esc(&ec, colors->leftcode) != 0
+ || cat_esc(&ec, colors->reset) != 0
+ || cat_esc(&ec, colors->rightcode) != 0
+ || set_esc(colors, "ec", ec) != 0) {
+ dstrfree(ec);
+ goto fail;
+ }
+ dstrfree(ec);
+ }
+
return colors;
fail:
@@ -727,10 +746,11 @@ CFILE *cfwrap(FILE *file, const struct colors *colors, bool close) {
}
cfile->file = file;
+ cfile->fd = fileno(file);
cfile->need_reset = false;
cfile->close = close;
- if (isatty(fileno(file))) {
+ if (isatty(cfile->fd)) {
cfile->colors = colors;
} else {
cfile->colors = NULL;
@@ -874,7 +894,7 @@ error:
/** Print an escape sequence chunk. */
static int print_esc_chunk(CFILE *cfile, const struct esc_seq *esc) {
- return dstrxcat(&cfile->buffer, esc->seq, esc->len);
+ return cat_esc(&cfile->buffer, esc);
}
/** Print an ANSI escape sequence. */
@@ -908,12 +928,7 @@ static int print_reset(CFILE *cfile) {
}
cfile->need_reset = false;
- const struct colors *colors = cfile->colors;
- if (colors->endcode) {
- return print_esc_chunk(cfile, colors->endcode);
- } else {
- return print_esc(cfile, colors->reset);
- }
+ return print_esc_chunk(cfile, cfile->colors->endcode);
}
/** Print a shell-escaped string. */
@@ -961,7 +976,7 @@ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf,
} else {
// We're in print_link_target(), so resolve relative to the link's parent directory
at_fd = ftwbuf->at_fd;
- if (at_fd == AT_FDCWD && path[0] != '/') {
+ if (at_fd == (int)AT_FDCWD && path[0] != '/') {
at_path = dstrndup(ftwbuf->path, ftwbuf->nameoff);
if (at_path && dstrncat(&at_path, path, max) != 0) {
ret = -1;
@@ -1390,3 +1405,14 @@ int cfprintf(CFILE *cfile, const char *format, ...) {
va_end(args);
return ret;
}
+
+int cfreset(CFILE *cfile) {
+ const struct colors *colors = cfile->colors;
+ if (!colors) {
+ return 0;
+ }
+
+ const struct esc_seq *esc = colors->endcode;
+ size_t ret = xwrite(cfile->fd, esc->seq, esc->len);
+ return ret == esc->len ? 0 : -1;
+}
diff --git a/src/color.h b/src/color.h
index 3278cd6..3550888 100644
--- a/src/color.h
+++ b/src/color.h
@@ -42,6 +42,8 @@ typedef struct CFILE {
const struct colors *colors;
/** A buffer for colored formatting. */
dchar *buffer;
+ /** Cached file descriptor number. */
+ int fd;
/** Whether the next ${rs} is actually necessary. */
bool need_reset;
/** Whether to close the underlying stream. */
@@ -108,4 +110,9 @@ int cfprintf(CFILE *cfile, const char *format, ...);
attr(printf(2, 0))
int cvfprintf(CFILE *cfile, const char *format, va_list args);
+/**
+ * Reset the TTY state when terminating abnormally (async-signal-safe).
+ */
+int cfreset(CFILE *cfile);
+
#endif // BFS_COLOR_H
diff --git a/src/ctx.c b/src/ctx.c
index aa73b35..11531df 100644
--- a/src/ctx.c
+++ b/src/ctx.c
@@ -3,24 +3,27 @@
#include "ctx.h"
#include "alloc.h"
+#include "bfstd.h"
#include "color.h"
#include "diag.h"
#include "expr.h"
#include "list.h"
#include "mtab.h"
#include "pwcache.h"
+#include "sighook.h"
#include "stat.h"
#include "trie.h"
#include "xtime.h"
#include <errno.h>
#include <limits.h>
+#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
/** Get the initial value for ctx->threads (-j). */
static int bfs_nproc(void) {
- long nproc = sysconf(_SC_NPROCESSORS_ONLN);
+ long nproc = xsysconf(_SC_NPROCESSORS_ONLN);
if (nproc < 1) {
nproc = 1;
@@ -98,13 +101,20 @@ struct bfs_ctx_file {
CFILE *cfile;
/** The path to the file (for diagnostics). */
const char *path;
+ /** Signal hook to send a reset escape sequence. */
+ struct sighook *hook;
/** Remembers I/O errors, to propagate them to the exit status. */
int error;
};
+/** Call cfreset() on a tracked file. */
+static void cfreset_hook(int sig, siginfo_t *info, void *arg) {
+ cfreset(arg);
+}
+
CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) {
struct bfs_stat sb;
- if (bfs_stat(fileno(cfile->file), NULL, 0, &sb) != 0) {
+ if (bfs_stat(cfile->fd, NULL, 0, &sb) != 0) {
return NULL;
}
@@ -124,19 +134,31 @@ CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) {
leaf->value = ctx_file = ALLOC(struct bfs_ctx_file);
if (!ctx_file) {
- trie_remove(&ctx->files, leaf);
- return NULL;
+ goto fail;
}
ctx_file->cfile = cfile;
ctx_file->path = path;
ctx_file->error = 0;
+ ctx_file->hook = NULL;
+
+ if (cfile->colors) {
+ ctx_file->hook = atsigexit(cfreset_hook, cfile);
+ if (!ctx_file->hook) {
+ goto fail;
+ }
+ }
if (cfile != ctx->cout && cfile != ctx->cerr) {
++ctx->nfiles;
}
return cfile;
+
+fail:
+ trie_remove(&ctx->files, leaf);
+ free(ctx_file);
+ return NULL;
}
void bfs_ctx_flush(const struct bfs_ctx *ctx) {
@@ -156,7 +178,7 @@ void bfs_ctx_flush(const struct bfs_ctx *ctx) {
const char *path = ctx_file->path;
if (path) {
- bfs_error(ctx, "'%s': %m.\n", path);
+ bfs_error(ctx, "%pq: %m.\n", path);
} else if (cfile == ctx->cout) {
bfs_error(ctx, "(standard output): %m.\n");
}
@@ -188,30 +210,49 @@ static int bfs_ctx_fflush(CFILE *cfile) {
static int bfs_ctx_fclose(struct bfs_ctx *ctx, struct bfs_ctx_file *ctx_file) {
CFILE *cfile = ctx_file->cfile;
- if (cfile == ctx->cout) {
- // Will be checked later
- return 0;
- } else if (cfile == ctx->cerr) {
- // Writes to stderr are allowed to fail silently, unless the same file was used by
- // -fprint, -fls, etc.
- if (ctx_file->path) {
- return bfs_ctx_fflush(cfile);
- } else {
- return 0;
- }
- }
-
+ // Writes to stderr are allowed to fail silently, unless the same file
+ // was used by -fprint, -fls, etc.
+ bool silent = cfile == ctx->cerr && !ctx_file->path;
int ret = 0, error = 0;
- if (ferror(cfile->file)) {
+
+ if (ctx_file->error) {
+ // An error was previously reported during bfs_ctx_flush()
ret = -1;
- error = EIO;
+ error = ctx_file->error;
}
- if (cfclose(cfile) != 0) {
+
+ // Flush the file just before we remove the hook, to maximize the chance
+ // we leave the TTY in a good state
+ if (bfs_ctx_fflush(cfile) != 0) {
ret = -1;
error = errno;
}
- errno = error;
+ if (ctx_file->hook) {
+ sigunhook(ctx_file->hook);
+ }
+
+ // Close the CFILE, except for stdio streams, which are closed later
+ if (cfile != ctx->cout && cfile != ctx->cerr) {
+ if (cfclose(cfile) != 0) {
+ ret = -1;
+ error = errno;
+ }
+ }
+
+ if (silent) {
+ ret = 0;
+ }
+
+ if (ret != 0 && ctx->cerr) {
+ if (ctx_file->path) {
+ bfs_error(ctx, "%pq: %s.\n", ctx_file->path, xstrerror(error));
+ } else if (cfile == ctx->cout) {
+ bfs_error(ctx, "(standard output): %s.\n", xstrerror(error));
+ }
+ }
+
+ free(ctx_file);
return ret;
}
@@ -229,33 +270,14 @@ int bfs_ctx_free(struct bfs_ctx *ctx) {
for_trie (leaf, &ctx->files) {
struct bfs_ctx_file *ctx_file = leaf->value;
-
- if (ctx_file->error) {
- // An error was previously reported during bfs_ctx_flush()
- ret = -1;
- }
-
if (bfs_ctx_fclose(ctx, ctx_file) != 0) {
- if (cerr) {
- bfs_error(ctx, "%pq: %m.\n", ctx_file->path);
- }
ret = -1;
}
-
- free(ctx_file);
}
trie_destroy(&ctx->files);
- if (cout && bfs_ctx_fflush(cout) != 0) {
- if (cerr) {
- bfs_error(ctx, "(standard output): %m.\n");
- }
- ret = -1;
- }
-
cfclose(cout);
cfclose(cerr);
-
free_colors(ctx->colors);
for_slist (struct bfs_expr, expr, &ctx->expr_list, freelist) {
diff --git a/src/diag.c b/src/diag.c
index deb6f26..bb744f6 100644
--- a/src/diag.c
+++ b/src/diag.c
@@ -38,6 +38,10 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) {
abort();
}
+const char *bfs_errstr(void) {
+ return xstrerror(errno);
+}
+
const char *debug_flag_name(enum debug_flags flag) {
switch (flag) {
case DEBUG_COST:
diff --git a/src/diag.h b/src/diag.h
index 2b13609..f2498b5 100644
--- a/src/diag.h
+++ b/src/diag.h
@@ -55,6 +55,20 @@ void bfs_diagf(const struct bfs_loc *loc, const char *format, ...);
#define bfs_diag(...) bfs_diagf(bfs_location(), __VA_ARGS__)
/**
+ * Get the last error message.
+ */
+const char *bfs_errstr(void);
+
+/**
+ * Print a diagnostic message including the last error.
+ */
+#define bfs_ediag(...) \
+ bfs_ediag_("" __VA_ARGS__, bfs_errstr())
+
+#define bfs_ediag_(format, ...) \
+ bfs_diag(sizeof(format) > 1 ? format ": %s" : "%s", __VA_ARGS__)
+
+/**
* Print a message to standard error and abort.
*/
attr(cold, printf(2, 3))
@@ -63,15 +77,27 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...);
/**
* Unconditional abort with a message.
*/
-#define bfs_abort(...) bfs_abortf(bfs_location(), __VA_ARGS__)
+#define bfs_abort(...) \
+ bfs_abortf(bfs_location(), __VA_ARGS__)
+
+/**
+ * Abort with a message including the last error.
+ */
+#define bfs_eabort(...) \
+ bfs_eabort_("" __VA_ARGS__, bfs_errstr())
+
+#define bfs_eabort_(format, ...) \
+ bfs_abort(sizeof(format) > 1 ? format ": %s" : "%s", __VA_ARGS__)
/**
* Abort in debug builds; no-op in release builds.
*/
#ifdef NDEBUG
# define bfs_bug(...) ((void)0)
+# define bfs_ebug(...) ((void)0)
#else
# define bfs_bug bfs_abort
+# define bfs_ebug bfs_eabort
#endif
/**
@@ -88,12 +114,27 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...);
str, __VA_ARGS__))
/**
+ * Unconditional assert, including the last error.
+ */
+#define bfs_everify(...) \
+ bfs_everify_(#__VA_ARGS__, __VA_ARGS__, "", bfs_errstr())
+
+#define bfs_everify_(str, cond, format, ...) \
+ ((cond) ? (void)0 : bfs_abort( \
+ sizeof(format) > 1 \
+ ? "%.0s" format "%s: %s" \
+ : "Assertion failed: `%s`: %s", \
+ str, __VA_ARGS__))
+
+/**
* Assert in debug builds; no-op in release builds.
*/
#ifdef NDEBUG
# define bfs_assert(...) ((void)0)
+# define bfs_eassert(...) ((void)0)
#else
# define bfs_assert bfs_verify
+# define bfs_eassert bfs_everify
#endif
struct bfs_ctx;
diff --git a/src/dir.c b/src/dir.c
index cfbbca4..fadf1c0 100644
--- a/src/dir.c
+++ b/src/dir.c
@@ -25,7 +25,13 @@
static ssize_t bfs_getdents(int fd, void *buf, size_t size) {
sanitize_uninit(buf, size);
-#if BFS_HAS_GETDENTS
+#if BFS_HAS_POSIX_GETDENTS
+ int flags = 0;
+# ifdef DT_FORCE_TYPE
+ flags |= DT_FORCE_TYPE;
+# endif
+ ssize_t ret = posix_getdents(fd, buf, size, flags);
+#elif BFS_HAS_GETDENTS
ssize_t ret = getdents(fd, buf, size);
#elif BFS_HAS_GETDENTS64
ssize_t ret = getdents64(fd, buf, size);
@@ -44,11 +50,13 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) {
#endif // BFS_USE_GETDENTS
-#if BFS_USE_GETDENTS && !BFS_HAS_GETDENTS
/** Directory entry type for bfs_getdents() */
-typedef struct dirent64 sys_dirent;
-#else
+#if !BFS_USE_GETDENTS || BFS_HAS_GETDENTS
typedef struct dirent sys_dirent;
+#elif BFS_HAS_POSIX_GETDENTS
+typedef struct posix_dent sys_dirent;
+#else
+typedef struct dirent64 sys_dirent;
#endif
enum bfs_type bfs_mode_to_type(mode_t mode) {
diff --git a/src/dir.h b/src/dir.h
index 6d5c9c5..bbba071 100644
--- a/src/dir.h
+++ b/src/dir.h
@@ -15,8 +15,12 @@
* Whether the implementation uses the getdents() syscall directly, rather than
* libc's readdir().
*/
-#if !defined(BFS_USE_GETDENTS) && (__linux__ || __FreeBSD__)
-# define BFS_USE_GETDENTS (BFS_HAS_GETDENTS || BFS_HAS_GETDENTS64 | BFS_HAS_GETDENTS64_SYSCALL)
+#ifndef BFS_USE_GETDENTS
+# if BFS_HAS_POSIX_GETDENTS
+# define BFS_USE_GETDENTS true
+# elif __linux__ || __FreeBSD__
+# define BFS_USE_GETDENTS (BFS_HAS_GETDENTS || BFS_HAS_GETDENTS64 | BFS_HAS_GETDENTS64_SYSCALL)
+# endif
#endif
/**
diff --git a/src/dstring.c b/src/dstring.c
index b5bf3d3..86ab646 100644
--- a/src/dstring.c
+++ b/src/dstring.c
@@ -174,7 +174,7 @@ int dstrxcpy(dchar **dest, const char *src, size_t len) {
return 0;
}
-char *dstrprintf(const char *format, ...) {
+dchar *dstrprintf(const char *format, ...) {
va_list args;
va_start(args, format);
@@ -184,7 +184,7 @@ char *dstrprintf(const char *format, ...) {
return str;
}
-char *dstrvprintf(const char *format, va_list args) {
+dchar *dstrvprintf(const char *format, va_list args) {
// Guess a capacity to try to avoid reallocating
dchar *str = dstralloc(2 * strlen(format));
if (!str) {
diff --git a/src/dstring.h b/src/dstring.h
index 9ea7eb9..14e1d3e 100644
--- a/src/dstring.h
+++ b/src/dstring.h
@@ -244,7 +244,7 @@ int dstrxcpy(dchar **dest, const char *str, size_t len);
* The created string, or NULL on failure.
*/
attr(printf(1, 2))
-char *dstrprintf(const char *format, ...);
+dchar *dstrprintf(const char *format, ...);
/**
* Create a dynamic string from a format string and a va_list.
@@ -257,7 +257,7 @@ char *dstrprintf(const char *format, ...);
* The created string, or NULL on failure.
*/
attr(printf(1, 0))
-char *dstrvprintf(const char *format, va_list args);
+dchar *dstrvprintf(const char *format, va_list args);
/**
* Format some text onto the end of a dynamic string.
diff --git a/src/eval.c b/src/eval.c
index 49028b7..4fcda60 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1270,7 +1270,7 @@ static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, enu
bfs_debug_prefix(ctx, DEBUG_STAT);
fprintf(stderr, "bfs_stat(");
- if (ftwbuf->at_fd == AT_FDCWD) {
+ if (ftwbuf->at_fd == (int)AT_FDCWD) {
fprintf(stderr, "AT_FDCWD");
} else {
size_t baselen = strlen(ftwbuf->path) - strlen(ftwbuf->at_path);
diff --git a/src/exec.c b/src/exec.c
index e782d49..cd73d6c 100644
--- a/src/exec.c
+++ b/src/exec.c
@@ -56,7 +56,7 @@ static size_t bfs_exec_arg_size(const char *arg) {
/** Determine the maximum argv size. */
static size_t bfs_exec_arg_max(const struct bfs_exec *execbuf) {
- long arg_max = sysconf(_SC_ARG_MAX);
+ long arg_max = xsysconf(_SC_ARG_MAX);
bfs_exec_debug(execbuf, "ARG_MAX: %ld according to sysconf()\n", arg_max);
if (arg_max < 0) {
arg_max = BFS_EXEC_ARG_MAX;
@@ -82,7 +82,7 @@ static size_t bfs_exec_arg_max(const struct bfs_exec *execbuf) {
// Assume arguments are counted with the granularity of a single page,
// so allow a one page cushion to account for rounding up
- long page_size = sysconf(_SC_PAGESIZE);
+ long page_size = xsysconf(_SC_PAGESIZE);
if (page_size < 4096) {
page_size = 4096;
}
@@ -268,7 +268,7 @@ static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf)
bfs_assert(execbuf->wd_fd < 0);
bfs_assert(!execbuf->wd_path);
- if (ftwbuf->at_fd != AT_FDCWD) {
+ if (ftwbuf->at_fd != (int)AT_FDCWD) {
// Rely on at_fd being the immediate parent
bfs_assert(xbaseoff(ftwbuf->at_path) == 0);
diff --git a/src/fsade.c b/src/fsade.c
index d56fb07..7310141 100644
--- a/src/fsade.c
+++ b/src/fsade.c
@@ -41,7 +41,7 @@ static const char *fake_at(const struct BFTW *ftwbuf) {
static atomic int proc_works = -1;
dchar *path = NULL;
- if (ftwbuf->at_fd == AT_FDCWD || load(&proc_works, relaxed) == 0) {
+ if (ftwbuf->at_fd == (int)AT_FDCWD || load(&proc_works, relaxed) == 0) {
goto fail;
}
diff --git a/src/main.c b/src/main.c
index 9d8b206..5dd88e4 100644
--- a/src/main.c
+++ b/src/main.c
@@ -36,6 +36,7 @@
* - mtab.[ch] (parses the system's mount table)
* - pwcache.[ch] (a cache for the user/group tables)
* - sanity.h (sanitizer interfaces)
+ * - sighook.[ch] (signal hooks)
* - stat.[ch] (wraps stat(), or statx() on Linux)
* - thread.h (multi-threading)
* - trie.[ch] (a trie set/map implementation)
diff --git a/src/mtab.c b/src/mtab.c
index 7905d14..0377fea 100644
--- a/src/mtab.c
+++ b/src/mtab.c
@@ -13,11 +13,11 @@
#include <string.h>
#include <sys/types.h>
-#if !defined(BFS_USE_MNTENT) && BFS_USE_MNTENT_H
+#if !defined(BFS_USE_MNTENT) && BFS_HAS_GETMNTENT_1
# define BFS_USE_MNTENT true
-#elif !defined(BFS_USE_MNTINFO) && BSD
+#elif !defined(BFS_USE_MNTINFO) && BFS_HAS_GETMNTINFO
# define BFS_USE_MNTINFO true
-#elif !defined(BFS_USE_MNTTAB) && __SVR4
+#elif !defined(BFS_USE_MNTTAB) && BFS_HAS_GETMNTENT_2
# define BFS_USE_MNTTAB true
#endif
@@ -27,7 +27,6 @@
# include <stdio.h>
#elif BFS_USE_MNTINFO
# include <sys/mount.h>
-# include <sys/ucred.h>
#elif BFS_USE_MNTTAB
# include <stdio.h>
# include <sys/mnttab.h>
@@ -148,7 +147,7 @@ struct bfs_mtab *bfs_mtab_parse(void) {
bfs_statfs *mntbuf;
int size = getmntinfo(&mntbuf, MNT_WAIT);
- if (size < 0) {
+ if (size <= 0) {
error = errno;
goto fail;
}
diff --git a/src/prelude.h b/src/prelude.h
index ddeacbd..72f88b0 100644
--- a/src/prelude.h
+++ b/src/prelude.h
@@ -127,21 +127,6 @@ extern const char bfs_version[];
# define __has_attribute(attr) false
#endif
-// Platform detection
-
-// Get the definition of BSD if available
-#if BFS_USE_SYS_PARAM_H
-# include <sys/param.h>
-#endif
-
-#ifndef __GLIBC_PREREQ
-# define __GLIBC_PREREQ(maj, min) false
-#endif
-
-#ifndef __NetBSD_Prereq__
-# define __NetBSD_Prereq__(maj, min, patch) false
-#endif
-
// Fundamental utilities
/**
diff --git a/src/printf.c b/src/printf.c
index f8428f7..be09ebd 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -709,9 +709,9 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha
case '#':
case '0':
case '+':
+ case ' ':
must_be_numeric = true;
fallthru;
- case ' ':
case '-':
if (strchr(fmt.str, c)) {
bfs_expr_error(ctx, expr);
diff --git a/src/sighook.c b/src/sighook.c
new file mode 100644
index 0000000..ff5b96f
--- /dev/null
+++ b/src/sighook.c
@@ -0,0 +1,600 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Dynamic (un)registration of signal handlers.
+ *
+ * Because signal handlers can interrupt any thread at an arbitrary point, they
+ * must be lock-free or risk deadlock. Therefore, we implement the global table
+ * of signal "hooks" with a simple read-copy-update (RCU) scheme. Readers get a
+ * reference-counted pointer (struct arc) to the table in a lock-free way, and
+ * release the reference count when finished.
+ *
+ * Updates are managed by struct rcu, which has two slots: one active and one
+ * inactive. Readers acquire a reference to the active slot. A single writer
+ * can safely update it by initializing the inactive slot, atomically swapping
+ * the slots, and waiting for the reference count of the newly inactive slot to
+ * 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 "bfstd.h"
+#include "diag.h"
+#include "thread.h"
+#include <errno.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <unistd.h>
+
+#if _POSIX_SEMAPHORES > 0
+# include <semaphore.h>
+#endif
+
+/**
+ * An atomically reference-counted pointer.
+ */
+struct arc {
+ /** The current reference count (0 means empty). */
+ atomic size_t refs;
+ /** The reference itself. */
+ void *ptr;
+
+#if _POSIX_SEMAPHORES > 0
+ /** A semaphore for arc_wake(). */
+ sem_t sem;
+ /** sem_init() result. */
+ int sem_status;
+#endif
+};
+
+/** Initialize an arc. */
+static void arc_init(struct arc *arc) {
+ atomic_init(&arc->refs, 0);
+ arc->ptr = NULL;
+
+#if _POSIX_SEMAPHORES > 0
+ arc->sem_status = sem_init(&arc->sem, false, 0);
+#endif
+}
+
+/** Get the current refcount. */
+static size_t arc_refs(const struct arc *arc) {
+ return load(&arc->refs, relaxed);
+}
+
+/** Set the pointer in an empty arc. */
+static void arc_set(struct arc *arc, void *ptr) {
+ bfs_assert(arc_refs(arc) == 0);
+ bfs_assert(ptr);
+
+ arc->ptr = ptr;
+ store(&arc->refs, 1, release);
+}
+
+/** Acquire a reference. */
+static void *arc_get(struct arc *arc) {
+ size_t refs = arc_refs(arc);
+ do {
+ if (refs < 1) {
+ return NULL;
+ }
+ } while (!compare_exchange_weak(&arc->refs, &refs, refs + 1, acquire, relaxed));
+
+ return arc->ptr;
+}
+
+/** Release a reference. */
+static void arc_put(struct arc *arc) {
+ size_t refs = fetch_sub(&arc->refs, 1, release);
+
+ if (refs == 1) {
+#if _POSIX_SEMAPHORES > 0
+ if (arc->sem_status == 0 && sem_post(&arc->sem) != 0) {
+ abort();
+ }
+#endif
+ }
+}
+
+/** Wait on the semaphore. */
+static int arc_sem_wait(struct arc *arc) {
+#if _POSIX_SEMAPHORES > 0
+ if (arc->sem_status == 0) {
+ while (sem_wait(&arc->sem) != 0) {
+ bfs_everify(errno == EINTR, "sem_wait()");
+ }
+ return 0;
+ }
+#endif
+
+ return -1;
+}
+
+/** Wait for all references to be released. */
+static void *arc_wait(struct arc *arc) {
+ size_t refs = fetch_sub(&arc->refs, 1, relaxed);
+ bfs_assert(refs > 0);
+
+ --refs;
+ while (refs > 0) {
+ if (arc_sem_wait(arc) == 0) {
+ bfs_assert(arc_refs(arc) == 0);
+ // sem_wait() provides enough ordering, so we can skip the fence
+ goto done;
+ }
+
+ // Some platforms (like macOS) don't support unnamed semaphores,
+ // but we can always busy-wait
+ spin_loop();
+ refs = arc_refs(arc);
+ }
+
+ thread_fence(&arc->refs, acquire);
+
+done:;
+ void *ptr = arc->ptr;
+ arc->ptr = NULL;
+ return ptr;
+}
+
+/** Destroy an arc. */
+static void arc_destroy(struct arc *arc) {
+ bfs_assert(arc_refs(arc) <= 1);
+
+#if _POSIX_SEMAPHORES > 0
+ if (arc->sem_status == 0) {
+ bfs_everify(sem_destroy(&arc->sem) == 0, "sem_destroy()");
+ }
+#endif
+}
+
+/**
+ * A simple read-copy-update memory reclamation scheme.
+ */
+struct rcu {
+ /** The currently active slot. */
+ atomic size_t active;
+ /** The two slots. */
+ struct arc slots[2];
+};
+
+/** Sentinel value for RCU, since arc uses NULL already. */
+static void *RCU_NULL = &RCU_NULL;
+
+/** Initialize an RCU block. */
+static void rcu_init(struct rcu *rcu) {
+ atomic_init(&rcu->active, 0);
+ arc_init(&rcu->slots[0]);
+ arc_init(&rcu->slots[1]);
+ arc_set(&rcu->slots[0], RCU_NULL);
+}
+
+/** Get the active slot. */
+static struct arc *rcu_active(struct rcu *rcu) {
+ size_t i = load(&rcu->active, relaxed);
+ return &rcu->slots[i];
+}
+
+/** 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;
+ }
+ // Otherwise, the other slot became active; retry
+ }
+}
+
+/** 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;
+ }
+}
+
+/** Update an RCU-protected pointer, and return the old one. */
+static void *rcu_update(struct rcu *rcu, void *ptr) {
+ size_t i = load(&rcu->active, relaxed);
+ struct arc *prev = &rcu->slots[i];
+
+ size_t j = i ^ 1;
+ struct arc *next = &rcu->slots[j];
+
+ arc_set(next, ptr ? ptr : RCU_NULL);
+ store(&rcu->active, j, relaxed);
+ return arc_wait(prev);
+}
+
+struct sighook {
+ int sig;
+ sighook_fn *fn;
+ void *arg;
+ enum sigflags flags;
+};
+
+/**
+ * A table 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[];
+};
+
+/** 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;
+}
+
+/** 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;
+}
+
+/** 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);
+ }
+ 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;
+ }
+
+ bfs_verify(sigtable_add(next, hook) == 0);
+ rcu_update(rcu, next);
+ sigtable_free(prev);
+ return 0;
+}
+
+/** 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
+ //
+ // 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 ...
+ int code = info->si_code;
+ return code == SI_USER || code == SI_QUEUE || code <= 0;
+}
+
+/** Check if a signal is caused by a fault. */
+static bool is_fault(const siginfo_t *info) {
+ int sig = info->si_signo;
+ if (sig == SIGBUS || sig == SIGFPE || sig == SIGILL || sig == SIGSEGV) {
+ return !is_user_generated(info);
+ } else {
+ return false;
+ }
+}
+
+// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html
+static const int FATAL_SIGNALS[] = {
+ SIGABRT,
+ SIGALRM,
+ SIGBUS,
+ SIGFPE,
+ SIGHUP,
+ SIGILL,
+ SIGINT,
+ SIGPIPE,
+ SIGQUIT,
+ SIGSEGV,
+ SIGTERM,
+ SIGUSR1,
+ SIGUSR2,
+#ifdef SIGPOLL
+ SIGPOLL,
+#endif
+#ifdef SIGPROF
+ SIGPROF,
+#endif
+#ifdef SIGSYS
+ SIGSYS,
+#endif
+ SIGTRAP,
+#ifdef SIGVTALRM
+ SIGVTALRM,
+#endif
+ SIGXCPU,
+ SIGXFSZ,
+};
+
+/** Check if a signal's default action is to terminate the process. */
+static bool is_fatal(int sig) {
+ for (size_t i = 0; i < countof(FATAL_SIGNALS); ++i) {
+ if (sig == FATAL_SIGNALS[i]) {
+ return true;
+ }
+ }
+
+#ifdef SIGRTMIN
+ // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
+ //
+ // The default actions for the realtime signals in the range
+ // SIGRTMIN to SIGRTMAX shall be to terminate the process
+ // abnormally.
+ if (sig >= SIGRTMIN && sig <= SIGRTMAX) {
+ return true;
+ }
+#endif
+
+ return false;
+}
+
+/** Reraise a fatal signal. */
+static noreturn void reraise(int sig) {
+ // Restore the default signal action
+ if (signal(sig, SIG_DFL) == SIG_ERR) {
+ goto fail;
+ }
+
+ // Unblock the signal, since we didn't set SA_NODEFER
+ sigset_t mask;
+ if (sigemptyset(&mask) != 0
+ || sigaddset(&mask, sig) != 0
+ || pthread_sigmask(SIG_UNBLOCK, &mask, NULL) != 0) {
+ goto fail;
+ }
+
+ raise(sig);
+fail:
+ abort();
+}
+
+/** Find any matching hooks and run them. */
+static enum sigflags run_hooks(struct rcu *rcu, 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;
+ }
+
+ if (hook->sig == sig || hook->sig == 0) {
+ hook->fn(sig, info, hook->arg);
+ ret |= hook->flags;
+ }
+ arc_put(arc);
+ }
+
+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
+ //
+ // The behavior of a process is undefined after it returns normally
+ // from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or
+ // SIGSEGV signal that was not generated by kill(), sigqueue(), or
+ // raise().
+ if (is_fault(info)) {
+ reraise(sig);
+ }
+
+ // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03
+ //
+ // After returning from a signal-catching function, the value of
+ // errno is unspecified if the signal-catching function or any
+ // function it called assigned a value to errno and the signal-
+ // catching function did not save and restore the original value of
+ // errno.
+ int error = errno;
+
+ // Run the normal hooks
+ enum sigflags flags = run_hooks(&rcu_sighooks, sig, info);
+
+ // Run the atsigexit() hooks, if we're exiting
+ if (!(flags & SH_CONTINUE) && is_fatal(sig)) {
+ run_hooks(&rcu_exithooks, sig, info);
+ reraise(sig);
+ }
+
+ errno = error;
+}
+
+/** Make sure our signal handler is installed for a given signal. */
+static int siginit(int sig) {
+ static struct sigaction action = {
+ .sa_sigaction = sigdispatch,
+ .sa_flags = SA_RESTART | SA_SIGINFO,
+ };
+
+ static sigset_t signals;
+ static bool initialized = false;
+
+ if (!initialized) {
+ if (sigemptyset(&signals) != 0
+ || sigemptyset(&action.sa_mask) != 0) {
+ return -1;
+ }
+ rcu_init(&rcu_sighooks);
+ rcu_init(&rcu_exithooks);
+ initialized = true;
+ }
+
+ int installed = sigismember(&signals, sig);
+ if (installed < 0) {
+ return -1;
+ } else if (installed) {
+ return 0;
+ }
+
+ if (sigaction(sig, &action, NULL) != 0) {
+ return -1;
+ }
+
+ if (sigaddset(&signals, sig) != 0) {
+ return -1;
+ }
+
+ return 0;
+}
+
+/** Shared sighook()/atsigexit() implementation. */
+static struct sighook *sighook_impl(struct rcu *rcu, int sig, sighook_fn *fn, void *arg, enum sigflags flags) {
+ struct sighook *hook = ALLOC(struct sighook);
+ if (!hook) {
+ return NULL;
+ }
+
+ hook->sig = sig;
+ hook->fn = fn;
+ hook->arg = arg;
+ hook->flags = flags;
+
+ if (rcu_sigtable_add(rcu, hook) != 0) {
+ free(hook);
+ return NULL;
+ }
+
+ return hook;
+}
+
+struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags) {
+ mutex_lock(&sigmutex);
+
+ struct sighook *ret = NULL;
+ if (siginit(sig) != 0) {
+ goto done;
+ }
+
+ ret = sighook_impl(&rcu_sighooks, sig, fn, arg, flags);
+done:
+ mutex_unlock(&sigmutex);
+ return ret;
+}
+
+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;
+ }
+ }
+
+#ifdef SIGRTMIN
+ for (int i = SIGRTMIN; i <= SIGRTMAX; ++i) {
+ if (siginit(i) != 0) {
+ goto done;
+ }
+ }
+#endif
+
+ ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0);
+done:
+ mutex_unlock(&sigmutex);
+ return ret;
+}
+
+void sigunhook(struct sighook *hook) {
+ mutex_lock(&sigmutex);
+
+ struct rcu *rcu = hook->sig ? &rcu_sighooks : &rcu_exithooks;
+ struct sigtable *table = rcu_peek(rcu);
+ bfs_verify(sigtable_del(table, hook) == 0);
+
+ if (table->filled == 0) {
+ rcu_update(rcu, NULL);
+ sigtable_free(table);
+ }
+
+ mutex_unlock(&sigmutex);
+ free(hook);
+}
diff --git a/src/sighook.h b/src/sighook.h
new file mode 100644
index 0000000..74d18c0
--- /dev/null
+++ b/src/sighook.h
@@ -0,0 +1,73 @@
+// Copyright © Tavian Barnes <tavianator@tavianator.com>
+// SPDX-License-Identifier: 0BSD
+
+/**
+ * Signal hooks.
+ */
+
+#ifndef BFS_SIGHOOK_H
+#define BFS_SIGHOOK_H
+
+#include <signal.h>
+
+/**
+ * A dynamic signal hook.
+ */
+struct sighook;
+
+/**
+ * Signal hook flags.
+ */
+enum sigflags {
+ /** Suppress the default action for this signal. */
+ SH_CONTINUE = 1 << 0,
+};
+
+/**
+ * A signal hook callback. Hooks are executed from a signal handler, so must
+ * only call async-signal-safe functions.
+ *
+ * @param sig
+ * The signal number.
+ * @param info
+ * Additional information about the signal.
+ * @param arg
+ * An arbitrary pointer passed to the hook.
+ */
+typedef void sighook_fn(int sig, siginfo_t *info, void *arg);
+
+/**
+ * Install a hook for a signal.
+ *
+ * @param sig
+ * The signal to hook.
+ * @param fn
+ * The function to call.
+ * @param arg
+ * An argument passed to the function.
+ * @param flags
+ * Flags for the new hook.
+ * @return
+ * The installed hook, or NULL on failure.
+ */
+struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags);
+
+/**
+ * On a best-effort basis, invoke the given hook just before the program is
+ * abnormally terminated by a signal.
+ *
+ * @param fn
+ * The function to call.
+ * @param arg
+ * An argument passed to the function.
+ * @return
+ * The installed hook, or NULL on failure.
+ */
+struct sighook *atsigexit(sighook_fn *fn, void *arg);
+
+/**
+ * Remove a signal hook.
+ */
+void sigunhook(struct sighook *hook);
+
+#endif // BFS_SIGHOOK_H
diff --git a/src/stat.c b/src/stat.c
index f5cf3fe..2044564 100644
--- a/src/stat.c
+++ b/src/stat.c
@@ -62,7 +62,7 @@ int bfs_fstatat_flags(enum bfs_stat_flags flags) {
ret |= AT_SYMLINK_NOFOLLOW;
}
-#if defined(AT_NO_AUTOMOUNT) && (!__GNU__ || __GLIBC_PREREQ(2, 35))
+#ifdef AT_NO_AUTOMOUNT
ret |= AT_NO_AUTOMOUNT;
#endif
@@ -116,6 +116,9 @@ void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) {
#if BFS_HAS_ST_BIRTHTIM
dest->btime = src->st_birthtim;
dest->mask |= BFS_STAT_BTIME;
+#elif BFS_HAS___ST_BIRTHTIM
+ dest->btime = src->__st_birthtim;
+ dest->mask |= BFS_STAT_BTIME;
#elif BFS_HAS_ST_BIRTHTIMESPEC
dest->btime = src->st_birthtimespec;
dest->mask |= BFS_STAT_BTIME;
@@ -297,27 +300,21 @@ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct b
return bfs_stat_tryfollow(at_fd, at_path, at_flags, flags, buf);
}
- // Check __GNU__ to work around https://lists.gnu.org/archive/html/bug-hurd/2021-12/msg00001.html
-#if defined(AT_EMPTY_PATH) && !__GNU__
- static atomic bool has_at_ep = true;
- if (load(&has_at_ep, relaxed)) {
- at_flags |= AT_EMPTY_PATH;
- int ret = bfs_stat_explicit(at_fd, "", at_flags, buf);
- if (ret != 0 && errno == EINVAL) {
- store(&has_at_ep, false, relaxed);
- } else {
- return ret;
- }
- }
-#endif
-
- struct stat statbuf;
- if (fstat(at_fd, &statbuf) == 0) {
- bfs_stat_convert(buf, &statbuf);
- return 0;
- } else {
+#if BFS_USE_STATX
+ // If we have statx(), use it with AT_EMPTY_PATH for its extra features
+ at_flags |= AT_EMPTY_PATH;
+ return bfs_stat_explicit(at_fd, "", at_flags, buf);
+#else
+ // Otherwise, just use fstat() rather than fstatat(at_fd, ""), to save
+ // the kernel the trouble of copying in the empty string
+ struct stat sb;
+ if (fstat(at_fd, &sb) != 0) {
return -1;
}
+
+ bfs_stat_convert(buf, &sb);
+ return 0;
+#endif
}
const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field) {
diff --git a/src/xspawn.c b/src/xspawn.c
index 0b0cea4..33e5a4a 100644
--- a/src/xspawn.c
+++ b/src/xspawn.c
@@ -5,9 +5,11 @@
#include "xspawn.h"
#include "alloc.h"
#include "bfstd.h"
+#include "diag.h"
#include "list.h"
#include <errno.h>
#include <fcntl.h>
+#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
@@ -590,30 +592,49 @@ static pid_t bfs_fork_spawn(struct bfs_resolver *res, const struct bfs_spawn *ct
return -1;
}
+ // Block signals before fork() so handlers don't run in the child
+ sigset_t new_mask;
+ if (sigfillset(&new_mask) != 0) {
+ goto fail;
+ }
+ sigset_t old_mask;
+ errno = pthread_sigmask(SIG_BLOCK, &new_mask, &old_mask);
+ if (errno != 0) {
+ goto fail;
+ }
+
pid_t pid = fork();
- if (pid < 0) {
- close_quietly(pipefd[1]);
- close_quietly(pipefd[0]);
- return -1;
- } else if (pid == 0) {
+ if (pid == 0) {
// Child
bfs_spawn_exec(res, ctx, argv, envp, pipefd);
}
- // Parent
+ // Restore the original signal mask
+ int ret = pthread_sigmask(SIG_SETMASK, &old_mask, NULL);
+ bfs_everify(ret == 0, "pthread_sigmask()");
+
+ if (pid < 0) {
+ // fork() failed
+ goto fail;
+ }
+
xclose(pipefd[1]);
int error;
ssize_t nbytes = xread(pipefd[0], &error, sizeof(error));
xclose(pipefd[0]);
if (nbytes == sizeof(error)) {
- int wstatus;
- xwaitpid(pid, &wstatus, 0);
+ xwaitpid(pid, NULL, 0);
errno = error;
return -1;
}
return pid;
+
+fail:
+ close_quietly(pipefd[1]);
+ close_quietly(pipefd[0]);
+ return -1;
}
/** Call the right bfs_spawn() implementation. */
diff --git a/src/xtime.c b/src/xtime.c
index 91ed915..2808455 100644
--- a/src/xtime.c
+++ b/src/xtime.c
@@ -5,6 +5,7 @@
#include "xtime.h"
#include "bfstd.h"
#include "diag.h"
+#include "sanity.h"
#include <errno.h>
#include <limits.h>
#include <sys/time.h>
@@ -12,14 +13,14 @@
#include <unistd.h>
int xmktime(struct tm *tm, time_t *timep) {
- *timep = mktime(tm);
+ time_t time = mktime(tm);
- if (*timep == -1) {
+ if (time == -1) {
int error = errno;
struct tm tmp;
- if (!localtime_r(timep, &tmp)) {
- bfs_bug("localtime_r(-1): %s", xstrerror(errno));
+ if (!localtime_r(&time, &tmp)) {
+ bfs_ebug("localtime_r(-1)");
return -1;
}
@@ -30,9 +31,38 @@ int xmktime(struct tm *tm, time_t *timep) {
}
}
+ *timep = time;
+ return 0;
+}
+
+// FreeBSD is missing an interceptor
+#if BFS_HAS_TIMEGM && !(__FreeBSD__ && SANITIZE_MEMORY)
+
+int xtimegm(struct tm *tm, time_t *timep) {
+ time_t time = timegm(tm);
+
+ if (time == -1) {
+ int error = errno;
+
+ struct tm tmp;
+ if (!gmtime_r(&time, &tmp)) {
+ bfs_ebug("gmtime_r(-1)");
+ return -1;
+ }
+
+ if (tm->tm_year != tmp.tm_year || tm->tm_yday != tmp.tm_yday
+ || tm->tm_hour != tmp.tm_hour || tm->tm_min != tmp.tm_min || tm->tm_sec != tmp.tm_sec) {
+ errno = error;
+ return -1;
+ }
+ }
+
+ *timep = time;
return 0;
}
+#else
+
static int safe_add(int *value, int delta) {
if (*value >= 0) {
if (delta > INT_MAX - *value) {
@@ -147,6 +177,8 @@ overflow:
return -1;
}
+#endif // !BFS_HAS_TIMEGM
+
/** Parse a decimal digit. */
static int xgetdigit(char c) {
int ret = c - '0';