summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/atomic.h33
-rw-r--r--src/bar.c142
-rw-r--r--src/bfstd.c47
-rw-r--r--src/bfstd.h20
-rw-r--r--src/bftw.c10
-rw-r--r--src/bftw.h2
-rw-r--r--src/bit.h100
-rw-r--r--src/color.c59
-rw-r--r--src/color.h8
-rw-r--r--src/ctx.c105
-rw-r--r--src/ctx.h2
-rw-r--r--src/diag.c14
-rw-r--r--src/diag.h39
-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.c85
-rw-r--r--src/exec.c6
-rw-r--r--src/expr.h6
-rw-r--r--src/fsade.c2
-rw-r--r--src/fsade.h4
-rw-r--r--src/ioq.c26
-rw-r--r--src/list.h24
-rw-r--r--src/main.c1
-rw-r--r--src/mtab.c9
-rw-r--r--src/opt.c56
-rw-r--r--src/parse.c52
-rw-r--r--src/prelude.h39
-rw-r--r--src/printf.c4
-rw-r--r--src/sighook.c604
-rw-r--r--src/sighook.h73
-rw-r--r--src/stat.c37
-rw-r--r--src/trie.c16
-rw-r--r--src/xregex.c63
-rw-r--r--src/xregex.h4
-rw-r--r--src/xspawn.c37
-rw-r--r--src/xtime.c40
38 files changed, 1408 insertions, 393 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..be77694 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,64 +133,47 @@ 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];
- ctermid(term);
- if (strlen(term) == 0) {
- errno = ENOTTY;
+ bar->fd = open_cterm(O_RDWR | O_CLOEXEC);
+ if (bar->fd < 0) {
goto fail;
}
- the_bar.fd = open(term, O_RDWR | O_CLOEXEC);
- if (the_bar.fd < 0) {
- goto fail;
+ if (bfs_bar_getsize(bar) != 0) {
+ goto fail_close;
}
- if (bfs_bar_getsize(&the_bar) != 0) {
+ bar->exit_hook = atsigexit(bfs_bar_sigexit, bar);
+ if (!bar->exit_hook) {
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);
-
#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 +201,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..1c5e289 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -44,7 +44,7 @@ bool error_is_like(int error, int category) {
switch (category) {
case ENOENT:
- return error == ENOTDIR;
+ return error == ENOTDIR || error == ELOOP;
case ENOSYS:
// https://github.com/opencontainers/runc/issues/2151
@@ -184,6 +184,16 @@ char *xgetdelim(FILE *file, char delim) {
}
}
+int open_cterm(int flags) {
+ char path[L_ctermid];
+ if (ctermid(path) == NULL || strlen(path) == 0) {
+ errno = ENOTTY;
+ return -1;
+ }
+
+ return open(path, flags);
+}
+
const char *xgetprogname(void) {
const char *cmd = NULL;
#if BFS_HAS_GETPROGNAME
@@ -322,6 +332,10 @@ const char *xstrerror(int errnum) {
return ret;
}
+const char *errstr(void) {
+ return xstrerror(errno);
+}
+
/** Get the single character describing the given file type. */
static char type_char(mode_t mode) {
switch (mode & S_IFMT) {
@@ -637,8 +651,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 +669,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 +679,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..f5d8622 100644
--- a/src/bfstd.h
+++ b/src/bfstd.h
@@ -158,6 +158,16 @@ FILE *xfopen(const char *path, int flags);
*/
char *xgetdelim(FILE *file, char delim);
+/**
+ * Open the controlling terminal.
+ *
+ * @param flags
+ * The open() flags.
+ * @return
+ * An open file descriptor, or -1 on failure.
+ */
+int open_cterm(int flags);
+
// #include <stdlib.h>
/**
@@ -248,6 +258,11 @@ char *xstpencpy(char *dest, char *end, const char *src, size_t n);
const char *xstrerror(int errnum);
/**
+ * Shorthand for xstrerror(errno).
+ */
+const char *errstr(void);
+
+/**
* Format a mode like ls -l (e.g. -rw-r--r--).
*
* @param mode
@@ -410,6 +425,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..b3fd62a 100644
--- a/src/bftw.c
+++ b/src/bftw.c
@@ -913,7 +913,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg
size_t qdepth = 4096;
size_t nthreads = args->nthreads;
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
// io_uring uses one fd per ring, ioq uses one ring per thread
if (nthreads >= nopenfd - 1) {
nthreads = nopenfd - 2;
@@ -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;
}
@@ -1676,6 +1676,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) {
ftwbuf->visit = visit;
ftwbuf->type = BFS_UNKNOWN;
ftwbuf->error = state->direrror;
+ ftwbuf->loopoff = 0;
ftwbuf->at_fd = AT_FDCWD;
ftwbuf->at_path = ftwbuf->path;
bftw_stat_init(&ftwbuf->stat_bufs, &state->stat_buf, &state->lstat_buf);
@@ -1733,6 +1734,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) {
if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) {
ftwbuf->type = BFS_ERROR;
ftwbuf->error = ELOOP;
+ ftwbuf->loopoff = ancestor->nameoff + ancestor->namelen;
return;
}
}
diff --git a/src/bftw.h b/src/bftw.h
index 8656ca7..a2a201c 100644
--- a/src/bftw.h
+++ b/src/bftw.h
@@ -56,6 +56,8 @@ struct BFTW {
enum bfs_type type;
/** The errno that occurred, if type == BFS_ERROR. */
int error;
+ /** For filesystem loops, the length of the loop prefix. */
+ size_t loopoff;
/** A parent file descriptor for the *at() family of calls. */
int at_fd;
diff --git a/src/bit.h b/src/bit.h
index 17cfbcf..c494130 100644
--- a/src/bit.h
+++ b/src/bit.h
@@ -12,7 +12,7 @@
#include <limits.h>
#include <stdint.h>
-#if __STDC_VERSION__ >= C23
+#if BFS_HAS_STDBIT_H
# include <stdbit.h>
#endif
@@ -173,11 +173,7 @@
# define ENDIAN_NATIVE 0
#endif
-#if __STDC_VERSION__ >= C23
-# define bswap_u16 stdc_memreverse8u16
-# define bswap_u32 stdc_memreverse8u32
-# define bswap_u64 stdc_memreverse8u64
-#elif __GNUC__
+#if __GNUC__
# define bswap_u16 __builtin_bswap16
# define bswap_u32 __builtin_bswap32
# define bswap_u64 __builtin_bswap64
@@ -222,25 +218,17 @@ static inline uint8_t bswap_u8(uint8_t n) {
// Select an overload based on an unsigned integer type
#define UINT_SELECT(n, name) \
_Generic((n), \
- char: name##_uc, \
- signed char: name##_uc, \
unsigned char: name##_uc, \
- signed short: name##_us, \
unsigned short: name##_us, \
- signed int: name##_ui, \
unsigned int: name##_ui, \
- signed long: name##_ul, \
unsigned long: name##_ul, \
- signed long long: name##_ull, \
unsigned long long: name##_ull)
// C23 polyfill: bit utilities
-#if __STDC_VERSION__ >= C23
+#if __STDC_VERSION_STDBIT_H__ >= C23
# define count_ones stdc_count_ones
# define count_zeros stdc_count_zeros
-# define rotate_left stdc_rotate_left
-# define rotate_right stdc_rotate_right
# define leading_zeros stdc_leading_zeros
# define leading_ones stdc_leading_ones
# define trailing_zeros stdc_trailing_zeros
@@ -273,31 +261,31 @@ static inline uint8_t bswap_u8(uint8_t n) {
#define BUILTIN_WIDTH(suffix) BUILTIN_WIDTH##suffix
#define COUNT_ONES(type, suffix, width) \
- static inline int count_ones##suffix(type n) { \
+ static inline unsigned int count_ones##suffix(type n) { \
return UINT_BUILTIN(popcount, suffix)(n); \
}
#define LEADING_ZEROS(type, suffix, width) \
- static inline int leading_zeros##suffix(type n) { \
+ static inline unsigned int leading_zeros##suffix(type n) { \
return n \
? UINT_BUILTIN(clz, suffix)(n) - (BUILTIN_WIDTH(suffix) - width) \
: width; \
}
#define TRAILING_ZEROS(type, suffix, width) \
- static inline int trailing_zeros##suffix(type n) { \
+ static inline unsigned int trailing_zeros##suffix(type n) { \
return n ? UINT_BUILTIN(ctz, suffix)(n) : (int)width; \
}
#define FIRST_TRAILING_ONE(type, suffix, width) \
- static inline int first_trailing_one##suffix(type n) { \
+ static inline unsigned int first_trailing_one##suffix(type n) { \
return UINT_BUILTIN(ffs, suffix)(n); \
}
#else // !__GNUC__
#define COUNT_ONES(type, suffix, width) \
- static inline int count_ones##suffix(type n) { \
+ static inline unsigned int count_ones##suffix(type n) { \
int ret; \
for (ret = 0; n; ++ret) { \
n &= n - 1; \
@@ -306,7 +294,7 @@ static inline uint8_t bswap_u8(uint8_t n) {
}
#define LEADING_ZEROS(type, suffix, width) \
- static inline int leading_zeros##suffix(type n) { \
+ static inline unsigned int leading_zeros##suffix(type n) { \
type bit = (type)1 << (width - 1); \
int ret; \
for (ret = 0; bit && !(n & bit); ++ret, bit >>= 1); \
@@ -314,7 +302,7 @@ static inline uint8_t bswap_u8(uint8_t n) {
}
#define TRAILING_ZEROS(type, suffix, width) \
- static inline int trailing_zeros##suffix(type n) { \
+ static inline unsigned int trailing_zeros##suffix(type n) { \
type bit = 1; \
int ret; \
for (ret = 0; bit && !(n & bit); ++ret, bit <<= 1); \
@@ -322,7 +310,7 @@ static inline uint8_t bswap_u8(uint8_t n) {
}
#define FIRST_TRAILING_ONE(type, suffix, width) \
- static inline int first_trailing_one##suffix(type n) { \
+ static inline unsigned int first_trailing_one##suffix(type n) { \
return n ? trailing_zeros##suffix(n) + 1 : 0; \
}
@@ -333,19 +321,9 @@ UINT_OVERLOADS(LEADING_ZEROS)
UINT_OVERLOADS(TRAILING_ZEROS)
UINT_OVERLOADS(FIRST_TRAILING_ONE)
-#define ROTATE_LEFT(type, suffix, width) \
- static inline type rotate_left##suffix(type n, int c) { \
- return (n << c) | (n >> ((width - c) % width)); \
- }
-
-#define ROTATE_RIGHT(type, suffix, width) \
- static inline type rotate_right##suffix(type n, int c) { \
- return (n >> c) | (n << ((width - c) % width)); \
- }
-
#define FIRST_LEADING_ONE(type, suffix, width) \
- static inline int first_leading_one##suffix(type n) { \
- return width - leading_zeros##suffix(n); \
+ static inline unsigned int first_leading_one##suffix(type n) { \
+ return n ? leading_zeros##suffix(n) + 1 : 0; \
}
#define HAS_SINGLE_BIT(type, suffix, width) \
@@ -354,17 +332,30 @@ UINT_OVERLOADS(FIRST_TRAILING_ONE)
return n - 1 < (n ^ (n - 1)); \
}
-UINT_OVERLOADS(ROTATE_LEFT)
-UINT_OVERLOADS(ROTATE_RIGHT)
+#define BIT_WIDTH(type, suffix, width) \
+ static inline unsigned int bit_width##suffix(type n) { \
+ return width - leading_zeros##suffix(n); \
+ }
+
+#define BIT_FLOOR(type, suffix, width) \
+ static inline type bit_floor##suffix(type n) { \
+ return n ? (type)1 << (bit_width##suffix(n) - 1) : 0; \
+ }
+
+#define BIT_CEIL(type, suffix, width) \
+ static inline type bit_ceil##suffix(type n) { \
+ return (type)1 << bit_width##suffix(n - !!n); \
+ }
+
UINT_OVERLOADS(FIRST_LEADING_ONE)
UINT_OVERLOADS(HAS_SINGLE_BIT)
+UINT_OVERLOADS(BIT_WIDTH)
+UINT_OVERLOADS(BIT_FLOOR)
+UINT_OVERLOADS(BIT_CEIL)
#define count_ones(n) UINT_SELECT(n, count_ones)(n)
#define count_zeros(n) UINT_SELECT(n, count_ones)(~(n))
-#define rotate_left(n, c) UINT_SELECT(n, rotate_left)(n, c)
-#define rotate_right(n, c) UINT_SELECT(n, rotate_right)(n, c)
-
#define leading_zeros(n) UINT_SELECT(n, leading_zeros)(n)
#define leading_ones(n) UINT_SELECT(n, leading_zeros)(~(n))
@@ -379,23 +370,26 @@ UINT_OVERLOADS(HAS_SINGLE_BIT)
#define has_single_bit(n) UINT_SELECT(n, has_single_bit)(n)
-#define BIT_FLOOR(type, suffix, width) \
- static inline type bit_floor##suffix(type n) { \
- return n ? (type)1 << (first_leading_one##suffix(n) - 1) : 0; \
- }
+#define bit_width(n) UINT_SELECT(n, bit_width)(n)
+#define bit_floor(n) UINT_SELECT(n, bit_floor)(n)
+#define bit_ceil(n) UINT_SELECT(n, bit_ceil)(n)
-#define BIT_CEIL(type, suffix, width) \
- static inline type bit_ceil##suffix(type n) { \
- return (type)1 << first_leading_one##suffix(n - !!n); \
+#endif // __STDC_VERSION_STDBIT_H__ < C23
+
+#define ROTATE_LEFT(type, suffix, width) \
+ static inline type rotate_left##suffix(type n, int c) { \
+ return (n << c) | (n >> ((width - c) % width)); \
}
-UINT_OVERLOADS(BIT_FLOOR)
-UINT_OVERLOADS(BIT_CEIL)
+#define ROTATE_RIGHT(type, suffix, width) \
+ static inline type rotate_right##suffix(type n, int c) { \
+ return (n >> c) | (n << ((width - c) % width)); \
+ }
-#define bit_width(n) first_leading_one(n)
-#define bit_floor(n) UINT_SELECT(n, bit_floor)(n)
-#define bit_ceil(n) UINT_SELECT(n, bit_ceil)(n)
+UINT_OVERLOADS(ROTATE_LEFT)
+UINT_OVERLOADS(ROTATE_RIGHT)
-#endif // __STDC_VERSION__ < C23
+#define rotate_left(n, c) UINT_SELECT(n, rotate_left)(n, c)
+#define rotate_right(n, c) UINT_SELECT(n, rotate_right)(n, c)
#endif // BFS_BIT_H
diff --git a/src/color.c b/src/color.c
index f004bf2..81f28bb 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;
@@ -1156,7 +1171,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i
}
int count = 0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (dstrcat(&cfile->buffer, " ") != 0) {
return -1;
}
@@ -1182,7 +1197,6 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i
attr(printf(2, 0))
static int cvbuff(CFILE *cfile, const char *format, va_list args) {
const struct colors *colors = cfile->colors;
- int error = errno;
// Color specifier (e.g. ${blu}) state
struct esc_seq **esc;
@@ -1240,12 +1254,6 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) {
}
break;
- case 'm':
- if (dstrcat(&cfile->buffer, xstrerror(error)) != 0) {
- return -1;
- }
- break;
-
case 'p':
switch (*++i) {
case 'q':
@@ -1390,3 +1398,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..8582eb3 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. */
@@ -85,7 +87,6 @@ int cfclose(CFILE *cfile);
* %g: A double
* %s: A string
* %zu: A size_t
- * %m: strerror(errno)
* %pq: A shell-escaped string, like bash's printf %q
* %pQ: A TTY-escaped string.
* %pF: A colored file name, from a const struct BFTW * argument
@@ -108,4 +109,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..0f619c2 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;
@@ -53,6 +56,7 @@ struct bfs_ctx *bfs_ctx_new(void) {
goto fail;
}
ctx->cur_nofile = ctx->orig_nofile;
+ ctx->raise_nofile = true;
ctx->users = bfs_users_new();
if (!ctx->users) {
@@ -98,13 +102,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 +135,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,9 +179,9 @@ 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: %s.\n", path, errstr());
} else if (cfile == ctx->cout) {
- bfs_error(ctx, "(standard output): %m.\n");
+ bfs_error(ctx, "(standard output): %s.\n", errstr());
}
}
@@ -188,30 +211,47 @@ 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;
+ 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 +269,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/ctx.h b/src/ctx.h
index fc3020c..b28a63c 100644
--- a/src/ctx.h
+++ b/src/ctx.h
@@ -102,6 +102,8 @@ struct bfs_ctx {
struct rlimit orig_nofile;
/** The current RLIMIT_NOFILE limits. */
struct rlimit cur_nofile;
+ /** Whether the fd limit should be raised. */
+ bool raise_nofile;
/** The current time. */
struct timespec now;
diff --git a/src/diag.c b/src/diag.c
index deb6f26..ccafd98 100644
--- a/src/diag.c
+++ b/src/diag.c
@@ -64,7 +64,7 @@ const char *debug_flag_name(enum debug_flags flag) {
}
void bfs_perror(const struct bfs_ctx *ctx, const char *str) {
- bfs_error(ctx, "%s: %m.\n", str);
+ bfs_error(ctx, "%s: %s.\n", str, errstr());
}
void bfs_error(const struct bfs_ctx *ctx, const char *format, ...) {
@@ -91,19 +91,12 @@ bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *for
}
void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args) {
- int error = errno;
-
bfs_error_prefix(ctx);
-
- errno = error;
cvfprintf(ctx->cerr, format, args);
}
bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args) {
- int error = errno;
-
if (bfs_warning_prefix(ctx)) {
- errno = error;
cvfprintf(ctx->cerr, format, args);
return true;
} else {
@@ -112,10 +105,7 @@ bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args) {
}
bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args) {
- int error = errno;
-
if (bfs_debug_prefix(ctx, flag)) {
- errno = error;
cvfprintf(ctx->cerr, format, args);
return true;
} else {
@@ -169,7 +159,7 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs
}
}
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
ret |= highlight_expr_recursive(ctx, child, args);
}
diff --git a/src/diag.h b/src/diag.h
index 2b13609..da22f3a 100644
--- a/src/diag.h
+++ b/src/diag.h
@@ -9,6 +9,7 @@
#define BFS_DIAG_H
#include "prelude.h"
+#include "bfstd.h"
#include <stdarg.h>
/**
@@ -55,6 +56,15 @@ void bfs_diagf(const struct bfs_loc *loc, const char *format, ...);
#define bfs_diag(...) bfs_diagf(bfs_location(), __VA_ARGS__)
/**
+ * Print a diagnostic message including the last error.
+ */
+#define bfs_ediag(...) \
+ bfs_ediag_("" __VA_ARGS__, 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 +73,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__, 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 +110,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__, "", 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..0495207 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -7,6 +7,7 @@
#include "prelude.h"
#include "eval.h"
+#include "atomic.h"
#include "bar.h"
#include "bfstd.h"
#include "bftw.h"
@@ -22,6 +23,7 @@
#include "printf.h"
#include "pwcache.h"
#include "sanity.h"
+#include "sighook.h"
#include "stat.h"
#include "trie.h"
#include "xregex.h"
@@ -63,7 +65,6 @@ static void eval_error(struct bfs_eval *state, const char *format, ...) {
// By POSIX, any errors should be accompanied by a non-zero exit status
*state->ret = EXIT_FAILURE;
- int error = errno;
const struct bfs_ctx *ctx = state->ctx;
CFILE *cerr = ctx->cerr;
@@ -71,7 +72,6 @@ static void eval_error(struct bfs_eval *state, const char *format, ...) {
va_list args;
va_start(args, format);
- errno = error;
cvfprintf(cerr, format, args);
va_end(args);
}
@@ -90,7 +90,7 @@ static bool eval_should_ignore(const struct bfs_eval *state, int error) {
*/
static void eval_report_error(struct bfs_eval *state) {
if (!eval_should_ignore(state, errno)) {
- eval_error(state, "%m.\n");
+ eval_error(state, "%s.\n", errstr());
}
}
@@ -99,9 +99,9 @@ static void eval_report_error(struct bfs_eval *state) {
*/
static void eval_io_error(const struct bfs_expr *expr, struct bfs_eval *state) {
if (expr->path) {
- eval_error(state, "'%s': %m.\n", expr->path);
+ eval_error(state, "'%s': %s.\n", expr->path, errstr());
} else {
- eval_error(state, "(standard output): %m.\n");
+ eval_error(state, "(standard output): %s.\n", errstr());
}
// Don't report the error again in bfs_ctx_free()
@@ -228,7 +228,7 @@ bool eval_context(const struct bfs_expr *expr, struct bfs_eval *state) {
static const struct timespec *eval_stat_time(const struct bfs_stat *statbuf, enum bfs_stat_field field, struct bfs_eval *state) {
const struct timespec *ret = bfs_stat_time(statbuf, field);
if (!ret) {
- eval_error(state, "Couldn't get file %s: %m.\n", bfs_stat_field_name(field));
+ eval_error(state, "Couldn't get file %s: %s.\n", bfs_stat_field_name(field), errstr());
}
return ret;
}
@@ -398,13 +398,13 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c
if (expr->eval_fn == eval_exec) {
if (bfs_exec_finish(expr->exec) != 0) {
if (errno != 0) {
- bfs_error(ctx, "%s %s: %m.\n", expr->argv[0], expr->argv[1]);
+ bfs_error(ctx, "%s %s: %s.\n", expr->argv[0], expr->argv[1], errstr());
}
ret = -1;
}
}
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (eval_exec_finish(child, ctx) != 0) {
ret = -1;
}
@@ -419,7 +419,7 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c
bool eval_exec(const struct bfs_expr *expr, struct bfs_eval *state) {
bool ret = bfs_exec(expr->exec, state->ftwbuf) == 0;
if (errno != 0) {
- eval_error(state, "%s %s: %m.\n", expr->argv[0], expr->argv[1]);
+ eval_error(state, "%s %s: %s.\n", expr->argv[0], expr->argv[1], errstr());
}
return ret;
}
@@ -902,7 +902,7 @@ bool eval_regex(const struct bfs_expr *expr, struct bfs_eval *state) {
eval_error(state, "%s.\n", str);
free(str);
} else {
- eval_error(state, "bfs_regerror(): %m.\n");
+ eval_error(state, "bfs_regerror(): %s.\n", errstr());
}
}
@@ -1020,7 +1020,7 @@ static int eval_gettime(struct bfs_eval *state, struct timespec *ts) {
#ifdef BFS_CLOCK
int ret = clock_gettime(BFS_CLOCK, ts);
if (ret != 0) {
- bfs_warning(state->ctx, "%pP: clock_gettime(): %m.\n", state->ftwbuf);
+ bfs_warning(state->ctx, "%pP: clock_gettime(): %s.\n", state->ftwbuf, errstr());
}
return ret;
#else
@@ -1091,7 +1091,7 @@ bool eval_not(const struct bfs_expr *expr, struct bfs_eval *state) {
* Evaluate a conjunction.
*/
bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) {
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (!eval_expr(child, state) || state->quit) {
return false;
}
@@ -1104,7 +1104,7 @@ bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) {
* Evaluate a disjunction.
*/
bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) {
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (eval_expr(child, state) || state->quit) {
return true;
}
@@ -1119,7 +1119,7 @@ bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) {
bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) {
bool ret uninit(false);
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
ret = eval_expr(child, state);
if (state->quit) {
break;
@@ -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);
@@ -1375,6 +1375,11 @@ struct callback_args {
struct bfs_bar *bar;
/** The time of the last status update. */
struct timespec last_status;
+ /** SIGINFO hook. */
+ struct sighook *info_hook;
+ /** Flag set by SIGINFO hook. */
+ atomic bool info_flag;
+
/** The number of files visited so far. */
size_t count;
@@ -1401,15 +1406,38 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
state.ret = &args->ret;
state.quit = false;
+ // Check whether SIGINFO was delivered and show/hide the bar
+ if (exchange(&args->info_flag, false, relaxed)) {
+ if (args->bar) {
+ bfs_bar_hide(args->bar);
+ args->bar = NULL;
+ } else {
+ args->bar = bfs_bar_show();
+ if (!args->bar) {
+ bfs_warning(ctx, "Couldn't show status bar: %s.\n", errstr());
+ }
+ }
+ }
+
if (args->bar) {
eval_status(&state, args->bar, &args->last_status, args->count);
}
if (ftwbuf->type == BFS_ERROR) {
- if (!eval_should_ignore(&state, ftwbuf->error)) {
- eval_error(&state, "%s.\n", xstrerror(ftwbuf->error));
- }
state.action = BFTW_PRUNE;
+
+ if (ftwbuf->error == ELOOP && ftwbuf->loopoff > 0) {
+ char *loop = strndup(ftwbuf->path, ftwbuf->loopoff);
+ if (loop) {
+ eval_error(&state, "Filesystem loop back to ${di}%pq${rs}\n", loop);
+ free(loop);
+ goto done;
+ }
+ } else if (eval_should_ignore(&state, ftwbuf->error)) {
+ goto done;
+ }
+
+ eval_error(&state, "%s.\n", xstrerror(ftwbuf->error));
goto done;
}
@@ -1464,10 +1492,19 @@ done:
return state.action;
}
+/** Show/hide the bar in response to SIGINFO. */
+static void eval_siginfo(int sig, siginfo_t *info, void *ptr) {
+ struct callback_args *args = ptr;
+ store(&args->info_flag, true, relaxed);
+}
+
/** Raise RLIMIT_NOFILE if possible, and return the new limit. */
static int raise_fdlimit(struct bfs_ctx *ctx) {
rlim_t cur = ctx->orig_nofile.rlim_cur;
rlim_t max = ctx->orig_nofile.rlim_max;
+ if (!ctx->raise_nofile) {
+ max = cur;
+ }
rlim_t target = 64 << 10;
if (rlim_cmp(target, max) > 0) {
@@ -1596,7 +1633,7 @@ static bool eval_must_buffer(const struct bfs_expr *expr) {
return true;
}
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (eval_must_buffer(child)) {
return true;
}
@@ -1619,10 +1656,17 @@ int bfs_eval(struct bfs_ctx *ctx) {
if (ctx->status) {
args.bar = bfs_bar_show();
if (!args.bar) {
- bfs_warning(ctx, "Couldn't show status bar: %m.\n\n");
+ bfs_warning(ctx, "Couldn't show status bar: %s.\n\n", errstr());
}
}
+#ifdef SIGINFO
+ int siginfo = SIGINFO;
+#else
+ int siginfo = SIGUSR1;
+#endif
+ args.info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE);
+
struct trie seen;
if (ctx->unique) {
trie_init(&seen);
@@ -1690,6 +1734,7 @@ int bfs_eval(struct bfs_ctx *ctx) {
trie_destroy(&seen);
}
+ sigunhook(args.info_hook);
bfs_bar_hide(args.bar);
return args.ret;
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/expr.h b/src/expr.h
index 7bcace7..60b298d 100644
--- a/src/expr.h
+++ b/src/expr.h
@@ -244,4 +244,10 @@ bool bfs_expr_cmp(const struct bfs_expr *expr, long long n);
*/
void bfs_expr_clear(struct bfs_expr *expr);
+/**
+ * Iterate over the children of an expression.
+ */
+#define for_expr(child, expr) \
+ for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next)
+
#endif // BFS_EXPR_H
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/fsade.h b/src/fsade.h
index eefef9f..4465017 100644
--- a/src/fsade.h
+++ b/src/fsade.h
@@ -13,9 +13,9 @@
#define BFS_CAN_CHECK_ACL (BFS_HAS_ACL_GET_FILE || BFS_HAS_ACL_TRIVIAL)
-#define BFS_CAN_CHECK_CAPABILITIES BFS_USE_LIBCAP
+#define BFS_CAN_CHECK_CAPABILITIES BFS_WITH_LIBCAP
-#define BFS_CAN_CHECK_CONTEXT BFS_USE_LIBSELINUX
+#define BFS_CAN_CHECK_CONTEXT BFS_WITH_LIBSELINUX
#define BFS_CAN_CHECK_XATTRS (BFS_USE_SYS_EXTATTR_H || BFS_USE_SYS_XATTR_H)
diff --git a/src/ioq.c b/src/ioq.c
index 43a1b35..4057a5e 100644
--- a/src/ioq.c
+++ b/src/ioq.c
@@ -135,7 +135,7 @@
#include <stdlib.h>
#include <sys/stat.h>
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
# include <liburing.h>
#endif
@@ -459,7 +459,7 @@ static void ioq_batch_push(struct ioqq *ioqq, struct ioq_batch *batch, struct io
/** Sentinel stop command. */
static struct ioq_ent IOQ_STOP;
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
/**
* Supported io_uring operations.
*/
@@ -477,7 +477,7 @@ struct ioq_thread {
/** Pointer back to the I/O queue. */
struct ioq *parent;
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
/** io_uring instance. */
struct io_uring ring;
/** Any error that occurred initializing the ring. */
@@ -497,7 +497,7 @@ struct ioq {
/** ioq_ent arena. */
struct arena ents;
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
/** struct statx arena. */
struct arena xbufs;
#endif
@@ -559,7 +559,7 @@ static void ioq_dispatch_sync(struct ioq *ioq, struct ioq_ent *ent) {
ent->result = -ENOSYS;
}
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
/** io_uring worker state. */
struct ioq_ring_state {
@@ -775,7 +775,7 @@ static void ioq_ring_work(struct ioq_thread *thread) {
}
}
-#endif // BFS_USE_LIBURING
+#endif // BFS_WITH_LIBURING
/** Synchronous syscall loop. */
static void ioq_sync_work(struct ioq_thread *thread) {
@@ -811,7 +811,7 @@ static void ioq_sync_work(struct ioq_thread *thread) {
static void *ioq_work(void *ptr) {
struct ioq_thread *thread = ptr;
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
if (thread->ring_err == 0) {
ioq_ring_work(thread);
return NULL;
@@ -824,7 +824,7 @@ static void *ioq_work(void *ptr) {
/** Initialize io_uring thread state. */
static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) {
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
struct ioq_thread *prev = NULL;
if (thread > ioq->threads) {
prev = thread - 1;
@@ -890,7 +890,7 @@ static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) {
/** Destroy an io_uring. */
static void ioq_ring_exit(struct ioq_thread *thread) {
-#if BFS_USE_LIBURING
+#if BFS_WITH_LIBURING
if (thread->ring_err == 0) {
io_uring_queue_exit(&thread->ring);
}
@@ -926,7 +926,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) {
ioq->depth = depth;
ARENA_INIT(&ioq->ents, struct ioq_ent);
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
ARENA_INIT(&ioq->xbufs, struct statx);
#endif
@@ -1036,7 +1036,7 @@ int ioq_stat(struct ioq *ioq, int dfd, const char *path, enum bfs_stat_flags fla
args->flags = flags;
args->buf = buf;
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
args->xbuf = arena_alloc(&ioq->xbufs);
if (!args->xbuf) {
ioq_free(ioq, ent);
@@ -1060,7 +1060,7 @@ void ioq_free(struct ioq *ioq, struct ioq_ent *ent) {
bfs_assert(ioq->size > 0);
--ioq->size;
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
if (ent->op == IOQ_STAT && ent->stat.xbuf) {
arena_free(&ioq->xbufs, ent->stat.xbuf);
}
@@ -1091,7 +1091,7 @@ void ioq_destroy(struct ioq *ioq) {
ioqq_destroy(ioq->ready);
ioqq_destroy(ioq->pending);
-#if BFS_USE_LIBURING && BFS_USE_STATX
+#if BFS_WITH_LIBURING && BFS_USE_STATX
arena_destroy(&ioq->xbufs);
#endif
arena_destroy(&ioq->ents);
diff --git a/src/list.h b/src/list.h
index 61d0e5b..d95483f 100644
--- a/src/list.h
+++ b/src/list.h
@@ -324,6 +324,25 @@
LIST_VOID_(SLIST_INSERT_(list, &(list)->head, item, __VA_ARGS__))
/**
+ * Splice a singly-linked list into another.
+ *
+ * @param dest
+ * The destination list.
+ * @param cursor
+ * A pointer to the item to splice after, e.g. &list->head or list->tail.
+ * @param src
+ * The source list.
+ */
+#define SLIST_SPLICE(dest, cursor, src) \
+ LIST_VOID_(SLIST_SPLICE_((dest), (cursor), (src)))
+
+#define SLIST_SPLICE_(dest, cursor, src) \
+ *src->tail = *cursor, \
+ *cursor = src->head, \
+ dest->tail = *dest->tail ? src->tail : dest->tail, \
+ SLIST_INIT(src)
+
+/**
* Add an entire singly-linked list to the tail of another.
*
* @param dest
@@ -332,10 +351,7 @@
* The source list.
*/
#define SLIST_EXTEND(dest, src) \
- SLIST_EXTEND_((dest), (src))
-
-#define SLIST_EXTEND_(dest, src) \
- (src->head ? (*dest->tail = src->head, dest->tail = src->tail, SLIST_INIT(src)) : (void)0)
+ SLIST_SPLICE(dest, (dest)->tail, src)
/**
* Remove an item from a singly-linked list.
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/opt.c b/src/opt.c
index 883d598..6704a46 100644
--- a/src/opt.c
+++ b/src/opt.c
@@ -1088,7 +1088,7 @@ static struct bfs_expr *annotate_and(struct bfs_opt *opt, struct bfs_expr *expr,
expr->cost = 0.0;
expr->probability = 1.0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
expr->pure &= child->pure;
expr->always_true &= child->always_true;
expr->always_false |= child->always_false;
@@ -1107,7 +1107,7 @@ static struct bfs_expr *annotate_or(struct bfs_opt *opt, struct bfs_expr *expr,
expr->cost = 0.0;
float false_prob = 1.0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
expr->pure &= child->pure;
expr->always_true |= child->always_true;
expr->always_false &= child->always_false;
@@ -1124,7 +1124,7 @@ static struct bfs_expr *annotate_comma(struct bfs_opt *opt, struct bfs_expr *exp
expr->pure = true;
expr->cost = 0.0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
expr->pure &= child->pure;
expr->always_true = child->always_true;
expr->always_false = child->always_false;
@@ -1790,7 +1790,7 @@ static struct bfs_expr *data_flow_leave(struct bfs_opt *opt, struct bfs_expr *ex
if (df_is_bottom(&opt->after_false)) {
if (!expr->pure) {
expr->always_true = true;
- expr->probability = 0.0;
+ expr->probability = 1.0;
} else if (expr->eval_fn != eval_true) {
opt_warning(opt, expr, "This expression is always true.\n\n");
opt_debug(opt, "pure, always true\n");
@@ -1919,7 +1919,7 @@ static struct bfs_expr *simplify_not(struct bfs_opt *opt, struct bfs_expr *expr,
static struct bfs_expr *lift_andor_not(struct bfs_opt *opt, struct bfs_expr *expr) {
// Only lift negations if it would reduce the number of (-not) expressions
size_t added = 0, removed = 0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (child->eval_fn == eval_not) {
++removed;
} else {
@@ -1968,7 +1968,7 @@ static struct bfs_expr *first_ignorable(struct bfs_opt *opt, struct bfs_expr *ex
}
struct bfs_expr *ret = NULL;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
if (!child->pure) {
ret = NULL;
} else if (!ret) {
@@ -2176,17 +2176,20 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) {
return expr;
}
-/** Estimate the odds of an expression calling stat(). */
-static float expr_stat_odds(struct bfs_expr *expr) {
- if (expr->calls_stat) {
+/** An expression predicate. */
+typedef bool expr_pred(const struct bfs_expr *expr);
+
+/** Estimate the odds that a matching expression will be evaluated. */
+static float estimate_odds(const struct bfs_expr *expr, expr_pred *pred) {
+ if (pred(expr)) {
return 1.0;
}
- float nostat_odds = 1.0;
+ float nonmatch_odds = 1.0;
float reached_odds = 1.0;
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
- float child_odds = expr_stat_odds(child);
- nostat_odds *= 1.0 - reached_odds * child_odds;
+ for_expr (child, expr) {
+ float child_odds = estimate_odds(child, pred);
+ nonmatch_odds *= 1.0 - reached_odds * child_odds;
if (expr->eval_fn == eval_and) {
reached_odds *= child->probability;
@@ -2195,7 +2198,12 @@ static float expr_stat_odds(struct bfs_expr *expr) {
}
}
- return 1.0 - nostat_odds;
+ return 1.0 - nonmatch_odds;
+}
+
+/** Whether an expression calls stat(). */
+static bool calls_stat(const struct bfs_expr *expr) {
+ return expr->calls_stat;
}
/** Estimate the odds of calling stat(). */
@@ -2204,15 +2212,20 @@ static float estimate_stat_odds(struct bfs_ctx *ctx) {
return 1.0;
}
- float nostat_odds = 1.0 - expr_stat_odds(ctx->exclude);
+ float nostat_odds = 1.0 - estimate_odds(ctx->exclude, calls_stat);
float reached_odds = 1.0 - ctx->exclude->probability;
- float expr_odds = expr_stat_odds(ctx->expr);
+ float expr_odds = estimate_odds(ctx->expr, calls_stat);
nostat_odds *= 1.0 - reached_odds * expr_odds;
return 1.0 - nostat_odds;
}
+/** Matches -(exec|ok) ... \; */
+static bool single_exec(const struct bfs_expr *expr) {
+ return expr->eval_fn == eval_exec && !(expr->exec->flags & BFS_EXEC_MULTI);
+}
+
int bfs_optimize(struct bfs_ctx *ctx) {
bfs_ctx_dump(ctx, DEBUG_OPT);
@@ -2291,6 +2304,17 @@ int bfs_optimize(struct bfs_ctx *ctx) {
opt_leave(&opt, "eager stat cost: ${ylw}%g${rs}\n", eager_cost);
}
+#ifndef POSIX_SPAWN_SETRLIMIT
+ // If bfs_spawn_setrlimit() would force us to use fork() over
+ // posix_spawn(), the extra cost may outweigh the benefit of a
+ // higher RLIMIT_NOFILE
+ float single_exec_odds = estimate_odds(ctx->expr, single_exec);
+ if (single_exec_odds >= 0.5) {
+ opt_enter(&opt, "single ${blu}-exec${rs} odds: ${ylw}%g${rs}\n", single_exec_odds);
+ ctx->raise_nofile = false;
+ opt_leave(&opt, "not raising RLIMIT_NOFILE\n");
+ }
+#endif
}
opt_leave(&opt, NULL);
diff --git a/src/parse.c b/src/parse.c
index a1155c0..58a0209 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -381,7 +381,7 @@ static int expr_open(struct bfs_parser *parser, struct bfs_expr *expr, const cha
return 0;
fail:
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
if (cfile) {
cfclose(cfile);
} else if (file) {
@@ -401,7 +401,7 @@ static int stat_arg(const struct bfs_parser *parser, char **arg, struct bfs_stat
int ret = bfs_stat(AT_FDCWD, *arg, flags, sb);
if (ret != 0) {
- parse_argv_error(parser, arg, 1, "%m.\n");
+ parse_argv_error(parser, arg, 1, "%s.\n", errstr());
}
return ret;
}
@@ -1344,7 +1344,7 @@ static struct bfs_expr *parse_files0_from(struct bfs_parser *parser, int arg1, i
file = xfopen(from, O_RDONLY | O_CLOEXEC);
}
if (!file) {
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
return NULL;
}
@@ -1509,7 +1509,7 @@ static struct bfs_expr *parse_fstype(struct bfs_parser *parser, int arg1, int ar
}
if (!bfs_ctx_mtab(parser->ctx)) {
- parse_expr_error(parser, expr, "Couldn't parse the mount table: %m.\n");
+ parse_expr_error(parser, expr, "Couldn't parse the mount table: %s.\n", errstr());
return NULL;
}
@@ -1534,7 +1534,7 @@ static struct bfs_expr *parse_group(struct bfs_parser *parser, int arg1, int arg
return NULL;
}
} else if (errno) {
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
return NULL;
} else {
parse_expr_error(parser, expr, "No such group.\n");
@@ -1577,7 +1577,7 @@ static struct bfs_expr *parse_user(struct bfs_parser *parser, int arg1, int arg2
return NULL;
}
} else if (errno) {
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
return NULL;
} else {
parse_expr_error(parser, expr, "No such user.\n");
@@ -1737,7 +1737,7 @@ static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr)
if (xgetdate(expr->argv[1], &expr->reftime) == 0) {
return 0;
} else if (errno != EINVAL) {
- parse_expr_error(parser, expr, "%m.\n");
+ parse_expr_error(parser, expr, "%s.\n", errstr());
return -1;
}
@@ -2253,16 +2253,27 @@ static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int
// See https://www.gnu.org/software/gnulib/manual/html_node/Predefined-Syntaxes.html
const char *type = expr->argv[1];
if (strcmp(type, "posix-basic") == 0
+ || strcmp(type, "posix-minimal-basic") == 0
|| strcmp(type, "ed") == 0
|| strcmp(type, "sed") == 0) {
parser->regex_type = BFS_REGEX_POSIX_BASIC;
} else if (strcmp(type, "posix-extended") == 0) {
parser->regex_type = BFS_REGEX_POSIX_EXTENDED;
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
+ } else if (strcmp(type, "awk") == 0
+ || strcmp(type, "posix-awk") == 0) {
+ parser->regex_type = BFS_REGEX_AWK;
+ } else if (strcmp(type, "gnu-awk") == 0) {
+ parser->regex_type = BFS_REGEX_GNU_AWK;
} else if (strcmp(type, "emacs") == 0) {
parser->regex_type = BFS_REGEX_EMACS;
} else if (strcmp(type, "grep") == 0) {
parser->regex_type = BFS_REGEX_GREP;
+ } else if (strcmp(type, "egrep") == 0
+ || strcmp(type, "posix-egrep") == 0) {
+ parser->regex_type = BFS_REGEX_EGREP;
+ } else if (strcmp(type, "findutils-default") == 0) {
+ parser->regex_type = BFS_REGEX_GNU_FIND;
#endif
} else if (strcmp(type, "help") == 0) {
parser->just_info = true;
@@ -2277,14 +2288,23 @@ static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int
list_types:
cfprintf(cfile, "Supported types are:\n\n");
- cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n");
- cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n");
- cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n");
-#if BFS_USE_ONIGURUMA
- cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n");
- cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n");
+ cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n");
+ cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n");
+ cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n\n");
+
+ cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n\n");
+
+#if BFS_WITH_ONIGURUMA
+ cfprintf(cfile, " [${bld}posix-${rs}]${bld}awk${rs}: Like ${grn}awk${rs}\n");
+ cfprintf(cfile, " ${bld}gnu-awk${rs}: Like GNU ${grn}awk${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n");
+ cfprintf(cfile, " [${bld}posix-${rs}]${bld}egrep${rs}: Like ${grn}grep${rs} ${cyn}-E${rs}\n\n");
+
+ cfprintf(cfile, " ${bld}findutils-default${rs}: Like GNU ${grn}find${rs}\n");
#endif
- cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n");
return NULL;
}
@@ -3458,7 +3478,7 @@ static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag
++rparens;
} else {
cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]);
- for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) {
+ for_expr (child, expr) {
int parens = child->next ? 0 : rparens + 1;
dump_expr_multiline(ctx, flag, child, indent + 1, parens);
}
diff --git a/src/prelude.h b/src/prelude.h
index ddeacbd..0944df1 100644
--- a/src/prelude.h
+++ b/src/prelude.h
@@ -16,7 +16,8 @@
#define C17 201710L
#define C23 202311L
-#include <stddef.h>
+// Get the static_assert() definition as well as __GLIBC__
+#include <assert.h>
#if __STDC_VERSION__ < C23
# include <stdalign.h>
@@ -49,6 +50,9 @@ extern const char bfs_version[];
#if __has_include(<paths.h>)
# define BFS_HAS_PATHS_H true
#endif
+#if __has_include(<stdbit.h>)
+# define BFS_HAS_STDBIT_H true
+#endif
#if __has_include(<sys/extattr.h>)
# define BFS_HAS_SYS_EXTATTR_H true
#endif
@@ -75,6 +79,7 @@ extern const char bfs_version[];
#define BFS_HAS_MNTENT_H __GLIBC__
#define BFS_HAS_PATHS_H true
+#define BFS_HAS_STDBIT_H (__STDC_VERSION__ >= C23)
#define BFS_HAS_SYS_EXTATTR_H __FreeBSD__
#define BFS_HAS_SYS_MKDEV_H false
#define BFS_HAS_SYS_PARAM_H true
@@ -127,27 +132,12 @@ 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
/**
* Get the length of an array.
*/
-#define countof(array) (sizeof(array) / sizeof(0[array]))
+#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__]))
/**
* False sharing/destructive interference/largest cache line size.
@@ -168,21 +158,6 @@ extern const char bfs_version[];
#endif
/**
- * Polyfill max_align_t if we don't already have it.
- */
-#if !BFS_HAS_MAX_ALIGN_T
-typedef union {
-# ifdef __BIGGEST_ALIGNMENT__
- alignas(__BIGGEST_ALIGNMENT__) char c;
-# else
- long double ld;
- long long ll;
- void *ptr;
-# endif
-} max_align_t;
-#endif
-
-/**
* Alignment specifier that avoids false sharing.
*/
#define cache_align alignas(FALSE_SHARING_SIZE)
diff --git a/src/printf.c b/src/printf.c
index f8428f7..f514361 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -544,7 +544,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF
int error = 0;
if (type == BFS_ERROR) {
- if (errno_is_like(ELOOP)) {
+ if (errno == ELOOP) {
str = "L";
} else if (errno_is_like(ENOENT)) {
str = "N";
@@ -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..ece8147
--- /dev/null
+++ b/src/sighook.c
@@ -0,0 +1,604 @@
+// 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) {
+ if (!hook) {
+ return;
+ }
+
+ 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/trie.c b/src/trie.c
index 808953e..cc5db09 100644
--- a/src/trie.c
+++ b/src/trie.c
@@ -221,7 +221,8 @@ struct trie_leaf *trie_find_str(const struct trie *trie, const char *key) {
return trie_find_mem(trie, key, strlen(key) + 1);
}
-struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t length) {
+trie_clones
+static struct trie_leaf *trie_find_mem_impl(const struct trie *trie, const void *key, size_t length) {
struct trie_leaf *rep = trie_representative(trie, key, length);
if (rep && rep->length == length && memcmp(rep->key, key, length) == 0) {
return rep;
@@ -230,7 +231,12 @@ struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t
}
}
-struct trie_leaf *trie_find_postfix(const struct trie *trie, const char *key) {
+struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t length) {
+ return trie_find_mem_impl(trie, key, length);
+}
+
+trie_clones
+static struct trie_leaf *trie_find_postfix_impl(const struct trie *trie, const char *key) {
size_t length = strlen(key);
struct trie_leaf *rep = trie_representative(trie, key, length + 1);
if (rep && rep->length >= length && memcmp(rep->key, key, length) == 0) {
@@ -240,6 +246,10 @@ struct trie_leaf *trie_find_postfix(const struct trie *trie, const char *key) {
}
}
+struct trie_leaf *trie_find_postfix(const struct trie *trie, const char *key) {
+ return trie_find_postfix_impl(trie, key);
+}
+
/**
* Find a leaf that may end at the current node.
*/
@@ -611,7 +621,7 @@ static void trie_free_singletons(struct trie *trie, uintptr_t ptr) {
struct trie_node *node = trie_decode_node(ptr);
// Make sure the bitmap is a power of two, i.e. it has just one child
- bfs_assert(has_single_bit(node->bitmap));
+ bfs_assert(has_single_bit((size_t)node->bitmap));
ptr = node->children[0];
trie_node_free(trie, node, 1);
diff --git a/src/xregex.c b/src/xregex.c
index c2711bc..2d089b2 100644
--- a/src/xregex.c
+++ b/src/xregex.c
@@ -13,7 +13,7 @@
#include <stdlib.h>
#include <string.h>
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
# include <langinfo.h>
# include <oniguruma.h>
#else
@@ -21,7 +21,7 @@
#endif
struct bfs_regex {
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
unsigned char *pattern;
OnigRegex impl;
int err;
@@ -32,11 +32,17 @@ struct bfs_regex {
#endif
};
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
static int bfs_onig_status;
static OnigEncoding bfs_onig_enc;
+static OnigSyntaxType bfs_onig_syntax_awk;
+static OnigSyntaxType bfs_onig_syntax_gnu_awk;
+static OnigSyntaxType bfs_onig_syntax_emacs;
+static OnigSyntaxType bfs_onig_syntax_egrep;
+static OnigSyntaxType bfs_onig_syntax_gnu_find;
+
/** pthread_once() callback. */
static void bfs_onig_once(void) {
// Fall back to ASCII by default
@@ -103,6 +109,35 @@ static void bfs_onig_once(void) {
if (bfs_onig_status != ONIG_NORMAL) {
bfs_onig_enc = NULL;
}
+
+ // Compute the GNU extensions
+ OnigSyntaxType *ere = ONIG_SYNTAX_POSIX_EXTENDED;
+ OnigSyntaxType *gnu = ONIG_SYNTAX_GNU_REGEX;
+ unsigned int gnu_op = gnu->op & ~ere->op;
+ unsigned int gnu_op2 = gnu->op2 & ~ere->op2;
+ unsigned int gnu_behavior = gnu->behavior & ~ere->behavior;
+
+ onig_copy_syntax(&bfs_onig_syntax_awk, ONIG_SYNTAX_POSIX_EXTENDED);
+ bfs_onig_syntax_awk.behavior |= ONIG_SYN_ALLOW_INVALID_INTERVAL;
+ bfs_onig_syntax_awk.behavior |= ONIG_SYN_BACKSLASH_ESCAPE_IN_CC;
+
+ onig_copy_syntax(&bfs_onig_syntax_gnu_awk, &bfs_onig_syntax_awk);
+ bfs_onig_syntax_gnu_awk.op |= gnu_op;
+ bfs_onig_syntax_gnu_awk.op2 |= gnu_op2;
+ bfs_onig_syntax_gnu_awk.behavior |= gnu_behavior;
+ bfs_onig_syntax_gnu_awk.behavior &= ~ONIG_SYN_CONTEXT_INDEP_REPEAT_OPS;
+ bfs_onig_syntax_gnu_awk.behavior &= ~ONIG_SYN_CONTEXT_INVALID_REPEAT_OPS;
+
+ // https://github.com/kkos/oniguruma/issues/296
+ onig_copy_syntax(&bfs_onig_syntax_emacs, ONIG_SYNTAX_EMACS);
+ bfs_onig_syntax_emacs.op2 |= ONIG_SYN_OP2_QMARK_GROUP_EFFECT;
+
+ onig_copy_syntax(&bfs_onig_syntax_egrep, ONIG_SYNTAX_POSIX_EXTENDED);
+ bfs_onig_syntax_egrep.behavior |= ONIG_SYN_ALLOW_INVALID_INTERVAL;
+ bfs_onig_syntax_egrep.behavior &= ~ONIG_SYN_CONTEXT_INVALID_REPEAT_OPS;
+
+ onig_copy_syntax(&bfs_onig_syntax_gnu_find, &bfs_onig_syntax_emacs);
+ bfs_onig_syntax_gnu_find.options |= ONIG_OPTION_MULTILINE;
}
/** Initialize Oniguruma. */
@@ -121,7 +156,7 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ
return -1;
}
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
// onig_error_code_to_str() says
//
// don't call this after the pattern argument of onig_new() is freed
@@ -143,12 +178,24 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ
case BFS_REGEX_POSIX_EXTENDED:
syntax = ONIG_SYNTAX_POSIX_EXTENDED;
break;
+ case BFS_REGEX_AWK:
+ syntax = &bfs_onig_syntax_awk;
+ break;
+ case BFS_REGEX_GNU_AWK:
+ syntax = &bfs_onig_syntax_gnu_awk;
+ break;
case BFS_REGEX_EMACS:
- syntax = ONIG_SYNTAX_EMACS;
+ syntax = &bfs_onig_syntax_emacs;
break;
case BFS_REGEX_GREP:
syntax = ONIG_SYNTAX_GREP;
break;
+ case BFS_REGEX_EGREP:
+ syntax = &bfs_onig_syntax_egrep;
+ break;
+ case BFS_REGEX_GNU_FIND:
+ syntax = &bfs_onig_syntax_gnu_find;
+ break;
}
bfs_assert(syntax, "Invalid regex type");
@@ -204,7 +251,7 @@ fail:
int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags flags) {
size_t len = strlen(str);
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
const unsigned char *ustr = (const unsigned char *)str;
const unsigned char *end = ustr + len;
@@ -263,7 +310,7 @@ int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags
void bfs_regfree(struct bfs_regex *regex) {
if (regex) {
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
onig_free(regex->impl);
free(regex->pattern);
#else
@@ -278,7 +325,7 @@ char *bfs_regerror(const struct bfs_regex *regex) {
return strdup(xstrerror(ENOMEM));
}
-#if BFS_USE_ONIGURUMA
+#if BFS_WITH_ONIGURUMA
unsigned char *str = malloc(ONIG_MAX_ERROR_MESSAGE_LEN);
if (str) {
onig_error_code_to_str(str, regex->err, &regex->einfo);
diff --git a/src/xregex.h b/src/xregex.h
index 998a2b0..750db24 100644
--- a/src/xregex.h
+++ b/src/xregex.h
@@ -15,8 +15,12 @@ struct bfs_regex;
enum bfs_regex_type {
BFS_REGEX_POSIX_BASIC,
BFS_REGEX_POSIX_EXTENDED,
+ BFS_REGEX_AWK,
+ BFS_REGEX_GNU_AWK,
BFS_REGEX_EMACS,
BFS_REGEX_GREP,
+ BFS_REGEX_EGREP,
+ BFS_REGEX_GNU_FIND,
};
/**
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';