diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/atomic.h | 33 | ||||
-rw-r--r-- | src/bar.c | 142 | ||||
-rw-r--r-- | src/bfstd.c | 47 | ||||
-rw-r--r-- | src/bfstd.h | 20 | ||||
-rw-r--r-- | src/bftw.c | 10 | ||||
-rw-r--r-- | src/bftw.h | 2 | ||||
-rw-r--r-- | src/bit.h | 100 | ||||
-rw-r--r-- | src/color.c | 59 | ||||
-rw-r--r-- | src/color.h | 8 | ||||
-rw-r--r-- | src/ctx.c | 105 | ||||
-rw-r--r-- | src/ctx.h | 2 | ||||
-rw-r--r-- | src/diag.c | 14 | ||||
-rw-r--r-- | src/diag.h | 39 | ||||
-rw-r--r-- | src/dir.c | 16 | ||||
-rw-r--r-- | src/dir.h | 8 | ||||
-rw-r--r-- | src/dstring.c | 4 | ||||
-rw-r--r-- | src/dstring.h | 4 | ||||
-rw-r--r-- | src/eval.c | 85 | ||||
-rw-r--r-- | src/exec.c | 6 | ||||
-rw-r--r-- | src/expr.h | 6 | ||||
-rw-r--r-- | src/fsade.c | 2 | ||||
-rw-r--r-- | src/fsade.h | 4 | ||||
-rw-r--r-- | src/ioq.c | 26 | ||||
-rw-r--r-- | src/list.h | 24 | ||||
-rw-r--r-- | src/main.c | 1 | ||||
-rw-r--r-- | src/mtab.c | 9 | ||||
-rw-r--r-- | src/opt.c | 56 | ||||
-rw-r--r-- | src/parse.c | 52 | ||||
-rw-r--r-- | src/prelude.h | 39 | ||||
-rw-r--r-- | src/printf.c | 4 | ||||
-rw-r--r-- | src/sighook.c | 604 | ||||
-rw-r--r-- | src/sighook.h | 73 | ||||
-rw-r--r-- | src/stat.c | 37 | ||||
-rw-r--r-- | src/trie.c | 16 | ||||
-rw-r--r-- | src/xregex.c | 63 | ||||
-rw-r--r-- | src/xregex.h | 4 | ||||
-rw-r--r-- | src/xspawn.c | 37 | ||||
-rw-r--r-- | src/xtime.c | 40 |
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 @@ -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> /** @@ -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; } } @@ -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; @@ -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 @@ -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) { @@ -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; @@ -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); } @@ -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; @@ -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) { @@ -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. @@ -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; @@ -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); @@ -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) @@ -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); @@ -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. @@ -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) @@ -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; } @@ -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 @@ -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) { @@ -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, ®ex->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'; |