diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/alloc.c | 6 | ||||
-rw-r--r-- | src/alloc.h | 22 | ||||
-rw-r--r-- | src/atomic.h | 6 | ||||
-rw-r--r-- | src/bar.c | 57 | ||||
-rw-r--r-- | src/bfs.h | 214 | ||||
-rw-r--r-- | src/bfstd.c | 20 | ||||
-rw-r--r-- | src/bfstd.h | 19 | ||||
-rw-r--r-- | src/bftw.c | 10 | ||||
-rw-r--r-- | src/bftw.h | 1 | ||||
-rw-r--r-- | src/bit.h | 5 | ||||
-rw-r--r-- | src/color.c | 204 | ||||
-rw-r--r-- | src/color.h | 7 | ||||
-rw-r--r-- | src/ctx.c | 10 | ||||
-rw-r--r-- | src/ctx.h | 17 | ||||
-rw-r--r-- | src/diag.c | 10 | ||||
-rw-r--r-- | src/diag.h | 55 | ||||
-rw-r--r-- | src/dir.c | 4 | ||||
-rw-r--r-- | src/dir.h | 3 | ||||
-rw-r--r-- | src/dstring.c | 20 | ||||
-rw-r--r-- | src/dstring.h | 26 | ||||
-rw-r--r-- | src/eval.c | 56 | ||||
-rw-r--r-- | src/eval.h | 2 | ||||
-rw-r--r-- | src/exec.c | 6 | ||||
-rw-r--r-- | src/expr.c | 7 | ||||
-rw-r--r-- | src/expr.h | 27 | ||||
-rw-r--r-- | src/fsade.c | 26 | ||||
-rw-r--r-- | src/fsade.h | 6 | ||||
-rw-r--r-- | src/ioq.c | 11 | ||||
-rw-r--r-- | src/ioq.h | 2 | ||||
-rw-r--r-- | src/list.h | 1 | ||||
-rw-r--r-- | src/main.c | 5 | ||||
-rw-r--r-- | src/mtab.c | 6 | ||||
-rw-r--r-- | src/mtab.h | 2 | ||||
-rw-r--r-- | src/opt.c | 156 | ||||
-rw-r--r-- | src/parse.c | 526 | ||||
-rw-r--r-- | src/prelude.h | 402 | ||||
-rw-r--r-- | src/printf.c | 12 | ||||
-rw-r--r-- | src/pwcache.c | 5 | ||||
-rw-r--r-- | src/sanity.h | 19 | ||||
-rw-r--r-- | src/sighook.c | 295 | ||||
-rw-r--r-- | src/stat.c | 4 | ||||
-rw-r--r-- | src/stat.h | 5 | ||||
-rw-r--r-- | src/thread.c | 3 | ||||
-rw-r--r-- | src/thread.h | 9 | ||||
-rw-r--r-- | src/trie.c | 24 | ||||
-rw-r--r-- | src/trie.h | 1 | ||||
-rw-r--r-- | src/typo.c | 3 | ||||
-rw-r--r-- | src/version.c | 2 | ||||
-rw-r--r-- | src/xregex.c | 4 | ||||
-rw-r--r-- | src/xspawn.c | 84 | ||||
-rw-r--r-- | src/xspawn.h | 11 | ||||
-rw-r--r-- | src/xtime.c | 21 | ||||
-rw-r--r-- | src/xtime.h | 10 |
53 files changed, 1317 insertions, 1152 deletions
diff --git a/src/alloc.c b/src/alloc.c index ebaff38..223d737 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -1,11 +1,13 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "alloc.h" + +#include "bfs.h" #include "bit.h" #include "diag.h" #include "sanity.h" + #include <errno.h> #include <stdlib.h> #include <stdint.h> @@ -176,7 +178,7 @@ void arena_init(struct arena *arena, size_t align, size_t size) { } /** Allocate a new slab. */ -attr(cold) +_cold static int slab_alloc(struct arena *arena) { // Make the initial allocation size ~4K size_t size = 4096; diff --git a/src/alloc.h b/src/alloc.h index 095134a..7b97b8c 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -8,7 +8,8 @@ #ifndef BFS_ALLOC_H #define BFS_ALLOC_H -#include "prelude.h" +#include "bfs.h" + #include <errno.h> #include <stddef.h> #include <stdlib.h> @@ -115,7 +116,8 @@ static inline size_t flex_size(size_t align, size_t min, size_t offset, size_t s * @return * The allocated memory, or NULL on failure. */ -attr(malloc(free, 1), aligned_alloc(1, 2)) +_malloc(free, 1) +_aligned_alloc(1, 2) void *alloc(size_t align, size_t size); /** @@ -128,7 +130,8 @@ void *alloc(size_t align, size_t size); * @return * The allocated memory, or NULL on failure. */ -attr(malloc(free, 1), aligned_alloc(1, 2)) +_malloc(free, 1) +_aligned_alloc(1, 2) void *zalloc(size_t align, size_t size); /** Allocate memory for the given type. */ @@ -169,7 +172,8 @@ void *zalloc(size_t align, size_t size); * @return * The reallocated memory, or NULL on failure. */ -attr(nodiscard, aligned_alloc(2, 4)) +_aligned_alloc(2, 4) +_nodiscard void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size); /** Reallocate memory for an array. */ @@ -195,7 +199,7 @@ void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size); * for (count + 1) elements. On failure, errno will be non-zero, and * ptr will returned unchanged. */ -attr(nodiscard) +_nodiscard void *reserve(void *ptr, size_t align, size_t size, size_t count); /** @@ -253,7 +257,7 @@ void arena_free(struct arena *arena, void *ptr); /** * Allocate an object out of the arena. */ -attr(malloc(arena_free, 2)) +_malloc(arena_free, 2) void *arena_alloc(struct arena *arena); /** @@ -335,7 +339,7 @@ void varena_free(struct varena *varena, void *ptr, size_t count); * @return * The allocated struct, or NULL on failure. */ -attr(malloc(varena_free, 2)) +_malloc(varena_free, 2) void *varena_alloc(struct varena *varena, size_t count); /** @@ -352,7 +356,7 @@ void *varena_alloc(struct varena *varena, size_t count); * @return * The resized struct, or NULL on failure. */ -attr(nodiscard) +_nodiscard void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count); /** @@ -367,7 +371,7 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t * @return * The resized struct, or NULL on failure. */ -attr(nodiscard) +_nodiscard void *varena_grow(struct varena *varena, void *ptr, size_t *count); /** diff --git a/src/atomic.h b/src/atomic.h index ad5303b..2682c29 100644 --- a/src/atomic.h +++ b/src/atomic.h @@ -8,8 +8,8 @@ #ifndef BFS_ATOMIC_H #define BFS_ATOMIC_H -#include "prelude.h" -#include "sanity.h" +#include "bfs.h" + #include <stdatomic.h> /** @@ -87,7 +87,7 @@ /** * Shorthand for atomic_thread_fence(). */ -#if SANITIZE_THREAD +#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) @@ -1,21 +1,26 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "bar.h" + #include "alloc.h" #include "atomic.h" +#include "bfs.h" #include "bfstd.h" #include "bit.h" #include "dstring.h" #include "sighook.h" + #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdarg.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> #include <sys/ioctl.h> +#include <termios.h> +#include <unistd.h> struct bfs_bar { int fd; @@ -28,10 +33,16 @@ struct bfs_bar { /** Get the terminal size, if possible. */ static int bfs_bar_getsize(struct bfs_bar *bar) { -#ifdef TIOCGWINSZ +#if BFS_HAS_TCGETWINSIZE || defined(TIOCGWINSZ) struct winsize ws; - if (ioctl(bar->fd, TIOCGWINSZ, &ws) != 0) { - return -1; + +# if BFS_HAS_TCGETWINSIZE + int ret = tcgetwinsize(bar->fd, &ws); +# else + int ret = ioctl(bar->fd, TIOCGWINSZ, &ws); +# endif + if (ret != 0) { + return ret; } store(&bar->width, ws.ws_col, relaxed); @@ -72,8 +83,26 @@ static char *ass_itoa(char *str, unsigned int n) { return str + len; } +/** Reset the scrollable region and hide the bar. */ +static int bfs_bar_reset(struct bfs_bar *bar) { + return bfs_bar_puts(bar, + "\0337" // DECSC: Save cursor + "\033[r" // DECSTBM: Reset scrollable region + "\0338" // DECRC: Restore cursor + "\033[J" // ED: Erase display from cursor to end + ); +} + +/** Hide the bar if the terminal is shorter than this. */ +#define BFS_BAR_MIN_HEIGHT 3 + /** Update the size of the scrollable region. */ static int bfs_bar_resize(struct bfs_bar *bar) { + unsigned int height = load(&bar->height, relaxed); + if (height < BFS_BAR_MIN_HEIGHT) { + return bfs_bar_reset(bar); + } + static const char PREFIX[] = "\033D" // IND: Line feed, possibly scrolling "\033[1A" // CUU: Move cursor up 1 row @@ -87,10 +116,8 @@ static int bfs_bar_resize(struct bfs_bar *bar) { char esc_seq[sizeof(PREFIX) + ITOA_DIGITS + sizeof(SUFFIX)]; // DECSTBM takes the height as the second argument - unsigned int height = load(&bar->height, relaxed) - 1; - char *cur = stpcpy(esc_seq, PREFIX); - cur = ass_itoa(cur, height); + cur = ass_itoa(cur, height - 1); cur = stpcpy(cur, SUFFIX); return bfs_bar_write(bar, esc_seq, cur - esc_seq); @@ -105,16 +132,6 @@ static void bfs_bar_sigwinch(int sig, siginfo_t *info, void *arg) { } #endif -/** Reset the scrollable region and hide the bar. */ -static int bfs_bar_reset(struct bfs_bar *bar) { - return bfs_bar_puts(bar, - "\0337" // DECSC: Save cursor - "\033[r" // DECSTBM: Reset scrollable region - "\0338" // DECRC: Restore cursor - "\033[J" // ED: Erase display from cursor to end - ); -} - /** Signal handler for process-terminating signals. */ static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) { struct bfs_bar *bar = arg; @@ -122,7 +139,7 @@ static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) { } /** printf() to the status bar with a single write(). */ -attr(printf(2, 3)) +_printf(2, 3) static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { va_list args; va_start(args, format); @@ -183,6 +200,10 @@ unsigned int bfs_bar_width(const struct bfs_bar *bar) { int bfs_bar_update(struct bfs_bar *bar, const char *str) { unsigned int height = load(&bar->height, relaxed); + if (height < BFS_BAR_MIN_HEIGHT) { + return 0; + } + return bfs_bar_printf(bar, "\0337" // DECSC: Save cursor "\033[%u;0f" // HVP: Move cursor to row, column diff --git a/src/bfs.h b/src/bfs.h new file mode 100644 index 0000000..91c6540 --- /dev/null +++ b/src/bfs.h @@ -0,0 +1,214 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +/** + * Configuration and fundamental utilities. + */ + +#ifndef BFS_H +#define BFS_H + +// Standard versions + +/** Possible __STDC_VERSION__ values. */ +#define C95 199409L +#define C99 199901L +#define C11 201112L +#define C17 201710L +#define C23 202311L + +/** Possible _POSIX_C_SOURCE and _POSIX_<OPTION> values. */ +#define POSIX_1990 1 +#define POSIX_1992 2 +#define POSIX_1993 199309L +#define POSIX_1995 199506L +#define POSIX_2001 200112L +#define POSIX_2008 200809L +#define POSIX_2024 202405L + +// Build configuration + +#include "config.h" + +#ifndef BFS_COMMAND +# define BFS_COMMAND "bfs" +#endif +#ifndef BFS_HOMEPAGE +# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" +#endif + +// This is a symbol instead of a literal so we don't have to rebuild everything +// when the version number changes +extern const char bfs_version[]; + +extern const char bfs_confflags[]; +extern const char bfs_cc[]; +extern const char bfs_cppflags[]; +extern const char bfs_cflags[]; +extern const char bfs_ldflags[]; +extern const char bfs_ldlibs[]; + +// Get __GLIBC__ +#include <assert.h> + +// Fundamental utilities + +/** + * Get the length of an array. + */ +#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__])) + +/** + * False sharing/destructive interference/largest cache line size. + */ +#ifdef __GCC_DESTRUCTIVE_SIZE +# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE +#else +# define FALSE_SHARING_SIZE 64 +#endif + +/** + * True sharing/constructive interference/smallest cache line size. + */ +#ifdef __GCC_CONSTRUCTIVE_SIZE +# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE +#else +# define TRUE_SHARING_SIZE 64 +#endif + +/** + * Alignment specifier that avoids false sharing. + */ +#define cache_align alignas(FALSE_SHARING_SIZE) + +// Wrappers for attributes + +/** + * Silence warnings about switch/case fall-throughs. + */ +#if __has_attribute(fallthrough) +# define _fallthrough __attribute__((fallthrough)) +#else +# define _fallthrough ((void)0) +#endif + +/** + * Silence warnings about unused declarations. + */ +#if __has_attribute(unused) +# define _maybe_unused __attribute__((unused)) +#else +# define _maybe_unused +#endif + +/** + * Warn if a value is unused. + */ +#if __has_attribute(warn_unused_result) +# define _nodiscard __attribute__((warn_unused_result)) +#else +# define _nodiscard +#endif + +/** + * Hint to avoid inlining a function. + */ +#if __has_attribute(noinline) +# define _noinline __attribute__((noinline)) +#else +# define _noinline +#endif + +/** + * Marks a non-returning function. + */ +#if __STDC_VERSION__ >= C23 +# define _noreturn [[noreturn]] +#else +# define _noreturn _Noreturn +#endif + +/** + * Hint that a function is unlikely to be called. + */ +#if __has_attribute(cold) +# define _cold _noinline __attribute__((cold)) +#else +# define _cold _noinline +#endif + +/** + * Adds compiler warnings for bad printf()-style function calls, if supported. + */ +#if __has_attribute(format) +# define _printf(fmt, args) __attribute__((format(printf, fmt, args))) +#else +# define _printf(fmt, args) +#endif + +/** + * Annotates functions that potentially modify and return format strings. + */ +#if __has_attribute(format_arg) +# define _format_arg(arg) __attribute__((format_arg(arg))) +#else +# define _format_arg(arg) +#endif + +/** + * Annotates allocator-like functions. + */ +#if __has_attribute(malloc) +# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC +# define _malloc(...) _nodiscard __attribute__((malloc(__VA_ARGS__))) +# else +# define _malloc(...) _nodiscard __attribute__((malloc)) +# endif +#else +# define _malloc(...) _nodiscard +#endif + +/** + * Specifies that a function returns allocations with a given alignment. + */ +#if __has_attribute(alloc_align) +# define _alloc_align(param) __attribute__((alloc_align(param))) +#else +# define _alloc_align(param) +#endif + +/** + * Specifies that a function returns allocations with a given size. + */ +#if __has_attribute(alloc_size) +# define _alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) +#else +# define _alloc_size(...) +#endif + +/** + * Shorthand for _alloc_align() and _alloc_size(). + */ +#define _aligned_alloc(align, ...) _alloc_align(align) _alloc_size(__VA_ARGS__) + +/** + * Check if function multiversioning via GNU indirect functions (ifunc) is supported. + * + * Disabled on TSan due to https://github.com/google/sanitizers/issues/342. + */ +#ifndef BFS_USE_TARGET_CLONES +# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) && !__SANITIZE_THREAD__ +# define BFS_USE_TARGET_CLONES true +# endif +#endif + +/** + * Apply the target_clones attribute, if available. + */ +#if BFS_USE_TARGET_CLONES +# define _target_clones(...) __attribute__((target_clones(__VA_ARGS__))) +#else +# define _target_clones(...) +#endif + +#endif // BFS_H diff --git a/src/bfstd.c b/src/bfstd.c index 6d244ca..b29fb7b 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -1,13 +1,15 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "bfstd.h" + +#include "bfs.h" #include "bit.h" #include "diag.h" #include "sanity.h" #include "thread.h" #include "xregex.h" + #include <errno.h> #include <fcntl.h> #include <langinfo.h> @@ -27,13 +29,13 @@ #include <unistd.h> #include <wchar.h> -#if BFS_USE_SYS_SYSMACROS_H +#if __has_include(<sys/sysmacros.h>) # include <sys/sysmacros.h> -#elif BFS_USE_SYS_MKDEV_H +#elif __has_include(<sys/mkdev.h>) # include <sys/mkdev.h> #endif -#if BFS_USE_UTIL_H +#if __has_include(<util.h>) # include <util.h> #endif @@ -324,7 +326,7 @@ const char *xstrerror(int errnum) { // On FreeBSD with MemorySanitizer, duplocale() triggers // https://github.com/llvm/llvm-project/issues/65532 -#if BFS_HAS_STRERROR_L && !(__FreeBSD__ && SANITIZE_MEMORY) +#if BFS_HAS_STRERROR_L && !(__FreeBSD__ && __SANITIZE_MEMORY__) # if BFS_HAS_USELOCALE locale_t loc = uselocale((locale_t)0); # else @@ -715,14 +717,14 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * } long xsysconf(int name) { -#if __FreeBSD__ && SANITIZE_MEMORY +#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 +#if __FreeBSD__ && __SANITIZE_MEMORY__ __msan_scoped_enable_interceptor_checks(); #endif @@ -965,14 +967,14 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, en /** How much of this string is safe as a bare word? */ static size_t bare_len(const char *str, size_t len) { - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 + // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_02 size_t ret = strcspn(str, "|&;<>()$`\\\"' *?[#~=%!{}"); return ret < len ? ret : len; } /** How much of this string is safe to double-quote? */ static size_t quotable_len(const char *str, size_t len) { - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_02_03 size_t ret = strcspn(str, "`$\\\"!"); return ret < len ? ret : len; } diff --git a/src/bfstd.h b/src/bfstd.h index afb5282..557f253 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -8,8 +8,8 @@ #ifndef BFS_BFSTD_H #define BFS_BFSTD_H -#include "prelude.h" -#include "sanity.h" +#include "bfs.h" + #include <stddef.h> #include <ctype.h> @@ -18,7 +18,7 @@ * Work around https://github.com/llvm/llvm-project/issues/65532 by forcing a * function, not a macro, to be called. */ -#if __FreeBSD__ && SANITIZE_MEMORY +#if __FreeBSD__ && __SANITIZE_MEMORY__ # define BFS_INTERCEPT(fn) (fn) #else # define BFS_INTERCEPT(fn) fn @@ -447,6 +447,19 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * */ long xsysconf(int name); +/** + * Check for a POSIX option[1] at runtime. + * + * [1]: https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap02.html#tag_02_01_06 + * + * @param name + * The symbolic name of the POSIX option (e.g. SPAWN). + * @return + * The value of the option, either -1 or a date like 202405. + */ +#define sysoption(name) \ + (_POSIX_##name == 0 ? xsysconf(_SC_##name) : _POSIX_##name) + #include <wchar.h> /** @@ -18,9 +18,10 @@ * various helper functions to take fewer parameters. */ -#include "prelude.h" #include "bftw.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "diag.h" #include "dir.h" @@ -30,6 +31,7 @@ #include "mtab.h" #include "stat.h" #include "trie.h" + #include <errno.h> #include <fcntl.h> #include <stdlib.h> @@ -1281,8 +1283,8 @@ static int bftw_pin_parent(struct bftw_state *state, struct bftw_file *file) { int fd = parent->fd; if (fd < 0) { - bfs_static_assert((int)AT_FDCWD != -1); - return -1; + // Don't confuse failures with AT_FDCWD + return (int)AT_FDCWD == -1 ? -2 : -1; } bftw_cache_pin(&state->cache, parent); @@ -1431,7 +1433,7 @@ static bool bftw_must_stat(const struct bftw_state *state, size_t depth, enum bf if (!(bftw_stat_flags(state, depth) & BFS_STAT_NOFOLLOW)) { return true; } - fallthru; + _fallthrough; default: #if __linux__ @@ -10,6 +10,7 @@ #include "dir.h" #include "stat.h" + #include <stddef.h> /** @@ -8,11 +8,12 @@ #ifndef BFS_BIT_H #define BFS_BIT_H -#include "prelude.h" +#include "bfs.h" + #include <limits.h> #include <stdint.h> -#if BFS_HAS_STDBIT_H +#if __has_include(<stdbit.h>) # include <stdbit.h> #endif diff --git a/src/color.c b/src/color.c index 701a89c..7f653b0 100644 --- a/src/color.c +++ b/src/color.c @@ -1,9 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "color.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "bftw.h" #include "diag.h" @@ -13,6 +14,7 @@ #include "fsade.h" #include "stat.h" #include "trie.h" + #include <errno.h> #include <fcntl.h> #include <stdarg.h> @@ -214,57 +216,26 @@ static void ext_tolower(char *ext, size_t len) { } } -/** - * The "smart case" algorithm. - * - * @param ext - * The current extension being added. - * @param prev - * The previous case-sensitive match, if any, for the same extension. - * @param iprev - * The previous case-insensitive match, if any, for the same extension. - * @return - * Whether this extension should become case-sensitive. - */ -static bool ext_case_sensitive(struct ext_color *ext, struct ext_color *prev, struct ext_color *iprev) { - // This is the first case-insensitive occurrence of this extension, e.g. - // - // *.gz=01;31:*.tar.gz=01;33 - if (!iprev) { - bfs_assert(!prev); - return false; - } - - // If the last version of this extension is already case-sensitive, - // this one should be too, e.g. - // - // *.tar.gz=01;31:*.TAR.GZ=01;32:*.TAR.GZ=01;33 - if (iprev->case_sensitive) { - return true; - } - - // The case matches the last occurrence exactly, e.g. - // - // *.tar.gz=01;31:*.tar.gz=01;33 - if (iprev == prev) { - return false; +/** Insert an extension into a trie. */ +static int insert_ext(struct trie *trie, struct ext_color *ext) { + // A later *.x should override any earlier *.x, *.y.x, etc. + struct trie_leaf *leaf; + while ((leaf = trie_find_postfix(trie, ext->ext))) { + trie_remove(trie, leaf); } - // Different case, but same value, e.g. - // - // *.tar.gz=01;31:*.TAR.GZ=01;31 - if (esc_eq(iprev->esc, ext->esc->seq, ext->esc->len)) { - return false; + size_t len = ext->len + 1; + leaf = trie_insert_mem(trie, ext->ext, len); + if (!leaf) { + return -1; } - // Different case, different value, e.g. - // - // *.tar.gz=01;31:*.TAR.GZ=01;33 - return true; + leaf->value = ext; + return 0; } /** Set the color for an extension. */ -static int set_ext(struct colors *colors, char *key, char *value) { +static int set_ext(struct colors *colors, dchar *key, dchar *value) { size_t len = dstrlen(key); struct ext_color *ext = varena_alloc(&colors->ext_arena, len + 1); if (!ext) { @@ -279,46 +250,20 @@ static int set_ext(struct colors *colors, char *key, char *value) { goto fail; } - key = memcpy(ext->ext, key, len + 1); + memcpy(ext->ext, key, len + 1); // Reverse the extension (`*.y.x` -> `x.y.*`) so we can use trie_find_prefix() - ext_reverse(key, len); - - // Find any pre-existing exact match - struct ext_color *prev = NULL; - struct trie_leaf *leaf = trie_find_str(&colors->ext_trie, key); - if (leaf) { - prev = leaf->value; - trie_remove(&colors->ext_trie, leaf); - } - - // A later *.x should override any earlier *.x, *.y.x, etc. - while ((leaf = trie_find_postfix(&colors->ext_trie, key))) { - trie_remove(&colors->ext_trie, leaf); - } + ext_reverse(ext->ext, len); // Insert the extension into the case-sensitive trie - leaf = trie_insert_str(&colors->ext_trie, key); - if (!leaf) { + if (insert_ext(&colors->ext_trie, ext) != 0) { goto fail; } - leaf->value = ext; - // "Smart case": if the same extension is given with two different - // capitalizations (e.g. `*.y.x=31:*.Y.Z=32:`), make it case-sensitive - ext_tolower(key, len); - leaf = trie_insert_str(&colors->iext_trie, key); - if (!leaf) { - goto fail; + if (colors->ext_len < len) { + colors->ext_len = len; } - struct ext_color *iprev = leaf->value; - if (ext_case_sensitive(ext, prev, iprev)) { - iprev->case_sensitive = true; - ext->case_sensitive = true; - } - leaf->value = ext; - return 0; fail: @@ -329,32 +274,83 @@ fail: return -1; } -/** Rebuild the case-insensitive trie after all extensions have been parsed. */ -static int build_iext_trie(struct colors *colors) { - trie_clear(&colors->iext_trie); +/** + * The "smart case" algorithm. + * + * @param ext + * The current extension being added. + * @param iext + * The previous case-insensitive match, if any, for the same extension. + * @return + * Whether this extension should become case-sensitive. + */ +static bool ext_case_sensitive(struct ext_color *ext, struct ext_color *iext) { + // This is the first case-insensitive occurrence of this extension, e.g. + // + // *.gz=01;31:*.tar.gz=01;33 + if (!iext) { + return false; + } + // If the last version of this extension is already case-sensitive, + // this one should be too, e.g. + // + // *.tar.gz=01;31:*.TAR.GZ=01;32:*.TAR.GZ=01;33 + if (iext->case_sensitive) { + return true; + } + + // Different case, but same value, e.g. + // + // *.tar.gz=01;31:*.TAR.GZ=01;31 + if (esc_eq(iext->esc, ext->esc->seq, ext->esc->len)) { + return false; + } + + // Different case, different value, e.g. + // + // *.tar.gz=01;31:*.TAR.GZ=01;33 + return true; +} + +/** Build the case-insensitive trie, after all extensions have been parsed. */ +static int build_iext_trie(struct colors *colors) { + // Find which extensions should be case-sensitive for_trie (leaf, &colors->ext_trie) { - size_t len = leaf->length - 1; - if (colors->ext_len < len) { - colors->ext_len = len; + struct ext_color *ext = leaf->value; + + // "Smart case": if the same extension is given with two different + // capitalizations (e.g. `*.y.x=31:*.Y.Z=32:`), make it case-sensitive + ext_tolower(ext->ext, ext->len); + + size_t len = ext->len + 1; + struct trie_leaf *ileaf = trie_insert_mem(&colors->iext_trie, ext->ext, len); + if (!ileaf) { + return -1; + } + + struct ext_color *iext = ileaf->value; + if (ext_case_sensitive(ext, iext)) { + ext->case_sensitive = true; + iext->case_sensitive = true; } + ileaf->value = ext; + } + + // Rebuild the trie with only the case-insensitive ones + trie_clear(&colors->iext_trie); + + for_trie (leaf, &colors->ext_trie) { struct ext_color *ext = leaf->value; if (ext->case_sensitive) { continue; } - // set_ext() already reversed and lowercased the extension - struct trie_leaf *ileaf; - while ((ileaf = trie_find_postfix(&colors->iext_trie, ext->ext))) { - trie_remove(&colors->iext_trie, ileaf); - } - - ileaf = trie_insert_str(&colors->iext_trie, ext->ext); - if (!ileaf) { + // We already lowercased the extension above + if (insert_ext(&colors->iext_trie, ext) != 0) { return -1; } - ileaf->value = ext; } return 0; @@ -1125,7 +1121,7 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { } /** Format some colored output to the buffer. */ -attr(printf(2, 3)) +_printf(2, 3) static int cbuff(CFILE *cfile, const char *format, ...); /** Dump a parsed expression tree, for debugging. */ @@ -1142,14 +1138,20 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i return -1; } - if (bfs_expr_is_parent(expr)) { - if (cbuff(cfile, "${red}%pq${rs}", expr->argv[0]) < 0) { - return -1; - } - } else { - if (cbuff(cfile, "${blu}%pq${rs}", expr->argv[0]) < 0) { - return -1; - } + int ret; + switch (expr->kind) { + case BFS_FLAG: + ret = cbuff(cfile, "${cyn}%pq${rs}", expr->argv[0]); + break; + case BFS_OPERATOR: + ret = cbuff(cfile, "${red}%pq${rs}", expr->argv[0]); + break; + default: + ret = cbuff(cfile, "${blu}%pq${rs}", expr->argv[0]); + break; + } + if (ret < 0) { + return -1; } for (size_t i = 1; i < expr->argc; ++i) { @@ -1194,7 +1196,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i return 0; } -attr(printf(2, 0)) +_printf(2, 0) static int cvbuff(CFILE *cfile, const char *format, va_list args) { const struct colors *colors = cfile->colors; diff --git a/src/color.h b/src/color.h index 8582eb3..38f5ad7 100644 --- a/src/color.h +++ b/src/color.h @@ -8,8 +8,9 @@ #ifndef BFS_COLOR_H #define BFS_COLOR_H -#include "prelude.h" +#include "bfs.h" #include "dstring.h" + #include <stdio.h> /** @@ -100,13 +101,13 @@ int cfclose(CFILE *cfile); * @return * 0 on success, -1 on failure. */ -attr(printf(2, 3)) +_printf(2, 3) int cfprintf(CFILE *cfile, const char *format, ...); /** * cfprintf() variant that takes a va_list. */ -attr(printf(2, 0)) +_printf(2, 0) int cvfprintf(CFILE *cfile, const char *format, va_list args); /** @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "ctx.h" + #include "alloc.h" #include "bfstd.h" #include "color.h" @@ -13,12 +14,14 @@ #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 <sys/stat.h> +#include <time.h> #include <unistd.h> /** Get the initial value for ctx->threads (-j). */ @@ -52,6 +55,9 @@ struct bfs_ctx *bfs_ctx_new(void) { trie_init(&ctx->files); + ctx->umask = umask(0); + umask(ctx->umask); + if (getrlimit(RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { goto fail; } @@ -68,7 +74,7 @@ struct bfs_ctx *bfs_ctx_new(void) { goto fail; } - if (xgettime(&ctx->now) != 0) { + if (clock_gettime(CLOCK_REALTIME, &ctx->now) != 0) { goto fail; } @@ -8,14 +8,15 @@ #ifndef BFS_CTX_H #define BFS_CTX_H -#include "prelude.h" #include "alloc.h" #include "bftw.h" #include "diag.h" #include "expr.h" #include "trie.h" + #include <stddef.h> #include <sys/resource.h> +#include <sys/types.h> #include <time.h> struct CFILE; @@ -67,11 +68,18 @@ struct bfs_ctx { bool status; /** Whether to only return unique files (-unique). */ bool unique; - /** Whether to print warnings (-warn/-nowarn). */ - bool warn; /** Whether to only handle paths with xargs-safe characters (-X). */ bool xargs_safe; + /** Whether bfs was run interactively. */ + bool interactive; + /** Whether to print warnings (-warn/-nowarn). */ + bool warn; + /** Whether to report errors (-noerror). */ + bool ignore_errors; + /** Whether any dangerous actions (-delete/-exec) are present. */ + bool dangerous; + /** Color data. */ struct colors *colors; /** The error that occurred parsing the color table, if any. */ @@ -98,6 +106,9 @@ struct bfs_ctx { /** The number of files owned by the context. */ int nfiles; + /** The current file creation mask. */ + mode_t umask; + /** The initial RLIMIT_NOFILE limits. */ struct rlimit orig_nofile; /** The current RLIMIT_NOFILE limits. */ @@ -1,21 +1,22 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "diag.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "color.h" #include "ctx.h" #include "dstring.h" #include "expr.h" -#include <errno.h> + #include <stdarg.h> #include <stdio.h> #include <stdlib.h> /** bfs_diagf() implementation. */ -attr(printf(2, 0)) +_printf(2, 0) static void bfs_vdiagf(const struct bfs_loc *loc, const char *format, va_list args) { fprintf(stderr, "%s: %s@%s:%d: ", xgetprogname(), loc->func, loc->file, loc->line); vfprintf(stderr, format, args); @@ -29,7 +30,8 @@ void bfs_diagf(const struct bfs_loc *loc, const char *format, ...) { va_end(args); } -noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { +_noreturn +void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { va_list args; va_start(args, format); bfs_vdiagf(loc, format, args); @@ -8,19 +8,10 @@ #ifndef BFS_DIAG_H #define BFS_DIAG_H -#include "prelude.h" +#include "bfs.h" #include "bfstd.h" -#include <stdarg.h> -/** - * static_assert() with an optional second argument. - */ -#if __STDC_VERSION__ >= C23 -# define bfs_static_assert static_assert -#else -# define bfs_static_assert(...) bfs_static_assert_(__VA_ARGS__, #__VA_ARGS__, ) -# define bfs_static_assert_(expr, msg, ...) _Static_assert(expr, msg) -#endif +#include <stdarg.h> /** * A source code location. @@ -47,7 +38,7 @@ struct bfs_loc { * * bfs: func@src/file.c:0: Message */ -attr(printf(2, 3)) +_printf(2, 3) void bfs_diagf(const struct bfs_loc *loc, const char *format, ...); /** @@ -67,8 +58,10 @@ void bfs_diagf(const struct bfs_loc *loc, const char *format, ...); /** * Print a message to standard error and abort. */ -attr(cold, printf(2, 3)) -noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); +_cold +_printf(2, 3) +_noreturn +void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); /** * Unconditional abort with a message. @@ -166,13 +159,14 @@ const char *debug_flag_name(enum debug_flags flag); /** * Like perror(), but decorated like bfs_error(). */ -attr(cold) +_cold void bfs_perror(const struct bfs_ctx *ctx, const char *str); /** * Shorthand for printing error messages. */ -attr(cold, printf(2, 3)) +_cold +_printf(2, 3) void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -180,7 +174,8 @@ void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a warning was printed. */ -attr(cold, printf(2, 3)) +_cold +_printf(2, 3) bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -188,67 +183,71 @@ bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a debug message was printed. */ -attr(cold, printf(3, 4)) +_cold +_printf(3, 4) bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, ...); /** * bfs_error() variant that takes a va_list. */ -attr(cold, printf(2, 0)) +_cold +_printf(2, 0) void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_warning() variant that takes a va_list. */ -attr(cold, printf(2, 0)) +_cold +_printf(2, 0) bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_debug() variant that takes a va_list. */ -attr(cold, printf(3, 0)) +_cold +_printf(3, 0) bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args); /** * Print the error message prefix. */ -attr(cold) +_cold void bfs_error_prefix(const struct bfs_ctx *ctx); /** * Print the warning message prefix. */ -attr(cold) +_cold bool bfs_warning_prefix(const struct bfs_ctx *ctx); /** * Print the debug message prefix. */ -attr(cold) +_cold bool bfs_debug_prefix(const struct bfs_ctx *ctx, enum debug_flags flag); /** * Highlight parts of the command line in an error message. */ -attr(cold) +_cold void bfs_argv_error(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in an error message. */ -attr(cold) +_cold void bfs_expr_error(const struct bfs_ctx *ctx, const struct bfs_expr *expr); /** * Highlight parts of the command line in a warning message. */ -attr(cold) +_cold bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in a warning message. */ -attr(cold) +_cold bool bfs_expr_warning(const struct bfs_ctx *ctx, const struct bfs_expr *expr); #endif // BFS_DIAG_H @@ -1,13 +1,15 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "dir.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "diag.h" #include "sanity.h" #include "trie.h" + #include <dirent.h> #include <errno.h> #include <fcntl.h> @@ -8,7 +8,8 @@ #ifndef BFS_DIR_H #define BFS_DIR_H -#include "prelude.h" +#include "bfs.h" + #include <sys/types.h> /** diff --git a/src/dstring.c b/src/dstring.c index 86ab646..af499fd 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -1,11 +1,12 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "dstring.h" + #include "alloc.h" #include "bit.h" #include "diag.h" + #include <stdarg.h> #include <stddef.h> #include <stdint.h> @@ -277,3 +278,20 @@ void dstrfree(dchar *dstr) { free(dstrheader(dstr)); } } + +dchar *dstrepeat(const char *str, size_t n) { + size_t len = strlen(str); + dchar *ret = dstralloc(n * len); + if (!ret) { + return NULL; + } + + for (size_t i = 0; i < n; ++i) { + if (dstrxcat(&ret, str, len) < 0) { + dstrfree(ret); + return NULL; + } + } + + return ret; +} diff --git a/src/dstring.h b/src/dstring.h index 14e1d3e..79458e4 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -8,8 +8,9 @@ #ifndef BFS_DSTRING_H #define BFS_DSTRING_H -#include "prelude.h" +#include "bfs.h" #include "bfstd.h" + #include <stdarg.h> #include <stddef.h> @@ -41,7 +42,7 @@ void dstrfree(dchar *dstr); * @param cap * The initial capacity of the string. */ -attr(malloc(dstrfree, 1)) +_malloc(dstrfree, 1) dchar *dstralloc(size_t cap); /** @@ -50,7 +51,7 @@ dchar *dstralloc(size_t cap); * @param str * The NUL-terminated string to copy. */ -attr(malloc(dstrfree, 1)) +_malloc(dstrfree, 1) dchar *dstrdup(const char *str); /** @@ -61,7 +62,7 @@ dchar *dstrdup(const char *str); * @param n * The maximum number of characters to copy from str. */ -attr(malloc(dstrfree, 1)) +_malloc(dstrfree, 1) dchar *dstrndup(const char *str, size_t n); /** @@ -70,7 +71,7 @@ dchar *dstrndup(const char *str, size_t n); * @param dstr * The dynamic string to copy. */ -attr(malloc(dstrfree, 1)) +_malloc(dstrfree, 1) dchar *dstrddup(const dchar *dstr); /** @@ -81,7 +82,7 @@ dchar *dstrddup(const dchar *dstr); * @param len * The length of the string, which may include internal NUL bytes. */ -attr(malloc(dstrfree, 1)) +_malloc(dstrfree, 1) dchar *dstrxdup(const char *str, size_t len); /** @@ -243,7 +244,7 @@ int dstrxcpy(dchar **dest, const char *str, size_t len); * @return * The created string, or NULL on failure. */ -attr(printf(1, 2)) +_printf(1, 2) dchar *dstrprintf(const char *format, ...); /** @@ -256,7 +257,7 @@ dchar *dstrprintf(const char *format, ...); * @return * The created string, or NULL on failure. */ -attr(printf(1, 0)) +_printf(1, 0) dchar *dstrvprintf(const char *format, va_list args); /** @@ -271,7 +272,7 @@ dchar *dstrvprintf(const char *format, va_list args); * @return * 0 on success, -1 on failure. */ -attr(printf(2, 3)) +_printf(2, 3) int dstrcatf(dchar **str, const char *format, ...); /** @@ -286,7 +287,7 @@ int dstrcatf(dchar **str, const char *format, ...); * @return * 0 on success, -1 on failure. */ -attr(printf(2, 0)) +_printf(2, 0) int dstrvcatf(dchar **str, const char *format, va_list args); /** @@ -319,4 +320,9 @@ int dstrescat(dchar **dest, const char *str, enum wesc_flags flags); */ int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags); +/** + * Repeat a string n times. + */ +dchar *dstrepeat(const char *str, size_t n); + #endif // BFS_DSTRING_H @@ -5,10 +5,11 @@ * Implementation of all the primary expressions. */ -#include "prelude.h" #include "eval.h" + #include "atomic.h" #include "bar.h" +#include "bfs.h" #include "bfstd.h" #include "bftw.h" #include "color.h" @@ -27,11 +28,13 @@ #include "stat.h" #include "trie.h" #include "xregex.h" + #include <errno.h> #include <fcntl.h> #include <fnmatch.h> #include <grp.h> #include <pwd.h> +#include <signal.h> #include <stdarg.h> #include <stdint.h> #include <stdio.h> @@ -53,6 +56,8 @@ struct bfs_eval { enum bftw_action action; /** The bfs_eval() return value. */ int *ret; + /** The number of errors that have occurred. */ + size_t *nerrors; /** Whether to quit immediately. */ bool quit; }; @@ -60,12 +65,18 @@ struct bfs_eval { /** * Print an error message. */ -attr(printf(2, 3)) +_printf(2, 3) static void eval_error(struct bfs_eval *state, const char *format, ...) { + const struct bfs_ctx *ctx = state->ctx; + + ++*state->nerrors; + if (ctx->ignore_errors) { + return; + } + // By POSIX, any errors should be accompanied by a non-zero exit status *state->ret = EXIT_FAILURE; - const struct bfs_ctx *ctx = state->ctx; CFILE *cerr = ctx->cerr; bfs_error(ctx, "%pP: ", state->ftwbuf); @@ -269,10 +280,10 @@ bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) { switch (expr->time_unit) { case BFS_DAYS: diff /= 60 * 24; - fallthru; + _fallthrough; case BFS_MINUTES: diff /= 60; - fallthru; + _fallthrough; case BFS_SECONDS: break; } @@ -1014,25 +1025,23 @@ bool eval_xtype(const struct bfs_expr *expr, struct bfs_eval *state) { } } -#if _POSIX_MONOTONIC_CLOCK > 0 -# define BFS_CLOCK CLOCK_MONOTONIC -#elif _POSIX_TIMERS > 0 -# define BFS_CLOCK CLOCK_REALTIME -#endif - /** - * Call clock_gettime(), if available. + * clock_gettime() wrapper. */ static int eval_gettime(struct bfs_eval *state, struct timespec *ts) { -#ifdef BFS_CLOCK - int ret = clock_gettime(BFS_CLOCK, ts); + clockid_t clock = CLOCK_REALTIME; + +#if defined(_POSIX_MONOTONIC_CLOCK) && _POSIX_MONOTONIC_CLOCK >= 0 + if (sysoption(MONOTONIC_CLOCK) > 0) { + clock = CLOCK_MONOTONIC; + } +#endif + + int ret = clock_gettime(clock, ts); if (ret != 0) { bfs_warning(state->ctx, "%pP: clock_gettime(): %s.\n", state->ftwbuf, errstr()); } return ret; -#else - return -1; -#endif } /** @@ -1382,8 +1391,6 @@ 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; @@ -1393,6 +1400,8 @@ struct callback_args { /** The set of seen files. */ struct trie *seen; + /** The number of errors that have occurred. */ + size_t nerrors; /** Eventual return value from bfs_eval(). */ int ret; }; @@ -1411,6 +1420,7 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) { state.ctx = ctx; state.action = BFTW_CONTINUE; state.ret = &args->ret; + state.nerrors = &args->nerrors; state.quit = false; // Check whether SIGINFO was delivered and show/hide the bar @@ -1672,7 +1682,7 @@ int bfs_eval(struct bfs_ctx *ctx) { #else int siginfo = SIGUSR1; #endif - args.info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE); + struct sighook *info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE); struct trie seen; if (ctx->unique) { @@ -1741,8 +1751,12 @@ int bfs_eval(struct bfs_ctx *ctx) { trie_destroy(&seen); } - sigunhook(args.info_hook); + sigunhook(info_hook); bfs_bar_hide(args.bar); + if (ctx->ignore_errors && args.nerrors > 0) { + bfs_warning(ctx, "Suppressed errors: %zu\n", args.nerrors); + } + return args.ret; } @@ -9,8 +9,6 @@ #ifndef BFS_EVAL_H #define BFS_EVAL_H -#include "prelude.h" - struct bfs_ctx; struct bfs_expr; @@ -1,9 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "exec.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "bftw.h" #include "color.h" @@ -11,6 +12,7 @@ #include "diag.h" #include "dstring.h" #include "xspawn.h" + #include <errno.h> #include <fcntl.h> #include <stdarg.h> @@ -22,7 +24,7 @@ #include <unistd.h> /** Print some debugging info. */ -attr(printf(2, 3)) +_printf(2, 3) static void bfs_exec_debug(const struct bfs_exec *execbuf, const char *format, ...) { const struct bfs_ctx *ctx = execbuf->ctx; @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "expr.h" + #include "alloc.h" #include "ctx.h" #include "diag.h" @@ -10,9 +11,12 @@ #include "list.h" #include "printf.h" #include "xregex.h" + #include <string.h> -struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t argc, char **argv) { +struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t argc, char **argv, enum bfs_kind kind) { + bfs_assert(kind != BFS_PATH); + struct bfs_expr *expr = arena_alloc(&ctx->expr_arena); if (!expr) { return NULL; @@ -22,6 +26,7 @@ struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t expr->eval_fn = eval_fn; expr->argc = argc; expr->argv = argv; + expr->kind = kind; expr->probability = 0.5; SLIST_PREPEND(&ctx->expr_list, expr, freelist); @@ -8,14 +8,35 @@ #ifndef BFS_EXPR_H #define BFS_EXPR_H -#include "prelude.h" #include "color.h" #include "eval.h" #include "stat.h" + #include <sys/types.h> #include <time.h> /** + * Argument/token/expression kinds. + */ +enum bfs_kind { + /** A flag (-H, -L, etc.). */ + BFS_FLAG, + + /** A root path. */ + BFS_PATH, + + /** An option (-follow, -mindepth, etc.). */ + BFS_OPTION, + /** A test (-name, -size, etc.). */ + BFS_TEST, + /** An action (-print, -exec, etc.). */ + BFS_ACTION, + + /** An operator (-and, -or, etc.). */ + BFS_OPERATOR, +}; + +/** * Integer comparison modes. */ enum bfs_int_cmp { @@ -97,6 +118,8 @@ struct bfs_expr { size_t argc; /** The command line arguments comprising this expression. */ char **argv; + /** The kind of expression this is. */ + enum bfs_kind kind; /** The number of files this expression keeps open between evaluations. */ int persistent_fds; @@ -207,7 +230,7 @@ struct bfs_ctx; /** * Create a new expression. */ -struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval, size_t argc, char **argv); +struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval, size_t argc, char **argv, enum bfs_kind kind); /** * @return Whether this type of expression has children. diff --git a/src/fsade.c b/src/fsade.c index 7310141..30b3018 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -1,14 +1,16 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "fsade.h" + #include "atomic.h" +#include "bfs.h" #include "bfstd.h" #include "bftw.h" #include "dir.h" #include "dstring.h" #include "sanity.h" + #include <errno.h> #include <fcntl.h> #include <stddef.h> @@ -26,17 +28,19 @@ # include <selinux/selinux.h> #endif -#if BFS_USE_SYS_EXTATTR_H +#if __has_include(<sys/extattr.h>) # include <sys/extattr.h> -#elif BFS_USE_SYS_XATTR_H +# define BFS_USE_EXTATTR true +#elif __has_include(<sys/xattr.h>) # include <sys/xattr.h> +# define BFS_USE_XATTR true #endif /** * Many of the APIs used here don't have *at() variants, but we can try to * emulate something similar if /proc/self/fd is available. */ -attr(maybe_unused) +_maybe_unused static const char *fake_at(const struct BFTW *ftwbuf) { static atomic int proc_works = -1; @@ -70,7 +74,7 @@ fail: return ftwbuf->path; } -attr(maybe_unused) +_maybe_unused static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { dstrfree((dchar *)path); @@ -80,7 +84,7 @@ static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { /** * Check if an error was caused by the absence of support or data for a feature. */ -attr(maybe_unused) +_maybe_unused static bool is_absence_error(int error) { // If the OS doesn't support the feature, it's obviously not enabled for // any files @@ -160,7 +164,7 @@ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { } /** Unified interface for acl_get_tag_type(). */ -attr(maybe_unused) +_maybe_unused static int bfs_acl_tag_type(acl_entry_t entry, acl_tag_t *tag) { #if BFS_HAS_ACL_GET_TAG_TYPE return acl_get_tag_type(entry, tag); @@ -344,7 +348,7 @@ int bfs_check_capabilities(const struct BFTW *ftwbuf) { #if BFS_CAN_CHECK_XATTRS -#if BFS_USE_SYS_EXTATTR_H +#if BFS_USE_EXTATTR /** Wrapper for extattr_list_{file,link}. */ static ssize_t bfs_extattr_list(const char *path, enum bfs_type type, int namespace) { @@ -390,13 +394,13 @@ static ssize_t bfs_extattr_get(const char *path, enum bfs_type type, int namespa #endif } -#endif // BFS_USE_SYS_EXTATTR_H +#endif // BFS_USE_EXTATTR int bfs_check_xattrs(const struct BFTW *ftwbuf) { const char *path = fake_at(ftwbuf); ssize_t len; -#if BFS_USE_SYS_EXTATTR_H +#if BFS_USE_EXTATTR len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM); if (len <= 0) { len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_USER); @@ -432,7 +436,7 @@ int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { const char *path = fake_at(ftwbuf); ssize_t len; -#if BFS_USE_SYS_EXTATTR_H +#if BFS_USE_EXTATTR len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM, name); if (len < 0) { len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_USER, name); diff --git a/src/fsade.h b/src/fsade.h index 4465017..77cf82a 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -9,7 +9,7 @@ #ifndef BFS_FSADE_H #define BFS_FSADE_H -#include "prelude.h" +#include "bfs.h" #define BFS_CAN_CHECK_ACL (BFS_HAS_ACL_GET_FILE || BFS_HAS_ACL_TRIVIAL) @@ -17,7 +17,9 @@ #define BFS_CAN_CHECK_CONTEXT BFS_WITH_LIBSELINUX -#define BFS_CAN_CHECK_XATTRS (BFS_USE_SYS_EXTATTR_H || BFS_USE_SYS_XATTR_H) +#if __has_include(<sys/extattr.h>) || __has_include(<sys/xattr.h>) +# define BFS_CAN_CHECK_XATTRS true +#endif struct BFTW; @@ -118,16 +118,18 @@ * [1]: https://arxiv.org/abs/2201.02179 */ -#include "prelude.h" #include "ioq.h" + #include "alloc.h" #include "atomic.h" +#include "bfs.h" #include "bfstd.h" #include "bit.h" #include "diag.h" #include "dir.h" #include "stat.h" #include "thread.h" + #include <errno.h> #include <fcntl.h> #include <pthread.h> @@ -180,8 +182,7 @@ typedef atomic uintptr_t ioq_slot; /** Amount to add for an additional skip. */ #define IOQ_SKIP_ONE (~IOQ_BLOCKED) -// Need room for two flag bits -bfs_static_assert(alignof(struct ioq_ent) >= (1 << 2)); +static_assert(alignof(struct ioq_ent) >= (1 << 2), "struct ioq_ent is underaligned"); /** * An MPMC queue of I/O commands. @@ -263,7 +264,7 @@ static struct ioq_monitor *ioq_slot_monitor(struct ioqq *ioqq, ioq_slot *slot) { } /** Atomically wait for a slot to change. */ -attr(noinline) +_noinline static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) { struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); mutex_lock(&monitor->mutex); @@ -293,7 +294,7 @@ done: } /** Wake up any threads waiting on a slot. */ -attr(noinline) +_noinline static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); @@ -8,9 +8,9 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H -#include "prelude.h" #include "dir.h" #include "stat.h" + #include <stddef.h> /** @@ -83,6 +83,7 @@ #define BFS_LIST_H #include "diag.h" + #include <stddef.h> #include <string.h> @@ -20,13 +20,14 @@ * - bftw.[ch] (an extended version of nftw(3)) * * - Utilities: + * - prelude.h (feature test macros; automatically included) * - alloc.[ch] (memory allocation) * - atomic.h (atomic operations) * - bar.[ch] (a terminal status bar) * - bit.h (bit manipulation) + * - bfs.h (configuration and fundamental utilities) * - bfstd.[ch] (standard library wrappers/polyfills) * - color.[ch] (for pretty terminal colors) - * - prelude.h (configuration and feature/platform detection) * - diag.[ch] (formats diagnostic messages) * - dir.[ch] (a directory API facade) * - dstring.[ch] (a dynamic string library) @@ -47,12 +48,12 @@ * - xtime.[ch] (date/time handling utilities) */ -#include "prelude.h" #include "bfstd.h" #include "ctx.h" #include "diag.h" #include "eval.h" #include "parse.h" + #include <errno.h> #include <fcntl.h> #include <locale.h> @@ -1,12 +1,14 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "mtab.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "stat.h" #include "trie.h" + #include <errno.h> #include <fcntl.h> #include <stdlib.h> @@ -65,7 +67,7 @@ struct bfs_mtab { /** * Add an entry to the mount table. */ -attr(maybe_unused) +_maybe_unused static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { size_t path_size = strlen(path) + 1; size_t type_size = strlen(type) + 1; @@ -8,8 +8,6 @@ #ifndef BFS_MTAB_H #define BFS_MTAB_H -#include "prelude.h" - struct bfs_stat; /** @@ -25,8 +25,10 @@ * effects are reachable at all, skipping the traversal if not. */ -#include "prelude.h" #include "opt.h" + +#include "bfs.h" +#include "bfstd.h" #include "bftw.h" #include "bit.h" #include "color.h" @@ -38,6 +40,8 @@ #include "expr.h" #include "list.h" #include "pwcache.h" +#include "xspawn.h" + #include <errno.h> #include <limits.h> #include <stdarg.h> @@ -173,11 +177,17 @@ static void constrain_min(struct df_range *range, long long value) { range->min = max_value(range->min, value); } -/** Contrain the maximum of a range. */ +/** Constrain the maximum of a range. */ static void constrain_max(struct df_range *range, long long value) { range->max = min_value(range->max, value); } +/** Constrain a range to a single value. */ +static void constrain_range(struct df_range *range, long long value) { + constrain_min(range, value); + constrain_max(range, value); +} + /** Remove a single value from a range. */ static void range_remove(struct df_range *range, long long value) { if (range->min == value) { @@ -364,7 +374,7 @@ struct bfs_opt { }; /** Log an optimization. */ -attr(printf(2, 3)) +_printf(2, 3) static bool opt_debug(struct bfs_opt *opt, const char *format, ...) { if (bfs_debug_prefix(opt->ctx, DEBUG_OPT)) { for (int i = 0; i < opt->depth; ++i) { @@ -382,7 +392,7 @@ static bool opt_debug(struct bfs_opt *opt, const char *format, ...) { } /** Log a recursive call. */ -attr(printf(2, 3)) +_printf(2, 3) static bool opt_enter(struct bfs_opt *opt, const char *format, ...) { int depth = opt->depth; if (depth > 0) { @@ -402,7 +412,7 @@ static bool opt_enter(struct bfs_opt *opt, const char *format, ...) { } /** Log a recursive return. */ -attr(printf(2, 3)) +_printf(2, 3) static bool opt_leave(struct bfs_opt *opt, const char *format, ...) { bool debug = false; int depth = opt->depth; @@ -426,7 +436,7 @@ static bool opt_leave(struct bfs_opt *opt, const char *format, ...) { } /** Log a shallow visit. */ -attr(printf(2, 3)) +_printf(2, 3) static bool opt_visit(struct bfs_opt *opt, const char *format, ...) { int depth = opt->depth; if (depth > 0) { @@ -446,7 +456,7 @@ static bool opt_visit(struct bfs_opt *opt, const char *format, ...) { } /** Log the deletion of an expression. */ -attr(printf(2, 3)) +_printf(2, 3) static bool opt_delete(struct bfs_opt *opt, const char *format, ...) { int depth = opt->depth; @@ -608,22 +618,26 @@ static bool is_const(const struct bfs_expr *expr) { } /** Warn about an expression. */ -attr(printf(3, 4)) -static void opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, const char *format, ...) { +_printf(3, 4) +static bool opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, const char *format, ...) { if (!opt->warn) { - return; + return false; } if (bfs_expr_is_parent(expr) || is_const(expr)) { - return; + return false; } - if (bfs_expr_warning(opt->ctx, expr)) { - va_list args; - va_start(args, format); - bfs_vwarning(opt->ctx, format, args); - va_end(args); + if (!bfs_expr_warning(opt->ctx, expr)) { + return false; } + + va_list args; + va_start(args, format); + bfs_vwarning(opt->ctx, format, args); + va_end(args); + + return true; } /** Remove and return an expression's children. */ @@ -1327,7 +1341,7 @@ static struct bfs_expr *opt_const(struct bfs_opt *opt, bool value) { static bfs_eval_fn *const fns[] = {eval_false, eval_true}; static char *fake_args[] = {"-false", "-true"}; - struct bfs_expr *expr = bfs_expr_new(opt->ctx, fns[value], 1, &fake_args[value]); + struct bfs_expr *expr = bfs_expr_new(opt->ctx, fns[value], 1, &fake_args[value], BFS_TEST); return visit_shallow(opt, expr, &annotate); } @@ -1341,7 +1355,7 @@ static struct bfs_expr *negate_expr(struct bfs_opt *opt, struct bfs_expr *expr, return opt_const(opt, true); } - struct bfs_expr *ret = bfs_expr_new(opt->ctx, eval_not, 1, argv); + struct bfs_expr *ret = bfs_expr_new(opt->ctx, eval_not, 1, argv, BFS_OPERATOR); if (!ret) { return NULL; } @@ -1389,12 +1403,11 @@ static struct bfs_expr *sink_not_andor(struct bfs_opt *opt, struct bfs_expr *exp /** Sink a negation into a comma expression. */ static struct bfs_expr *sink_not_comma(struct bfs_opt *opt, struct bfs_expr *expr) { - bfs_assert(expr->eval_fn == eval_comma); - - opt_enter(opt, "%pe\n", expr); - char **argv = expr->argv; expr = only_child(expr); + opt_enter(opt, "%pe\n", expr); + + bfs_assert(expr->eval_fn == eval_comma); struct bfs_exprs children; foster_children(expr, &children); @@ -1601,8 +1614,7 @@ static void data_flow_icmp(struct bfs_opt *opt, const struct bfs_expr *expr, enu switch (expr->int_cmp) { case BFS_INT_EQUAL: - constrain_min(true_range, value); - constrain_max(true_range, value); + constrain_range(true_range, value); range_remove(false_range, value); break; @@ -1635,6 +1647,18 @@ static struct bfs_expr *data_flow_access(struct bfs_opt *opt, struct bfs_expr *e return expr; } +/** Transfer function for -empty. */ +static struct bfs_expr *data_flow_empty(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + opt->after_true.types &= (1 << BFS_REG) | (1 << BFS_DIR); + + if (opt->before.types == (1 << BFS_REG)) { + constrain_range(&opt->after_true.ranges[SIZE_RANGE], 0); + range_remove(&opt->after_false.ranges[SIZE_RANGE], 0); + } + + return expr; +} + /** Transfer function for -gid. */ static struct bfs_expr *data_flow_gid(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { struct df_range *range = &opt->after_true.ranges[GID_RANGE]; @@ -1673,11 +1697,16 @@ static struct bfs_expr *data_flow_links(struct bfs_opt *opt, struct bfs_expr *ex return expr; } +/** Transfer function for -lname. */ +static struct bfs_expr *data_flow_lname(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + opt->after_true.types &= 1 << BFS_LNK; + return expr; +} + /** Transfer function for -samefile. */ static struct bfs_expr *data_flow_samefile(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { struct df_range *true_range = &opt->after_true.ranges[INUM_RANGE]; - constrain_min(true_range, expr->ino); - constrain_max(true_range, expr->ino); + constrain_range(true_range, expr->ino); struct df_range *false_range = &opt->after_false.ranges[INUM_RANGE]; range_remove(false_range, expr->ino); @@ -1785,12 +1814,45 @@ static struct bfs_expr *data_flow_leave(struct bfs_opt *opt, struct bfs_expr *ex return visit_leave(opt, expr, visitor); } -/** Data flow visitor function. */ -static struct bfs_expr *data_flow_visit(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { - if (opt->ignore_result && expr->pure) { +/** Ignore an expression (and possibly warn/prompt). */ +static struct bfs_expr *opt_ignore(struct bfs_opt *opt, struct bfs_expr *expr, bool delete) { + if (delete) { + opt_delete(opt, "%pe [ignored result]\n", expr); + } else { opt_debug(opt, "ignored result\n"); - opt_warning(opt, expr, "The result of this expression is ignored.\n\n"); + } + + if (expr->kind != BFS_TEST) { + goto done; + } + + if (!opt_warning(opt, expr, "The result of this expression is ignored.\n")) { + goto done; + } + + struct bfs_ctx *ctx = opt->ctx; + if (ctx->interactive && ctx->dangerous) { + bfs_warning(ctx, "Do you want to continue? "); + if (ynprompt() <= 0) { + errno = 0; + return NULL; + } + } + + fprintf(stderr, "\n"); + +done: + if (!delete && expr->pure) { + // If we're not deleting the expression entirely, replace it with -false expr = opt_const(opt, false); + } + return expr; +} + +/** Data flow visitor function. */ +static struct bfs_expr *data_flow_visit(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + if (opt->ignore_result) { + expr = opt_ignore(opt, expr, false); if (!expr) { return NULL; } @@ -1860,9 +1922,11 @@ static const struct visitor data_flow = { .leave = data_flow_leave, .table = (const struct visitor_table[]) { {eval_access, data_flow_access}, + {eval_empty, data_flow_empty}, {eval_gid, data_flow_gid}, {eval_inum, data_flow_inum}, {eval_links, data_flow_links}, + {eval_lname, data_flow_lname}, {eval_samefile, data_flow_samefile}, {eval_size, data_flow_size}, {eval_type, data_flow_type}, @@ -1925,6 +1989,10 @@ static struct bfs_expr *lift_andor_not(struct bfs_opt *opt, struct bfs_expr *exp } expr = visit_shallow(opt, expr, &annotate); + if (!expr) { + return NULL; + } + return negate_expr(opt, expr, &fake_not_arg); } @@ -1962,8 +2030,9 @@ static struct bfs_expr *simplify_and(struct bfs_opt *opt, struct bfs_expr *expr, } if (ignore) { - opt_delete(opt, "%pe [ignored result]\n", child); - opt_warning(opt, child, "The result of this expression is ignored.\n\n"); + if (!opt_ignore(opt, child, true)) { + return NULL; + } continue; } @@ -2010,8 +2079,9 @@ static struct bfs_expr *simplify_or(struct bfs_opt *opt, struct bfs_expr *expr, } if (ignore) { - opt_delete(opt, "%pe [ignored result]\n", child); - opt_warning(opt, child, "The result of this expression is ignored.\n\n"); + if (!opt_ignore(opt, child, true)) { + return NULL; + } continue; } @@ -2051,8 +2121,9 @@ static struct bfs_expr *simplify_comma(struct bfs_opt *opt, struct bfs_expr *exp struct bfs_expr *child = SLIST_POP(&children); if (opt->level >= 2 && child->pure && !SLIST_EMPTY(&children)) { - opt_delete(opt, "%pe [ignored result]\n", child); - opt_warning(opt, child, "The result of this expression is ignored.\n\n"); + if (!opt_ignore(opt, child, true)) { + return NULL; + } continue; } @@ -2103,6 +2174,8 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) { }; struct df_domain impure; + df_init_top(&opt->after_true); + df_init_top(&opt->after_false); for (int i = 0; i < 3; ++i) { struct bfs_opt nested = *opt; @@ -2116,9 +2189,11 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) { continue; } + const struct visitor *visitor = passes[j].visitor; + // Skip reordering the first time through the passes, to // make warnings more understandable - if (passes[j].visitor == &reorder) { + if (visitor == &reorder) { if (i == 0) { continue; } else { @@ -2126,10 +2201,15 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) { } } - expr = visit(&nested, expr, passes[j].visitor); + expr = visit(&nested, expr, visitor); if (!expr) { return NULL; } + + if (visitor == &data_flow) { + opt->after_true = nested.after_true; + opt->after_false = nested.after_false; + } } opt_leave(&nested, NULL); diff --git a/src/parse.c b/src/parse.c index 86ce72c..42f71cc 100644 --- a/src/parse.c +++ b/src/parse.c @@ -8,9 +8,10 @@ * flags like always-true options, and skipping over paths wherever they appear. */ -#include "prelude.h" #include "parse.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "bftw.h" #include "color.h" @@ -31,6 +32,7 @@ #include "xregex.h" #include "xspawn.h" #include "xtime.h" + #include <errno.h> #include <fcntl.h> #include <fnmatch.h> @@ -78,8 +80,6 @@ struct bfs_parser { /** Whether stdout is a terminal. */ bool stdout_tty; - /** Whether this session is interactive (stdin and stderr are each a terminal). */ - bool interactive; /** Whether -color or -nocolor has been passed. */ enum use_color use_color; /** Whether a -print action is implied. */ @@ -115,31 +115,6 @@ struct bfs_parser { }; /** - * Token types and flags. - */ -enum token_info { - /** A flag. */ - T_FLAG = 1, - /** A root path. */ - T_PATH = 2, - /** An option. */ - T_OPTION = 3, - /** A test. */ - T_TEST = 4, - /** An action. */ - T_ACTION = 5, - /** An operator. */ - T_OPERATOR = 6, - /** Mask for token types. */ - T_TYPE = (1 << 3) - 1, - - /** A token can match a prefix of an argument, like -On, -newerXY, etc. */ - T_PREFIX = 1 << 3, - /** A flag that takes an argument. */ - T_NEEDS_ARG = 1 << 4, -}; - -/** * Print a low-level error message during parsing. */ static void parse_perror(const struct bfs_parser *parser, const char *str) { @@ -165,7 +140,7 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, /** * Print an error message during parsing. */ -attr(printf(2, 3)) +_printf(2, 3) static void parse_error(const struct bfs_parser *parser, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -183,7 +158,7 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ... /** * Print an error about some command line arguments. */ -attr(printf(4, 5)) +_printf(4, 5) static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_t argc, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -201,7 +176,7 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_ /** * Print an error about conflicting command line arguments. */ -attr(printf(6, 7)) +_printf(6, 7) static void parse_conflict_error(const struct bfs_parser *parser, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -220,7 +195,7 @@ static void parse_conflict_error(const struct bfs_parser *parser, char **argv1, /** * Print an error about an expression. */ -attr(printf(3, 4)) +_printf(3, 4) static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -235,7 +210,7 @@ static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_e /** * Print a warning message during parsing. */ -attr(printf(2, 3)) +_printf(2, 3) static bool parse_warning(const struct bfs_parser *parser, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -256,7 +231,7 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, . /** * Print a warning about conflicting command line arguments. */ -attr(printf(6, 7)) +_printf(6, 7) static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -278,7 +253,7 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1 /** * Print a warning about an expression. */ -attr(printf(3, 4)) +_printf(3, 4) static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -296,8 +271,8 @@ static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs /** * Allocate a new expression. */ -static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc, char **argv) { - struct bfs_expr *expr = bfs_expr_new(parser->ctx, eval_fn, argc, argv); +static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc, char **argv, enum bfs_kind kind) { + struct bfs_expr *expr = bfs_expr_new(parser->ctx, eval_fn, argc, argv, kind); if (!expr) { parse_perror(parser, "bfs_expr_new()"); } @@ -308,7 +283,7 @@ static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval * Create a new unary expression. */ static struct bfs_expr *new_unary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *rhs, char **argv) { - struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv); + struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv, BFS_OPERATOR); if (!expr) { return NULL; } @@ -322,7 +297,7 @@ static struct bfs_expr *new_unary_expr(const struct bfs_parser *parser, bfs_eval * Create a new binary expression. */ static struct bfs_expr *new_binary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *lhs, struct bfs_expr *rhs, char **argv) { - struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv); + struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv, BFS_OPERATOR); if (!expr) { return NULL; } @@ -407,14 +382,12 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser); /** * Advance by a single token. */ -static char **parser_advance(struct bfs_parser *parser, enum token_info type, size_t argc) { - bfs_assert(type == (type & T_TYPE)); - - if (type != T_FLAG && type != T_PATH) { +static char **parser_advance(struct bfs_parser *parser, enum bfs_kind kind, size_t argc) { + if (kind != BFS_FLAG && kind != BFS_PATH) { parser->expr_started = true; } - if (type != T_PATH) { + if (kind != BFS_PATH) { parser->last_arg = parser->argv; } @@ -460,7 +433,7 @@ static int skip_paths(struct bfs_parser *parser) { // find uses -- to separate flags from the rest // of the command line. We allow mixing flags // and paths/predicates, so we just ignore --. - parser_advance(parser, T_FLAG, 1); + parser_advance(parser, BFS_FLAG, 1); continue; } if (strcmp(arg, "-") != 0) { @@ -492,7 +465,7 @@ static int skip_paths(struct bfs_parser *parser) { return -1; } - parser_advance(parser, T_PATH, 1); + parser_advance(parser, BFS_PATH, 1); } } @@ -623,8 +596,8 @@ static bool looks_like_icmp(const char *str) { * Parse a single flag. */ static struct bfs_expr *parse_flag(struct bfs_parser *parser, size_t argc) { - char **argv = parser_advance(parser, T_FLAG, argc); - return parse_new_expr(parser, eval_true, argc, argv); + char **argv = parser_advance(parser, BFS_FLAG, argc); + return parse_new_expr(parser, eval_true, argc, argv, BFS_FLAG); } /** @@ -677,8 +650,8 @@ static struct bfs_expr *parse_prefix_flag(struct bfs_parser *parser, char flag, * Parse a single option. */ static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) { - char **argv = parser_advance(parser, T_OPTION, argc); - return parse_new_expr(parser, eval_true, argc, argv); + char **argv = parser_advance(parser, BFS_OPTION, argc); + return parse_new_expr(parser, eval_true, argc, argv, BFS_OPTION); } /** @@ -706,8 +679,8 @@ static struct bfs_expr *parse_unary_option(struct bfs_parser *parser) { * Parse a single test. */ static struct bfs_expr *parse_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { - char **argv = parser_advance(parser, T_TEST, argc); - return parse_new_expr(parser, eval_fn, argc, argv); + char **argv = parser_advance(parser, BFS_TEST, argc); + return parse_new_expr(parser, eval_fn, argc, argv, BFS_TEST); } /** @@ -735,7 +708,7 @@ static struct bfs_expr *parse_unary_test(struct bfs_parser *parser, bfs_eval_fn * Parse a single action. */ static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { - char **argv = parser_advance(parser, T_ACTION, argc); + char **argv = parser_advance(parser, BFS_ACTION, argc); if (parser->excluding) { parse_argv_error(parser, argv, argc, "This action is not supported within ${red}-exclude${rs}.\n"); @@ -746,7 +719,7 @@ static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eva parser->implicit_print = false; } - return parse_new_expr(parser, eval_fn, argc, argv); + return parse_new_expr(parser, eval_fn, argc, argv, BFS_ACTION); } /** @@ -1005,16 +978,16 @@ static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg switch (*tail) { case 'w': time *= 7; - fallthru; + _fallthrough; case 'd': time *= 24; - fallthru; + _fallthrough; case 'h': time *= 60; - fallthru; + _fallthrough; case 'm': time *= 60; - fallthru; + _fallthrough; case 's': break; default: @@ -1126,7 +1099,7 @@ static struct bfs_expr *parse_fnmatch(const struct bfs_parser *parser, struct bf // strcmp() can be much faster than fnmatch() since it doesn't have to // parse the pattern, so special-case patterns with no wildcards. // - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 + // https://pubs.opengroup.org/onlinepubs/9799919799/utilities/V3_chap02.html#tag_19_14_01 expr->literal = strcspn(expr->pattern, "?*\\[") == len; return expr; @@ -1185,8 +1158,12 @@ static struct bfs_expr *parse_daystart(struct bfs_parser *parser, int arg1, int * Parse -delete. */ static struct bfs_expr *parse_delete(struct bfs_parser *parser, int arg1, int arg2) { - parser->ctx->flags |= BFTW_POST_ORDER; + struct bfs_ctx *ctx = parser->ctx; + ctx->flags |= BFTW_POST_ORDER; + ctx->dangerous = true; + parser->depth_arg = parser->argv; + return parse_nullary_action(parser, eval_delete); } @@ -1246,7 +1223,9 @@ static struct bfs_expr *parse_empty(struct bfs_parser *parser, int arg1, int arg * Parse -exec(dir)?/-ok(dir)?. */ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg2) { - struct bfs_exec *execbuf = bfs_exec_parse(parser->ctx, parser->argv, flags); + struct bfs_ctx *ctx = parser->ctx; + + struct bfs_exec *execbuf = bfs_exec_parse(ctx, parser->argv, flags); if (!execbuf) { return NULL; } @@ -1295,6 +1274,8 @@ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg if (execbuf->flags & BFS_EXEC_CONFIRM) { parser->ok_expr = expr; + } else { + ctx->dangerous = true; } return expr; @@ -1694,10 +1675,7 @@ static struct bfs_expr *parse_mount(struct bfs_parser *parser, int arg1, int arg return NULL; } - parse_expr_warning(parser, expr, "In the future, ${blu}%s${rs} will skip mount points entirely, unlike\n", expr->argv[0]); - bfs_warning(parser->ctx, "${blu}-xdev${rs}, due to http://austingroupbugs.net/view.php?id=1133.\n\n"); - - parser->ctx->flags |= BFTW_PRUNE_MOUNTS; + parser->ctx->flags |= BFTW_SKIP_MOUNTS; parser->mount_arg = expr->argv; return expr; } @@ -1841,6 +1819,14 @@ static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int a } /** + * Parse -noerror. + */ +static struct bfs_expr *parse_noerror(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->ignore_errors = true; + return parse_nullary_option(parser); +} + +/** * Parse -nogroup. */ static struct bfs_expr *parse_nogroup(struct bfs_parser *parser, int arg1, int arg2) { @@ -1856,7 +1842,7 @@ static struct bfs_expr *parse_nogroup(struct bfs_parser *parser, int arg1, int a * Parse -nohidden. */ static struct bfs_expr *parse_nohidden(struct bfs_parser *parser, int arg1, int arg2) { - struct bfs_expr *hidden = parse_new_expr(parser, eval_hidden, 1, &fake_hidden_arg); + struct bfs_expr *hidden = parse_new_expr(parser, eval_hidden, 1, &fake_hidden_arg, BFS_TEST); if (!hidden) { return NULL; } @@ -1904,6 +1890,8 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct return 0; } + mode_t umask = parser->ctx->umask; + expr->file_mode = 0; expr->dir_mode = 0; @@ -1924,7 +1912,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct // // PERMCOPY : "u" | "g" | "o" - // Parser machine parser + // State machine state enum { MODE_CLAUSE, MODE_WHO, @@ -1932,7 +1920,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct MODE_ACTION_APPLY, MODE_OP, MODE_PERM, - } mparser = MODE_CLAUSE; + } state = MODE_CLAUSE; enum { MODE_PLUS, @@ -1941,16 +1929,18 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct } op uninit(MODE_EQUALS); mode_t who uninit(0); + mode_t mask uninit(0); mode_t file_change uninit(0); mode_t dir_change uninit(0); const char *i = mode; while (true) { - switch (mparser) { + switch (state) { case MODE_CLAUSE: who = 0; - mparser = MODE_WHO; - fallthru; + mask = 0777; + state = MODE_WHO; + _fallthrough; case MODE_WHO: switch (*i) { @@ -1967,7 +1957,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct who |= 0777; break; default: - mparser = MODE_ACTION; + state = MODE_ACTION; continue; } break; @@ -1977,7 +1967,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct case MODE_EQUALS: expr->file_mode &= ~who; expr->dir_mode &= ~who; - fallthru; + _fallthrough; case MODE_PLUS: expr->file_mode |= file_change; expr->dir_mode |= dir_change; @@ -1987,37 +1977,40 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct expr->dir_mode &= ~dir_change; break; } - fallthru; + _fallthrough; case MODE_ACTION: if (who == 0) { who = 0777; + mask = who & ~umask; + } else { + mask = who; } switch (*i) { case '+': op = MODE_PLUS; - mparser = MODE_OP; + state = MODE_OP; break; case '-': op = MODE_MINUS; - mparser = MODE_OP; + state = MODE_OP; break; case '=': op = MODE_EQUALS; - mparser = MODE_OP; + state = MODE_OP; break; case ',': - if (mparser == MODE_ACTION_APPLY) { - mparser = MODE_CLAUSE; + if (state == MODE_ACTION_APPLY) { + state = MODE_CLAUSE; } else { goto fail; } break; case '\0': - if (mparser == MODE_ACTION_APPLY) { + if (state == MODE_ACTION_APPLY) { goto done; } else { goto fail; @@ -2046,32 +2039,32 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct default: file_change = 0; dir_change = 0; - mparser = MODE_PERM; + state = MODE_PERM; continue; } file_change |= (file_change << 6) | (file_change << 3); - file_change &= who; + file_change &= mask; dir_change |= (dir_change << 6) | (dir_change << 3); - dir_change &= who; - mparser = MODE_ACTION_APPLY; + dir_change &= mask; + state = MODE_ACTION_APPLY; break; case MODE_PERM: switch (*i) { case 'r': - file_change |= who & 0444; - dir_change |= who & 0444; + file_change |= mask & 0444; + dir_change |= mask & 0444; break; case 'w': - file_change |= who & 0222; - dir_change |= who & 0222; + file_change |= mask & 0222; + dir_change |= mask & 0222; break; case 'x': - file_change |= who & 0111; - fallthru; + file_change |= mask & 0111; + _fallthrough; case 'X': - dir_change |= who & 0111; + dir_change |= mask & 0111; break; case 's': if (who & 0700) { @@ -2090,7 +2083,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct } break; default: - mparser = MODE_ACTION_APPLY; + state = MODE_ACTION_APPLY; continue; } break; @@ -2132,7 +2125,7 @@ static struct bfs_expr *parse_perm(struct bfs_parser *parser, int field, int arg ++mode; break; } - fallthru; + _fallthrough; default: expr->mode_cmp = BFS_MODE_EQUAL; break; @@ -2774,8 +2767,9 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2 cfprintf(cout, " ${blu}-mindepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " Ignore files deeper/shallower than ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mount${rs}\n"); - cfprintf(cout, " Don't descend into other mount points (same as ${blu}-xdev${rs} for now, but will\n"); - cfprintf(cout, " skip mount points entirely in the future)\n"); + cfprintf(cout, " Exclude mount points entirely from the results\n"); + cfprintf(cout, " ${blu}-noerror${rs}\n"); + cfprintf(cout, " Ignore any errors that occur during traversal\n"); cfprintf(cout, " ${blu}-nohidden${rs}\n"); cfprintf(cout, " Exclude hidden files\n"); cfprintf(cout, " ${blu}-noleaf${rs}\n"); @@ -2952,11 +2946,42 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2 return NULL; } +/** Print the bfs "logo". */ +static void print_logo(CFILE *cout) { + if (!cout->colors) { + goto boring; + } + + size_t vwidth = xstrwidth(bfs_version); + dchar *spaces = dstrepeat(" ", vwidth); + dchar *lines = dstrepeat("─", vwidth); + if (!spaces || !lines) { + dstrfree(lines); + dstrfree(spaces); + goto boring; + } + + // We do ----\r<emoji> rather than <emoji>--- so we don't have to assume + // anything about the width of the emoji + cfprintf(cout, "╭─────%s╮\r📂\n", lines); + cfprintf(cout, "├${ex}b${rs} %s │\n", spaces); + cfprintf(cout, "╰├${ex}f${rs} ${bld}%s${rs} │\n", bfs_version); + cfprintf(cout, " ╰├${ex}s${rs} %s │\n", spaces); + cfprintf(cout, " ╰──%s─╯\n\n", lines); + + dstrfree(lines); + dstrfree(spaces); + return; + +boring: + printf("%s %s\n\n", BFS_COMMAND, bfs_version); +} + /** * "Parse" -version. */ static struct bfs_expr *parse_version(struct bfs_parser *parser, int arg1, int arg2) { - cfprintf(parser->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, bfs_version); + print_logo(parser->ctx->cout); printf("Copyright © Tavian Barnes and the bfs contributors\n"); printf("No rights reserved (https://opensource.org/license/0BSD)\n\n"); @@ -2982,136 +3007,139 @@ typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2) */ struct table_entry { char *arg; - enum token_info info; + enum bfs_kind kind; parse_fn *parse; int arg1; int arg2; + bool prefix; + bool needs_arg; }; /** * The parse table for primary expressions. */ static const struct table_entry parse_table[] = { - {"--", T_FLAG}, - {"--help", T_ACTION, parse_help}, - {"--version", T_ACTION, parse_version}, - {"-Bmin", T_TEST, parse_min, BFS_STAT_BTIME}, - {"-Bnewer", T_TEST, parse_newer, BFS_STAT_BTIME}, - {"-Bsince", T_TEST, parse_since, BFS_STAT_BTIME}, - {"-Btime", T_TEST, parse_time, BFS_STAT_BTIME}, - {"-D", T_FLAG | T_PREFIX, parse_debug}, - {"-E", T_FLAG, parse_regex_extended}, - {"-H", T_FLAG, parse_follow, BFTW_FOLLOW_ROOTS, false}, - {"-L", T_FLAG, parse_follow, BFTW_FOLLOW_ALL, false}, - {"-O", T_FLAG | T_PREFIX, parse_optlevel}, - {"-P", T_FLAG, parse_follow, 0, false}, - {"-S", T_FLAG | T_PREFIX, parse_search_strategy}, - {"-X", T_FLAG, parse_xargs_safe}, - {"-a", T_OPERATOR}, - {"-acl", T_TEST, parse_acl}, - {"-amin", T_TEST, parse_min, BFS_STAT_ATIME}, - {"-and", T_OPERATOR}, - {"-anewer", T_TEST, parse_newer, BFS_STAT_ATIME}, - {"-asince", T_TEST, parse_since, BFS_STAT_ATIME}, - {"-atime", T_TEST, parse_time, BFS_STAT_ATIME}, - {"-capable", T_TEST, parse_capable}, - {"-cmin", T_TEST, parse_min, BFS_STAT_CTIME}, - {"-cnewer", T_TEST, parse_newer, BFS_STAT_CTIME}, - {"-color", T_OPTION, parse_color, true}, - {"-context", T_TEST, parse_context, true}, - {"-csince", T_TEST, parse_since, BFS_STAT_CTIME}, - {"-ctime", T_TEST, parse_time, BFS_STAT_CTIME}, - {"-d", T_FLAG, parse_depth}, - {"-daystart", T_OPTION, parse_daystart}, - {"-delete", T_ACTION, parse_delete}, - {"-depth", T_OPTION, parse_depth_n}, - {"-empty", T_TEST, parse_empty}, - {"-exclude", T_OPERATOR}, - {"-exec", T_ACTION, parse_exec, 0}, - {"-execdir", T_ACTION, parse_exec, BFS_EXEC_CHDIR}, - {"-executable", T_TEST, parse_access, X_OK}, - {"-exit", T_ACTION, parse_exit}, - {"-f", T_FLAG | T_NEEDS_ARG, parse_f}, - {"-false", T_TEST, parse_const, false}, - {"-files0-from", T_OPTION, parse_files0_from}, - {"-flags", T_TEST, parse_flags}, - {"-fls", T_ACTION, parse_fls}, - {"-follow", T_OPTION, parse_follow, BFTW_FOLLOW_ALL, true}, - {"-fprint", T_ACTION, parse_fprint}, - {"-fprint0", T_ACTION, parse_fprint0}, - {"-fprintf", T_ACTION, parse_fprintf}, - {"-fstype", T_TEST, parse_fstype}, - {"-gid", T_TEST, parse_group}, - {"-group", T_TEST, parse_group}, - {"-help", T_ACTION, parse_help}, - {"-hidden", T_TEST, parse_hidden}, - {"-ignore_readdir_race", T_OPTION, parse_ignore_races, true}, - {"-ilname", T_TEST, parse_lname, true}, - {"-iname", T_TEST, parse_name, true}, - {"-inum", T_TEST, parse_inum}, - {"-ipath", T_TEST, parse_path, true}, - {"-iregex", T_TEST, parse_regex, BFS_REGEX_ICASE}, - {"-iwholename", T_TEST, parse_path, true}, - {"-j", T_FLAG | T_PREFIX, parse_jobs}, - {"-limit", T_ACTION, parse_limit}, - {"-links", T_TEST, parse_links}, - {"-lname", T_TEST, parse_lname, false}, - {"-ls", T_ACTION, parse_ls}, - {"-maxdepth", T_OPTION, parse_depth_limit, false}, - {"-mindepth", T_OPTION, parse_depth_limit, true}, - {"-mmin", T_TEST, parse_min, BFS_STAT_MTIME}, - {"-mnewer", T_TEST, parse_newer, BFS_STAT_MTIME}, - {"-mount", T_OPTION, parse_mount}, - {"-msince", T_TEST, parse_since, BFS_STAT_MTIME}, - {"-mtime", T_TEST, parse_time, BFS_STAT_MTIME}, - {"-name", T_TEST, parse_name, false}, - {"-newer", T_TEST, parse_newer, BFS_STAT_MTIME}, - {"-newer", T_TEST | T_PREFIX, parse_newerxy}, - {"-nocolor", T_OPTION, parse_color, false}, - {"-nogroup", T_TEST, parse_nogroup}, - {"-nohidden", T_TEST, parse_nohidden}, - {"-noignore_readdir_race", T_OPTION, parse_ignore_races, false}, - {"-noleaf", T_OPTION, parse_noleaf}, - {"-not", T_OPERATOR}, - {"-nouser", T_TEST, parse_nouser}, - {"-nowarn", T_OPTION, parse_warn, false}, - {"-o", T_OPERATOR}, - {"-ok", T_ACTION, parse_exec, BFS_EXEC_CONFIRM}, - {"-okdir", T_ACTION, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, - {"-or", T_OPERATOR}, - {"-path", T_TEST, parse_path, false}, - {"-perm", T_TEST, parse_perm}, - {"-print", T_ACTION, parse_print}, - {"-print0", T_ACTION, parse_print0}, - {"-printf", T_ACTION, parse_printf}, - {"-printx", T_ACTION, parse_printx}, - {"-prune", T_ACTION, parse_prune}, - {"-quit", T_ACTION, parse_quit}, - {"-readable", T_TEST, parse_access, R_OK}, - {"-regex", T_TEST, parse_regex, 0}, - {"-regextype", T_OPTION, parse_regextype}, - {"-rm", T_ACTION, parse_delete}, - {"-s", T_FLAG, parse_s}, - {"-samefile", T_TEST, parse_samefile}, - {"-since", T_TEST, parse_since, BFS_STAT_MTIME}, - {"-size", T_TEST, parse_size}, - {"-sparse", T_TEST, parse_sparse}, - {"-status", T_OPTION, parse_status}, - {"-true", T_TEST, parse_const, true}, - {"-type", T_TEST, parse_type, false}, - {"-uid", T_TEST, parse_user}, - {"-unique", T_OPTION, parse_unique}, - {"-used", T_TEST, parse_used}, - {"-user", T_TEST, parse_user}, - {"-version", T_ACTION, parse_version}, - {"-warn", T_OPTION, parse_warn, true}, - {"-wholename", T_TEST, parse_path, false}, - {"-writable", T_TEST, parse_access, W_OK}, - {"-x", T_FLAG, parse_xdev}, - {"-xattr", T_TEST, parse_xattr}, - {"-xattrname", T_TEST, parse_xattrname}, - {"-xdev", T_OPTION, parse_xdev}, - {"-xtype", T_TEST, parse_type, true}, + {"--", BFS_FLAG}, + {"--help", BFS_ACTION, parse_help}, + {"--version", BFS_ACTION, parse_version}, + {"-Bmin", BFS_TEST, parse_min, BFS_STAT_BTIME}, + {"-Bnewer", BFS_TEST, parse_newer, BFS_STAT_BTIME}, + {"-Bsince", BFS_TEST, parse_since, BFS_STAT_BTIME}, + {"-Btime", BFS_TEST, parse_time, BFS_STAT_BTIME}, + {"-D", BFS_FLAG, parse_debug, .prefix = true}, + {"-E", BFS_FLAG, parse_regex_extended}, + {"-H", BFS_FLAG, parse_follow, BFTW_FOLLOW_ROOTS, false}, + {"-L", BFS_FLAG, parse_follow, BFTW_FOLLOW_ALL, false}, + {"-O", BFS_FLAG, parse_optlevel, .prefix = true}, + {"-P", BFS_FLAG, parse_follow, 0, false}, + {"-S", BFS_FLAG, parse_search_strategy, .prefix = true}, + {"-X", BFS_FLAG, parse_xargs_safe}, + {"-a", BFS_OPERATOR}, + {"-acl", BFS_TEST, parse_acl}, + {"-amin", BFS_TEST, parse_min, BFS_STAT_ATIME}, + {"-and", BFS_OPERATOR}, + {"-anewer", BFS_TEST, parse_newer, BFS_STAT_ATIME}, + {"-asince", BFS_TEST, parse_since, BFS_STAT_ATIME}, + {"-atime", BFS_TEST, parse_time, BFS_STAT_ATIME}, + {"-capable", BFS_TEST, parse_capable}, + {"-cmin", BFS_TEST, parse_min, BFS_STAT_CTIME}, + {"-cnewer", BFS_TEST, parse_newer, BFS_STAT_CTIME}, + {"-color", BFS_OPTION, parse_color, true}, + {"-context", BFS_TEST, parse_context, true}, + {"-csince", BFS_TEST, parse_since, BFS_STAT_CTIME}, + {"-ctime", BFS_TEST, parse_time, BFS_STAT_CTIME}, + {"-d", BFS_FLAG, parse_depth}, + {"-daystart", BFS_OPTION, parse_daystart}, + {"-delete", BFS_ACTION, parse_delete}, + {"-depth", BFS_OPTION, parse_depth_n}, + {"-empty", BFS_TEST, parse_empty}, + {"-exclude", BFS_OPERATOR}, + {"-exec", BFS_ACTION, parse_exec, 0}, + {"-execdir", BFS_ACTION, parse_exec, BFS_EXEC_CHDIR}, + {"-executable", BFS_TEST, parse_access, X_OK}, + {"-exit", BFS_ACTION, parse_exit}, + {"-f", BFS_FLAG, parse_f, .needs_arg = true}, + {"-false", BFS_TEST, parse_const, false}, + {"-files0-from", BFS_OPTION, parse_files0_from}, + {"-flags", BFS_TEST, parse_flags}, + {"-fls", BFS_ACTION, parse_fls}, + {"-follow", BFS_OPTION, parse_follow, BFTW_FOLLOW_ALL, true}, + {"-fprint", BFS_ACTION, parse_fprint}, + {"-fprint0", BFS_ACTION, parse_fprint0}, + {"-fprintf", BFS_ACTION, parse_fprintf}, + {"-fstype", BFS_TEST, parse_fstype}, + {"-gid", BFS_TEST, parse_group}, + {"-group", BFS_TEST, parse_group}, + {"-help", BFS_ACTION, parse_help}, + {"-hidden", BFS_TEST, parse_hidden}, + {"-ignore_readdir_race", BFS_OPTION, parse_ignore_races, true}, + {"-ilname", BFS_TEST, parse_lname, true}, + {"-iname", BFS_TEST, parse_name, true}, + {"-inum", BFS_TEST, parse_inum}, + {"-ipath", BFS_TEST, parse_path, true}, + {"-iregex", BFS_TEST, parse_regex, BFS_REGEX_ICASE}, + {"-iwholename", BFS_TEST, parse_path, true}, + {"-j", BFS_FLAG, parse_jobs, .prefix = true}, + {"-limit", BFS_ACTION, parse_limit}, + {"-links", BFS_TEST, parse_links}, + {"-lname", BFS_TEST, parse_lname, false}, + {"-ls", BFS_ACTION, parse_ls}, + {"-maxdepth", BFS_OPTION, parse_depth_limit, false}, + {"-mindepth", BFS_OPTION, parse_depth_limit, true}, + {"-mmin", BFS_TEST, parse_min, BFS_STAT_MTIME}, + {"-mnewer", BFS_TEST, parse_newer, BFS_STAT_MTIME}, + {"-mount", BFS_OPTION, parse_mount}, + {"-msince", BFS_TEST, parse_since, BFS_STAT_MTIME}, + {"-mtime", BFS_TEST, parse_time, BFS_STAT_MTIME}, + {"-name", BFS_TEST, parse_name, false}, + {"-newer", BFS_TEST, parse_newer, BFS_STAT_MTIME}, + {"-newer", BFS_TEST, parse_newerxy, .prefix = true}, + {"-nocolor", BFS_OPTION, parse_color, false}, + {"-noerror", BFS_OPTION, parse_noerror}, + {"-nogroup", BFS_TEST, parse_nogroup}, + {"-nohidden", BFS_TEST, parse_nohidden}, + {"-noignore_readdir_race", BFS_OPTION, parse_ignore_races, false}, + {"-noleaf", BFS_OPTION, parse_noleaf}, + {"-not", BFS_OPERATOR}, + {"-nouser", BFS_TEST, parse_nouser}, + {"-nowarn", BFS_OPTION, parse_warn, false}, + {"-o", BFS_OPERATOR}, + {"-ok", BFS_ACTION, parse_exec, BFS_EXEC_CONFIRM}, + {"-okdir", BFS_ACTION, parse_exec, BFS_EXEC_CONFIRM | BFS_EXEC_CHDIR}, + {"-or", BFS_OPERATOR}, + {"-path", BFS_TEST, parse_path, false}, + {"-perm", BFS_TEST, parse_perm}, + {"-print", BFS_ACTION, parse_print}, + {"-print0", BFS_ACTION, parse_print0}, + {"-printf", BFS_ACTION, parse_printf}, + {"-printx", BFS_ACTION, parse_printx}, + {"-prune", BFS_ACTION, parse_prune}, + {"-quit", BFS_ACTION, parse_quit}, + {"-readable", BFS_TEST, parse_access, R_OK}, + {"-regex", BFS_TEST, parse_regex, 0}, + {"-regextype", BFS_OPTION, parse_regextype}, + {"-rm", BFS_ACTION, parse_delete}, + {"-s", BFS_FLAG, parse_s}, + {"-samefile", BFS_TEST, parse_samefile}, + {"-since", BFS_TEST, parse_since, BFS_STAT_MTIME}, + {"-size", BFS_TEST, parse_size}, + {"-sparse", BFS_TEST, parse_sparse}, + {"-status", BFS_OPTION, parse_status}, + {"-true", BFS_TEST, parse_const, true}, + {"-type", BFS_TEST, parse_type, false}, + {"-uid", BFS_TEST, parse_user}, + {"-unique", BFS_OPTION, parse_unique}, + {"-used", BFS_TEST, parse_used}, + {"-user", BFS_TEST, parse_user}, + {"-version", BFS_ACTION, parse_version}, + {"-warn", BFS_OPTION, parse_warn, true}, + {"-wholename", BFS_TEST, parse_path, false}, + {"-writable", BFS_TEST, parse_access, W_OK}, + {"-x", BFS_FLAG, parse_xdev}, + {"-xattr", BFS_TEST, parse_xattr}, + {"-xattrname", BFS_TEST, parse_xattrname}, + {"-xdev", BFS_OPTION, parse_xdev}, + {"-xtype", BFS_TEST, parse_type, true}, {0}, }; @@ -3119,7 +3147,7 @@ static const struct table_entry parse_table[] = { static const struct table_entry *table_lookup(const char *arg) { for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { bool match; - if (entry->info & T_PREFIX) { + if (entry->prefix) { match = strncmp(arg, entry->arg, strlen(entry->arg)) == 0; } else { match = strcmp(arg, entry->arg) == 0; @@ -3135,8 +3163,8 @@ static const struct table_entry *table_lookup(const char *arg) { /** Look up a single-character flag in the parse table. */ static const struct table_entry *flag_lookup(char flag) { for (const struct table_entry *entry = parse_table; entry->arg; ++entry) { - enum token_info type = entry->info & T_TYPE; - if (type == T_FLAG && entry->arg[1] == flag && !entry->arg[2]) { + enum bfs_kind kind = entry->kind; + if (kind == BFS_FLAG && entry->arg[1] == flag && !entry->arg[2]) { return entry; } } @@ -3168,14 +3196,12 @@ static bool is_flag_group(const char *arg) { return false; } - if (entry->info & T_PREFIX) { + if (entry->prefix) { // The rest is the flag's argument break; } - if (entry->info & T_NEEDS_ARG) { - needs_arg = true; - } + needs_arg |= entry->needs_arg; } return has_upper; @@ -3199,7 +3225,7 @@ static struct bfs_expr *parse_flag_group(struct bfs_parser *parser) { end = parser->argv; } - if (!expr || entry->info & T_PREFIX) { + if (!expr || entry->prefix) { break; } } @@ -3233,6 +3259,8 @@ static const struct table_entry *table_lookup_fuzzy(const char *arg) { * | ACTION */ static struct bfs_expr *parse_primary(struct bfs_parser *parser) { + struct bfs_ctx *ctx = parser->ctx; + // Paths are already skipped at this point const char *arg = parser->argv[0]; @@ -3255,13 +3283,13 @@ static struct bfs_expr *parse_primary(struct bfs_parser *parser) { match = table_lookup_fuzzy(arg); - CFILE *cerr = parser->ctx->cerr; + CFILE *cerr = ctx->cerr; parse_error(parser, "Unknown argument; did you mean "); - switch (match->info & T_TYPE) { - case T_FLAG: + switch (match->kind) { + case BFS_FLAG: cfprintf(cerr, "${cyn}%s${rs}?", match->arg); break; - case T_OPERATOR: + case BFS_OPERATOR: cfprintf(cerr, "${red}%s${rs}?", match->arg); break; default: @@ -3269,7 +3297,7 @@ static struct bfs_expr *parse_primary(struct bfs_parser *parser) { break; } - if (!parser->interactive || !match->parse) { + if (!ctx->interactive || !match->parse) { fprintf(stderr, "\n"); goto unmatched; } @@ -3311,7 +3339,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) { } if (strcmp(arg, "(") == 0) { - parser_advance(parser, T_OPERATOR, 1); + parser_advance(parser, BFS_OPERATOR, 1); struct bfs_expr *expr = parse_expr(parser); if (!expr) { @@ -3328,7 +3356,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) { return NULL; } - parser_advance(parser, T_OPERATOR, 1); + parser_advance(parser, BFS_OPERATOR, 1); return expr; } else if (strcmp(arg, "-exclude") == 0) { if (parser->excluding) { @@ -3336,7 +3364,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) { return NULL; } - char **argv = parser_advance(parser, T_OPERATOR, 1); + char **argv = parser_advance(parser, BFS_OPERATOR, 1); parser->excluding = true; struct bfs_expr *factor = parse_factor(parser); @@ -3347,9 +3375,9 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) { parser->excluding = false; bfs_expr_append(parser->ctx->exclude, factor); - return parse_new_expr(parser, eval_true, parser->argv - argv, argv); + return parse_new_expr(parser, eval_true, parser->argv - argv, argv, BFS_OPERATOR); } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { - char **argv = parser_advance(parser, T_OPERATOR, 1); + char **argv = parser_advance(parser, BFS_OPERATOR, 1); struct bfs_expr *factor = parse_factor(parser); if (!factor) { @@ -3389,7 +3417,7 @@ static struct bfs_expr *parse_term(struct bfs_parser *parser) { char **argv = &fake_and_arg; if (strcmp(arg, "-a") == 0 || strcmp(arg, "-and") == 0) { - argv = parser_advance(parser, T_OPERATOR, 1); + argv = parser_advance(parser, BFS_OPERATOR, 1); } struct bfs_expr *lhs = term; @@ -3426,7 +3454,7 @@ static struct bfs_expr *parse_clause(struct bfs_parser *parser) { break; } - char **argv = parser_advance(parser, T_OPERATOR, 1); + char **argv = parser_advance(parser, BFS_OPERATOR, 1); struct bfs_expr *lhs = clause; struct bfs_expr *rhs = parse_term(parser); @@ -3461,7 +3489,7 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser) { break; } - char **argv = parser_advance(parser, T_OPERATOR, 1); + char **argv = parser_advance(parser, BFS_OPERATOR, 1); struct bfs_expr *lhs = expr; struct bfs_expr *rhs = parse_clause(parser); @@ -3479,6 +3507,8 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser) { * Parse the top-level expression. */ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { + struct bfs_ctx *ctx = parser->ctx; + if (skip_paths(parser) != 0) { return NULL; } @@ -3487,7 +3517,7 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { if (parser->argv[0]) { expr = parse_expr(parser); } else { - expr = parse_new_expr(parser, eval_true, 1, &fake_true_arg); + expr = parse_new_expr(parser, eval_true, 1, &fake_true_arg, BFS_TEST); } if (!expr) { return NULL; @@ -3507,7 +3537,7 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { return NULL; } - struct bfs_expr *print = parse_new_expr(parser, eval_fprint, 1, &fake_print_arg); + struct bfs_expr *print = parse_new_expr(parser, eval_fprint, 1, &fake_print_arg, BFS_ACTION); if (!print) { return NULL; } @@ -3525,14 +3555,14 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { parser->xdev_arg[0], parser->mount_arg[0]); } - if (parser->ctx->warn && parser->depth_arg && parser->prune_arg) { + if (ctx->warn && parser->depth_arg && parser->prune_arg) { parse_conflict_warning(parser, parser->depth_arg, 1, parser->prune_arg, 1, "${blu}%s${rs} does not work in the presence of ${blu}%s${rs}.\n", parser->prune_arg[0], parser->depth_arg[0]); - if (parser->interactive) { - bfs_warning(parser->ctx, "Do you want to continue? "); - if (ynprompt() == 0) { + if (ctx->interactive) { + bfs_warning(ctx, "Do you want to continue? "); + if (ynprompt() <= 0) { return NULL; } } @@ -3766,6 +3796,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } else { ctx->warn = stdin_tty; } + ctx->interactive = stdin_tty && stderr_tty; struct bfs_parser parser = { .ctx = ctx, @@ -3773,7 +3804,6 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .command = ctx->argv[0], .regex_type = BFS_REGEX_POSIX_BASIC, .stdout_tty = stdout_tty, - .interactive = stdin_tty && stderr_tty, .use_color = use_color, .implicit_print = true, .implicit_root = true, @@ -3789,7 +3819,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .now = ctx->now, }; - ctx->exclude = parse_new_expr(&parser, eval_or, 1, &fake_or_arg); + ctx->exclude = parse_new_expr(&parser, eval_or, 1, &fake_or_arg, BFS_OPERATOR); if (!ctx->exclude) { goto fail; } @@ -3808,7 +3838,9 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } if (bfs_optimize(ctx) != 0) { - bfs_perror(ctx, "bfs_optimize()"); + if (errno != 0) { + bfs_perror(ctx, "bfs_optimize()"); + } goto fail; } diff --git a/src/prelude.h b/src/prelude.h index 34ce803..a0cc2a1 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -2,366 +2,124 @@ // SPDX-License-Identifier: 0BSD /** - * Configuration and feature/platform detection. + * Praeludium. + * + * This header is automatically included in every translation unit, before any + * other headers, so it can set feature test macros[1][2]. This sets up our own + * mini-dialect of C, which includes + * + * - Standard C17 and POSIX.1 2024 features + * - Portable and platform-specific extensions + * - Convenience macros like `bool`, `alignof`, etc. + * - Common compiler extensions like __has_include() + * + * Further bfs-specific utilities are defined in "bfs.h". + * + * [1]: https://www.gnu.org/software/libc/manual/html_node/Feature-Test-Macros.html + * [2]: https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html */ #ifndef BFS_PRELUDE_H #define BFS_PRELUDE_H -// Possible __STDC_VERSION__ values - -#define C95 199409L -#define C99 199901L -#define C11 201112L -#define C17 201710L -#define C23 202311L - -// Get the static_assert() definition as well as __GLIBC__ -#include <assert.h> - -#if __STDC_VERSION__ < C23 -# include <stdalign.h> -# include <stdbool.h> -# include <stdnoreturn.h> -#endif - -// bfs packaging configuration - -#include "config.h" - -#ifndef BFS_COMMAND -# define BFS_COMMAND "bfs" -#endif -#ifndef BFS_HOMEPAGE -# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -#endif - -// This is a symbol instead of a literal so we don't have to rebuild everything -// when the version number changes -extern const char bfs_version[]; +// Feature test macros -extern const char bfs_confflags[]; -extern const char bfs_cc[]; -extern const char bfs_cppflags[]; -extern const char bfs_cflags[]; -extern const char bfs_ldflags[]; -extern const char bfs_ldlibs[]; - -// Check for system headers - -#ifdef __has_include - -#if __has_include(<mntent.h>) -# define BFS_HAS_MNTENT_H true -#endif -#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 -#if __has_include(<sys/mkdev.h>) -# define BFS_HAS_SYS_MKDEV_H true -#endif -#if __has_include(<sys/param.h>) -# define BFS_HAS_SYS_PARAM_H true -#endif -#if __has_include(<sys/sysmacros.h>) -# define BFS_HAS_SYS_SYSMACROS_H true -#endif -#if __has_include(<sys/xattr.h>) -# define BFS_HAS_SYS_XATTR_H true -#endif -#if __has_include(<threads.h>) -# define BFS_HAS_THREADS_H true -#endif -#if __has_include(<util.h>) -# define BFS_HAS_UTIL_H true -#endif +/** + * Linux and BSD handle _POSIX_C_SOURCE differently: on Linux, it enables POSIX + * interfaces that are not visible by default. On BSD, it also *disables* most + * extensions, giving a strict POSIX environment. Since we want the extensions, + * we don't set _POSIX_C_SOURCE. + */ +// #define _POSIX_C_SOURCE 202405L -#else // !__has_include +/** openat() etc. */ +#define _ATFILE_SOURCE 1 -#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 -#define BFS_HAS_SYS_SYSMACROS_H __GLIBC__ -#define BFS_HAS_SYS_XATTR_H __linux__ -#define BFS_HAS_THREADS_H (!__STDC_NO_THREADS__) -#define BFS_HAS_UTIL_H __NetBSD__ +/** BSD-derived extensions. */ +#define _BSD_SOURCE 1 -#endif // !__has_include +/** glibc successor to _BSD_SOURCE. */ +#define _DEFAULT_SOURCE 1 -#ifndef BFS_USE_MNTENT_H -# define BFS_USE_MNTENT_H BFS_HAS_MNTENT_H -#endif -#ifndef BFS_USE_PATHS_H -# define BFS_USE_PATHS_H BFS_HAS_PATHS_H -#endif -#ifndef BFS_USE_SYS_EXTATTR_H -# define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H -#endif -#ifndef BFS_USE_SYS_MKDEV_H -# define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H -#endif -#ifndef BFS_USE_SYS_PARAM_H -# define BFS_USE_SYS_PARAM_H BFS_HAS_SYS_PARAM_H -#endif -#ifndef BFS_USE_SYS_SYSMACROS_H -# define BFS_USE_SYS_SYSMACROS_H BFS_HAS_SYS_SYSMACROS_H -#endif -#ifndef BFS_USE_SYS_XATTR_H -# define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H -#endif -#ifndef BFS_USE_THREADS_H -# define BFS_USE_THREADS_H BFS_HAS_THREADS_H -#endif -#ifndef BFS_USE_UTIL_H -# define BFS_USE_UTIL_H BFS_HAS_UTIL_H -#endif +/** GNU extensions. */ +#define _GNU_SOURCE 1 -// Stub out feature detection on old/incompatible compilers +/** Use 64-bit off_t. */ +#define _FILE_OFFSET_BITS 64 -#ifndef __has_feature -# define __has_feature(feat) false -#endif +/** Use 64-bit time_t. */ +#define _TIME_BITS 64 -#ifndef __has_c_attribute -# define __has_c_attribute(attr) false +/** macOS extensions. */ +#if __APPLE__ +# define _DARWIN_C_SOURCE 1 #endif -#ifndef __has_attribute -# define __has_attribute(attr) false +/** Solaris extensions. */ +#if __sun +# define __EXTENSIONS__ 1 +// https://illumos.org/man/3C/getpwnam#standard-conforming +# define _POSIX_PTHREAD_SEMANTICS 1 #endif -// Fundamental utilities +// Get the convenience macros that became standard spellings in C23 +#if __STDC_VERSION__ < 202311L -/** - * Get the length of an array. - */ -#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__])) +/** _Static_assert() => static_assert() */ +#include <assert.h> +/** _Alignas(), _Alignof() => alignas(), alignof() */ +#include <stdalign.h> +/** _Bool => bool, true, false */ +#include <stdbool.h> /** - * False sharing/destructive interference/largest cache line size. + * C23 deprecates `noreturn void` in favour of `[[noreturn]] void`, so we expose + * _noreturn instead with the other attributes in "bfs.h". */ -#ifdef __GCC_DESTRUCTIVE_SIZE -# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE -#else -# define FALSE_SHARING_SIZE 64 -#endif +// #include <stdnoreturn.h> -/** - * True sharing/constructive interference/smallest cache line size. - */ -#ifdef __GCC_CONSTRUCTIVE_SIZE -# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE -#else -# define TRUE_SHARING_SIZE 64 -#endif +/** Part of <threads.h>, but we don't use anything else from it. */ +#define thread_local _Thread_local -/** - * Alignment specifier that avoids false sharing. - */ -#define cache_align alignas(FALSE_SHARING_SIZE) +#endif // !C23 -// Wrappers for attributes +// Feature detection -/** - * Silence warnings about switch/case fall-throughs. - */ -#if __has_attribute(fallthrough) -# define fallthru __attribute__((fallthrough)) -#else -# define fallthru ((void)0) -#endif - -/** - * Silence warnings about unused declarations. - */ -#if __has_attribute(unused) -# define attr_maybe_unused __attribute__((unused)) -#else -# define attr_maybe_unused +// https://clang.llvm.org/docs/LanguageExtensions.html#has-attribute +#ifndef __has_attribute +# define __has_attribute(attr) false #endif -/** - * Warn if a value is unused. - */ -#if __has_attribute(warn_unused_result) -# define attr_nodiscard __attribute__((warn_unused_result)) -#else -# define attr_nodiscard +// https://clang.llvm.org/docs/LanguageExtensions.html#has-builtin +#ifndef __has_builtin +# define __has_builtin(builtin) false #endif -/** - * Hint to avoid inlining a function. - */ -#if __has_attribute(noinline) -# define attr_noinline __attribute__((noinline)) -#else -# define attr_noinline +// https://en.cppreference.com/w/c/language/attributes#Attribute_testing +#ifndef __has_c_attribute +# define __has_c_attribute(attr) false #endif -/** - * Hint that a function is unlikely to be called. - */ -#if __has_attribute(cold) -# define attr_cold attr_noinline __attribute__((cold)) -#else -# define attr_cold attr_noinline +// https://clang.llvm.org/docs/LanguageExtensions.html#has-feature-and-has-extension +#ifndef __has_feature +# define __has_feature(feat) false #endif -/** - * Adds compiler warnings for bad printf()-style function calls, if supported. - */ -#if __has_attribute(format) -# define attr_printf(fmt, args) __attribute__((format(printf, fmt, args))) -#else -# define attr_printf(fmt, args) +// https://en.cppreference.com/w/c/preprocessor/include +#ifndef __has_include +# define __has_include(header) false #endif -/** - * Annotates functions that potentially modify and return format strings. - */ -#if __has_attribute(format_arg) -# define attr_format_arg(arg) __attribute__((format_arg(arg))) -#else -# define attr_format_arg(args) -#endif +// Sanitizer macros (GCC defines these but Clang does not) -/** - * Annotates allocator-like functions. - */ -#if __has_attribute(malloc) -# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC -# define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__))) -# else -# define attr_malloc(...) attr_nodiscard __attribute__((malloc)) -# endif -#else -# define attr_malloc(...) attr_nodiscard +#if __has_feature(address_sanitizer) && !defined(__SANITIZE_ADDRESS__) +# define __SANITIZE_ADDRESS__ true #endif - -/** - * Specifies that a function returns allocations with a given alignment. - */ -#if __has_attribute(alloc_align) -# define attr_alloc_align(param) __attribute__((alloc_align(param))) -#else -# define attr_alloc_align(param) +#if __has_feature(memory_sanitizer) && !defined(__SANITIZE_MEMORY__) +# define __SANITIZE_MEMORY__ true #endif - -/** - * Specifies that a function returns allocations with a given size. - */ -#if __has_attribute(alloc_size) -# define attr_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) -#else -# define attr_alloc_size(...) +#if __has_feature(thread_sanitizer) && !defined(__SANITIZE_THREAD__) +# define __SANITIZE_THREAD__ true #endif -/** - * Shorthand for attr_alloc_align() and attr_alloc_size(). - */ -#define attr_aligned_alloc(align, ...) \ - attr_alloc_align(align) \ - attr_alloc_size(__VA_ARGS__) - -/** - * Check if function multiversioning via GNU indirect functions (ifunc) is supported. - */ -#ifndef BFS_USE_TARGET_CLONES -# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) -# define BFS_USE_TARGET_CLONES true -# endif -#endif - -/** - * Apply the target_clones attribute, if available. - */ -#if BFS_USE_TARGET_CLONES -# define attr_target_clones(...) __attribute__((target_clones(__VA_ARGS__))) -#else -# define attr_target_clones(...) -#endif - -/** - * Shorthand for multiple attributes at once. attr(a, b(c), d) is equivalent to - * - * attr_a - * attr_b(c) - * attr_d - */ -#define attr(...) \ - attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none, ) - -/** - * attr() helper. For exposition, pretend we support only 2 args, instead of 9. - * There are a few cases: - * - * attr() - * => attr__(attr_, none, none) - * => attr_ => - * attr_none => - * attr_too_many_none() => - * - * attr(a) - * => attr__(attr_a, none, none) - * => attr_a => __attribute__((a)) - * attr_none => - * attr_too_many_none() => - * - * attr(a, b(c)) - * => attr__(attr_a, b(c), none, none) - * => attr_a => __attribute__((a)) - * attr_b(c) => __attribute__((b(c))) - * attr_too_many_none(none) => - * - * attr(a, b(c), d) - * => attr__(attr_a, b(c), d, none, none) - * => attr_a => __attribute__((a)) - * attr_b(c) => __attribute__((b(c))) - * attr_too_many_d(none, none) => error - * - * Some attribute names are the same as standard library functions, e.g. printf. - * Standard libraries are permitted to define these functions as macros, like - * - * #define printf(...) __builtin_printf(__VA_ARGS__) - * - * The token paste in - * - * #define attr(...) attr__(attr_##__VA_ARGS__, none, none) - * - * is necessary to prevent macro expansion before evaluating attr__(). - * Otherwise, we could get - * - * attr(printf(1, 2)) - * => attr__(__builtin_printf(1, 2), none, none) - * => attr____builtin_printf(1, 2) - * => error - */ -#define attr__(a1, a2, a3, a4, a5, a6, a7, a8, a9, none, ...) \ - a1 \ - attr_##a2 \ - attr_##a3 \ - attr_##a4 \ - attr_##a5 \ - attr_##a6 \ - attr_##a7 \ - attr_##a8 \ - attr_##a9 \ - attr_too_many_##none(__VA_ARGS__) - -// Ignore `attr_none` from expanding 1-9 argument attr(a1, a2, ...) -#define attr_none -// Ignore `attr_` from expanding 0-argument attr() -#define attr_ -// Only trigger an error on more than 9 arguments -#define attr_too_many_none(...) - #endif // BFS_PRELUDE_H diff --git a/src/printf.c b/src/printf.c index 6b07c54..30ec201 100644 --- a/src/printf.c +++ b/src/printf.c @@ -1,9 +1,10 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "printf.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "bftw.h" #include "color.h" @@ -16,6 +17,7 @@ #include "mtab.h" #include "pwcache.h" #include "stat.h" + #include <errno.h> #include <grp.h> #include <pwd.h> @@ -89,7 +91,7 @@ static bool should_color(CFILE *cfile, const struct bfs_fmt *fmt) { (void)ret /** Return a dynamic format string. */ -attr(format_arg(2)) +_format_arg(2) static const char *dyn_fmt(const char *str, const char *fake) { bfs_assert(strcmp(str + strlen(str) - strlen(fake) + 1, fake + 1) == 0, "Mismatched format specifiers: '%s' vs. '%s'", str, fake); @@ -97,7 +99,7 @@ static const char *dyn_fmt(const char *str, const char *fake) { } /** Wrapper for fprintf(). */ -attr(printf(3, 4)) +_printf(3, 4) static int bfs_fprintf(CFILE *cfile, const struct bfs_fmt *fmt, const char *fake, ...) { va_list args; va_start(args, fake); @@ -560,7 +562,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } /** %Z: SELinux context */ -attr(maybe_unused) +_maybe_unused static int bfs_printf_Z(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { char *con = bfs_getfilecon(ftwbuf); if (!con) { @@ -706,7 +708,7 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha case '+': case ' ': must_be_numeric = true; - fallthru; + _fallthrough; case '-': if (strchr(fmt.str, c)) { bfs_expr_error(ctx, expr); diff --git a/src/pwcache.c b/src/pwcache.c index af8c237..fa19dad 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -1,16 +1,15 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "pwcache.h" + #include "alloc.h" #include "trie.h" + #include <errno.h> #include <grp.h> #include <pwd.h> #include <stdlib.h> -#include <string.h> -#include <unistd.h> /** Represents cache hits for negative results. */ static void *MISSING = &MISSING; diff --git a/src/sanity.h b/src/sanity.h index e168b8f..0b770cf 100644 --- a/src/sanity.h +++ b/src/sanity.h @@ -8,21 +8,8 @@ #ifndef BFS_SANITY_H #define BFS_SANITY_H -#include "prelude.h" #include <stddef.h> -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) -# define SANITIZE_ADDRESS true -#endif - -#if __has_feature(memory_sanitizer) || defined(__SANITIZE_MEMORY__) -# define SANITIZE_MEMORY true -#endif - -#if __has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__) -# define SANITIZE_THREAD true -#endif - // Call macro(ptr, size) or macro(ptr, sizeof(*ptr)) #define SANITIZE_CALL(...) \ SANITIZE_CALL_(__VA_ARGS__, ) @@ -33,7 +20,7 @@ #define SANITIZE_CALL__(macro, ptr, size, ...) \ macro(ptr, size) -#if SANITIZE_ADDRESS +#if __SANITIZE_ADDRESS__ # include <sanitizer/asan_interface.h> /** @@ -55,7 +42,7 @@ # define sanitize_free sanitize_uninit #endif -#if SANITIZE_MEMORY +#if __SANITIZE_MEMORY__ # include <sanitizer/msan_interface.h> /** @@ -85,7 +72,7 @@ /** * Initialize a variable, unless sanitizers would detect uninitialized uses. */ -#if SANITIZE_MEMORY +#if __SANITIZE_MEMORY__ # define uninit(value) #else # define uninit(value) = value diff --git a/src/sighook.c b/src/sighook.c index 6d6ff01..4356fdb 100644 --- a/src/sighook.c +++ b/src/sighook.c @@ -17,19 +17,29 @@ * drop to zero. Once it does, the old pointer can be safely freed. */ -#include "prelude.h" #include "sighook.h" + #include "alloc.h" #include "atomic.h" +#include "bfs.h" #include "bfstd.h" #include "diag.h" #include "thread.h" + #include <errno.h> +#include <pthread.h> #include <signal.h> #include <stdlib.h> #include <unistd.h> -#if _POSIX_SEMAPHORES > 0 +// NetBSD opens a file descriptor for each sem_init() +#if defined(_POSIX_SEMAPHORES) && !__NetBSD__ +# define BFS_POSIX_SEMAPHORES _POSIX_SEMAPHORES +#else +# define BFS_POSIX_SEMAPHORES (-1) +#endif + +#if BFS_POSIX_SEMAPHORES >= 0 # include <semaphore.h> #endif @@ -42,8 +52,8 @@ struct arc { /** The reference itself. */ void *ptr; -#if _POSIX_SEMAPHORES > 0 - /** A semaphore for arc_wake(). */ +#if BFS_POSIX_SEMAPHORES >= 0 + /** A semaphore for arc_wait(). */ sem_t sem; /** sem_init() result. */ int sem_status; @@ -57,8 +67,12 @@ 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); +#if BFS_POSIX_SEMAPHORES >= 0 + if (sysoption(SEMAPHORES) > 0) { + arc->sem_status = sem_init(&arc->sem, false, 0); + } else { + arc->sem_status = -1; + } #endif } @@ -93,7 +107,7 @@ static void arc_put(struct arc *arc) { size_t refs = fetch_sub(&arc->refs, 1, release); if (refs == 1) { -#if _POSIX_SEMAPHORES > 0 +#if BFS_POSIX_SEMAPHORES >= 0 if (arc->sem_status == 0 && sem_post(&arc->sem) != 0) { abort(); } @@ -103,7 +117,7 @@ static void arc_put(struct arc *arc) { /** Wait on the semaphore. */ static int arc_sem_wait(struct arc *arc) { -#if _POSIX_SEMAPHORES > 0 +#if BFS_POSIX_SEMAPHORES >= 0 if (arc->sem_status == 0) { while (sem_wait(&arc->sem) != 0) { bfs_everify(errno == EINTR, "sem_wait()"); @@ -144,9 +158,9 @@ done:; /** Destroy an arc. */ static void arc_destroy(struct arc *arc) { - bfs_assert(arc_refs(arc) <= 1); + bfs_assert(arc_refs(arc) == 0); -#if _POSIX_SEMAPHORES > 0 +#if BFS_POSIX_SEMAPHORES >= 0 if (arc->sem_status == 0) { bfs_everify(sem_destroy(&arc->sem) == 0, "sem_destroy()"); } @@ -166,14 +180,25 @@ struct rcu { /** Sentinel value for RCU, since arc uses NULL already. */ static void *RCU_NULL = &RCU_NULL; +/** Map NULL -> RCU_NULL. */ +static void *rcu_encode(void *ptr) { + return ptr ? ptr : RCU_NULL; +} + +/** Map RCU_NULL -> NULL. */ +static void *rcu_decode(void *ptr) { + bfs_assert(ptr != NULL); + return ptr == RCU_NULL ? NULL : ptr; +} + /** Initialize an RCU block. */ -static void rcu_init(struct rcu *rcu) { +static void rcu_init(struct rcu *rcu, void *ptr) { bfs_verify(atomic_is_lock_free(&rcu->active)); atomic_init(&rcu->active, 0); arc_init(&rcu->slots[0]); arc_init(&rcu->slots[1]); - arc_set(&rcu->slots[0], RCU_NULL); + arc_set(&rcu->slots[0], rcu_encode(ptr)); } /** Get the active slot. */ @@ -182,15 +207,20 @@ static struct arc *rcu_active(struct rcu *rcu) { return &rcu->slots[i]; } +/** Destroy an RCU block. */ +static void rcu_destroy(struct rcu *rcu) { + arc_wait(rcu_active(rcu)); + arc_destroy(&rcu->slots[1]); + arc_destroy(&rcu->slots[0]); +} + /** Read an RCU-protected pointer. */ static void *rcu_read(struct rcu *rcu, struct arc **slot) { while (true) { *slot = rcu_active(rcu); void *ptr = arc_get(*slot); - if (ptr == RCU_NULL) { - return NULL; - } else if (ptr) { - return ptr; + if (ptr) { + return rcu_decode(ptr); } // Otherwise, the other slot became active; retry } @@ -199,12 +229,7 @@ static void *rcu_read(struct rcu *rcu, struct arc **slot) { /** Get the RCU-protected pointer without acquiring a reference. */ static void *rcu_peek(struct rcu *rcu) { struct arc *arc = rcu_active(rcu); - void *ptr = arc->ptr; - if (ptr == RCU_NULL) { - return NULL; - } else { - return ptr; - } + return rcu_decode(arc->ptr); } /** Update an RCU-protected pointer, and return the old one. */ @@ -215,126 +240,66 @@ static void *rcu_update(struct rcu *rcu, void *ptr) { size_t j = i ^ 1; struct arc *next = &rcu->slots[j]; - arc_set(next, ptr ? ptr : RCU_NULL); + arc_set(next, rcu_encode(ptr)); store(&rcu->active, j, relaxed); - return arc_wait(prev); + return rcu_decode(arc_wait(prev)); } struct sighook { + /** The signal being hooked, or 0 for atsigexit(). */ int sig; + /** Signal hook flags. */ + enum sigflags flags; + /** The function to call. */ sighook_fn *fn; + /** An argument to pass to the function. */ void *arg; - enum sigflags flags; + + /** The RCU pointer to this hook. */ + struct rcu *self; + /** The next hook in the list. */ + struct rcu next; }; /** - * A table of signal hooks. + * An RCU-protected linked list of signal hooks. */ -struct sigtable { - /** The number of filled slots. */ - size_t filled; - /** The length of the array. */ - size_t size; - /** An array of signal hooks. */ - struct arc hooks[]; +struct siglist { + /** The first hook in the list. */ + struct rcu head; + /** &last->next */ + struct rcu *tail; }; -/** Add a hook to a table. */ -static int sigtable_add(struct sigtable *table, struct sighook *hook) { - if (!table || table->filled == table->size) { - return -1; - } - - for (size_t i = 0; i < table->size; ++i) { - struct arc *arc = &table->hooks[i]; - if (arc_refs(arc) == 0) { - arc_set(arc, hook); - ++table->filled; - return 0; - } - } - - return -1; -} - -/** 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; +/** Initialize a siglist. */ +static void siglist_init(struct siglist *list) { + rcu_init(&list->head, NULL); + list->tail = &list->head; } -/** 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); +/** Append a hook to a linked list. */ +static void sigpush(struct siglist *list, struct sighook *hook) { + hook->self = list->tail; + list->tail = &hook->next; + rcu_init(&hook->next, NULL); + rcu_update(hook->self, hook); } -/** 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; +/** Remove a hook from the linked list. */ +static void sigpop(struct siglist *list, struct sighook *hook) { + struct sighook *next = rcu_peek(&hook->next); + rcu_update(hook->self, next); + if (next) { + next->self = hook->self; } - - 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 sharded table of signal hooks. */ -static struct rcu rcu_sighooks[64]; -/** The table of atsigexit() hooks. */ -static struct rcu rcu_exithooks; +/** The lists of signal hooks. */ +static struct siglist sighooks[64]; -/** Get the table for a particular signal. */ -static struct rcu *sigshard(int sig) { - return &rcu_sighooks[sig % countof(rcu_sighooks)]; +/** Get the hook list for a particular signal. */ +static struct siglist *siglist(int sig) { + return &sighooks[sig % countof(sighooks)]; } /** Mutex for initialization and RCU writer exclusion. */ @@ -342,7 +307,7 @@ static pthread_mutex_t sigmutex = PTHREAD_MUTEX_INITIALIZER; /** Check if a signal was generated by userspace. */ static bool is_user_generated(const siginfo_t *info) { - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_03 // // If si_code is SI_USER or SI_QUEUE, or any value less than or // equal to 0, then the signal was generated by a process ... @@ -360,7 +325,7 @@ static bool is_fault(const siginfo_t *info) { } } -// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/signal.h.html +// https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/signal.h.html static const int FATAL_SIGNALS[] = { SIGABRT, SIGALRM, @@ -401,7 +366,7 @@ static bool is_fatal(int sig) { } #ifdef SIGRTMIN - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_01 // // The default actions for the realtime signals in the range // SIGRTMIN to SIGRTMAX shall be to terminate the process @@ -415,7 +380,8 @@ static bool is_fatal(int sig) { } /** Reraise a fatal signal. */ -static noreturn void reraise(int sig) { +_noreturn +static void reraise(int sig) { // Restore the default signal action if (signal(sig, SIG_DFL) == SIG_ERR) { goto fail; @@ -435,36 +401,29 @@ fail: } /** Find any matching hooks and run them. */ -static enum sigflags run_hooks(struct rcu *rcu, int sig, siginfo_t *info) { +static enum sigflags run_hooks(struct siglist *list, int sig, siginfo_t *info) { enum sigflags ret = 0; - struct arc *slot; - struct sigtable *table = rcu_read(rcu, &slot); - if (!table) { - goto done; - } - - for (size_t i = 0; i < table->size; ++i) { - struct arc *arc = &table->hooks[i]; - struct sighook *hook = arc_get(arc); - if (!hook) { - continue; - } + struct arc *slot = NULL; + struct sighook *hook = rcu_read(&list->head, &slot); + while (hook) { if (hook->sig == sig || hook->sig == 0) { hook->fn(sig, info, hook->arg); ret |= hook->flags; } - arc_put(arc); + + struct arc *prev = slot; + hook = rcu_read(&hook->next, &slot); + arc_put(prev); } -done: arc_put(slot); return ret; } /** Dispatches a signal to the registered handlers. */ static void sigdispatch(int sig, siginfo_t *info, void *context) { - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_03_03 // // The behavior of a process is undefined after it returns normally // from a signal-catching function for a SIGBUS, SIGFPE, SIGILL, or @@ -474,7 +433,7 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) { reraise(sig); } - // https://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html#tag_15_04_03 + // https://pubs.opengroup.org/onlinepubs/9799919799/functions/V2_chap02.html#tag_16_04_04 // // After returning from a signal-catching function, the value of // errno is unspecified if the signal-catching function or any @@ -484,12 +443,13 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) { int error = errno; // Run the normal hooks - struct rcu *shard = sigshard(sig); - enum sigflags flags = run_hooks(shard, sig, info); + struct siglist *list = siglist(sig); + enum sigflags flags = run_hooks(list, sig, info); // Run the atsigexit() hooks, if we're exiting if (!(flags & SH_CONTINUE) && is_fatal(sig)) { - run_hooks(&rcu_exithooks, sig, info); + list = siglist(0); + run_hooks(list, sig, info); reraise(sig); } @@ -512,10 +472,9 @@ static int siginit(int sig) { return -1; } - for (size_t i = 0; i < countof(rcu_sighooks); ++i) { - rcu_init(&rcu_sighooks[i]); + for (size_t i = 0; i < countof(sighooks); ++i) { + siglist_init(&sighooks[i]); } - rcu_init(&rcu_exithooks); initialized = true; } @@ -539,36 +498,32 @@ static int siginit(int sig) { } /** Shared sighook()/atsigexit() implementation. */ -static struct sighook *sighook_impl(struct rcu *rcu, int sig, sighook_fn *fn, void *arg, enum sigflags flags) { +static struct sighook *sighook_impl(int sig, sighook_fn *fn, void *arg, enum sigflags flags) { struct sighook *hook = ALLOC(struct sighook); if (!hook) { return NULL; } hook->sig = sig; + hook->flags = flags; hook->fn = fn; hook->arg = arg; - hook->flags = flags; - - if (rcu_sigtable_add(rcu, hook) != 0) { - free(hook); - return NULL; - } + struct siglist *list = siglist(sig); + sigpush(list, hook); return hook; } struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags) { + bfs_assert(sig > 0); + mutex_lock(&sigmutex); struct sighook *ret = NULL; - if (siginit(sig) != 0) { - goto done; + if (siginit(sig) == 0) { + ret = sighook_impl(sig, fn, arg, flags); } - struct rcu *shard = sigshard(sig); - ret = sighook_impl(shard, sig, fn, arg, flags); -done: mutex_unlock(&sigmutex); return ret; } @@ -589,7 +544,7 @@ struct sighook *atsigexit(sighook_fn *fn, void *arg) { } #endif - struct sighook *ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0); + struct sighook *ret = sighook_impl(0, fn, arg, 0); mutex_unlock(&sigmutex); return ret; } @@ -601,21 +556,11 @@ void sigunhook(struct sighook *hook) { mutex_lock(&sigmutex); - struct rcu *rcu; - if (hook->sig) { - rcu = sigshard(hook->sig); - } else { - rcu = &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); - } + struct siglist *list = siglist(hook->sig); + sigpop(list, hook); mutex_unlock(&sigmutex); + + rcu_destroy(&hook->next); free(hook); } @@ -1,12 +1,14 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "stat.h" + #include "atomic.h" +#include "bfs.h" #include "bfstd.h" #include "diag.h" #include "sanity.h" + #include <errno.h> #include <fcntl.h> #include <string.h> @@ -12,7 +12,8 @@ #ifndef BFS_STAT_H #define BFS_STAT_H -#include "prelude.h" +#include "bfs.h" + #include <sys/stat.h> #include <sys/types.h> #include <time.h> @@ -25,7 +26,7 @@ # define BFS_USE_STATX (BFS_HAS_STATX || BFS_HAS_STATX_SYSCALL) #endif -#if BFS_USE_SYS_PARAM_H +#if __has_include(<sys/param.h>) # include <sys/param.h> #endif diff --git a/src/thread.c b/src/thread.c index 3793896..d61ff8c 100644 --- a/src/thread.c +++ b/src/thread.c @@ -1,10 +1,11 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "thread.h" + #include "bfstd.h" #include "diag.h" + #include <errno.h> #include <pthread.h> diff --git a/src/thread.h b/src/thread.h index db11bd8..7d12468 100644 --- a/src/thread.h +++ b/src/thread.h @@ -8,17 +8,8 @@ #ifndef BFS_THREAD_H #define BFS_THREAD_H -#include "prelude.h" #include <pthread.h> -#if __STDC_VERSION__ < C23 && !defined(thread_local) -# if BFS_USE_THREADS_H -# include <threads.h> -# else -# define thread_local _Thread_local -# endif -#endif - /** Thread entry point type. */ typedef void *thread_fn(void *arg); @@ -81,21 +81,23 @@ * and insert intermediate singleton "jump" nodes when necessary. */ -#include "prelude.h" #include "trie.h" + #include "alloc.h" +#include "bfs.h" #include "bit.h" #include "diag.h" #include "list.h" + #include <stdint.h> #include <string.h> -bfs_static_assert(CHAR_WIDTH == 8); +static_assert(CHAR_WIDTH == 8, "This trie implementation assumes 8-bit bytes."); #if __i386__ || __x86_64__ -# define trie_clones attr(target_clones("popcnt", "default")) +# define _trie_clones _target_clones("popcnt", "default") #else -# define trie_clones +# define _trie_clones #endif /** Number of bits for the sparse array bitmap, aka the range of a nibble. */ @@ -190,7 +192,7 @@ static unsigned char trie_key_nibble(const void *key, size_t offset) { * that case, the first mismatch between the key and the representative will be * the depth at which to make a new branch to insert the key. */ -trie_clones +_trie_clones static struct trie_leaf *trie_representative(const struct trie *trie, const void *key, size_t length) { uintptr_t ptr = trie->root; if (!ptr) { @@ -221,7 +223,7 @@ struct trie_leaf *trie_find_str(const struct trie *trie, const char *key) { return trie_find_mem(trie, key, strlen(key) + 1); } -trie_clones +_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) { @@ -235,7 +237,7 @@ struct trie_leaf *trie_find_mem(const struct trie *trie, const void *key, size_t return trie_find_mem_impl(trie, key, length); } -trie_clones +_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); @@ -280,7 +282,7 @@ static bool trie_check_prefix(struct trie_leaf *leaf, size_t skip, const char *k } } -trie_clones +_trie_clones static struct trie_leaf *trie_find_prefix_impl(const struct trie *trie, const char *key) { uintptr_t ptr = trie->root; if (!ptr) { @@ -438,7 +440,7 @@ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t * | Z * +--->... */ -trie_clones +_trie_clones static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, struct trie_leaf *leaf, unsigned char nibble) { struct trie_node *node = trie_decode_node(*ptr); unsigned int size = count_ones(node->bitmap); @@ -561,7 +563,7 @@ struct trie_leaf *trie_insert_str(struct trie *trie, const char *key) { return trie_insert_mem(trie, key, strlen(key) + 1); } -trie_clones +_trie_clones static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key, size_t length) { struct trie_leaf *rep = trie_representative(trie, key, length); size_t mismatch = trie_mismatch(rep, key, length); @@ -663,7 +665,7 @@ static int trie_collapse_node(struct trie *trie, uintptr_t *parent, struct trie_ return 0; } -trie_clones +_trie_clones static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { uintptr_t *child = &trie->root; uintptr_t *parent = NULL; @@ -6,6 +6,7 @@ #include "alloc.h" #include "list.h" + #include <stddef.h> #include <stdint.h> @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "typo.h" + #include <limits.h> #include <stdint.h> #include <stdlib.h> @@ -125,7 +126,7 @@ int typo_distance(const char *actual, const char *expected) { // This is the Wagner-Fischer algorithm for Levenshtein distance, using // Manhattan distance on the keyboard for individual characters. - const int insert_cost = (40 + 12 + 1) / 2; + const int insert_cost = 12; size_t rows = strlen(actual) + 1; size_t cols = strlen(expected) + 1; diff --git a/src/version.c b/src/version.c index 8383350..7479a9f 100644 --- a/src/version.c +++ b/src/version.c @@ -1,7 +1,7 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" +#include "bfs.h" const char bfs_version[] = { #include "version.i" diff --git a/src/xregex.c b/src/xregex.c index 2d089b2..796544e 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -1,13 +1,15 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "xregex.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "diag.h" #include "sanity.h" #include "thread.h" + #include <errno.h> #include <pthread.h> #include <stdlib.h> diff --git a/src/xspawn.c b/src/xspawn.c index 2c64011..b3eed79 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -1,12 +1,14 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "xspawn.h" + #include "alloc.h" +#include "bfs.h" #include "bfstd.h" #include "diag.h" #include "list.h" + #include <errno.h> #include <fcntl.h> #include <signal.h> @@ -16,11 +18,11 @@ #include <sys/types.h> #include <unistd.h> -#if BFS_USE_PATHS_H +#if __has_include(<paths.h>) # include <paths.h> #endif -#if _POSIX_SPAWN > 0 +#if BFS_POSIX_SPAWN >= 0 # include <spawn.h> #endif @@ -70,29 +72,42 @@ int bfs_spawn_init(struct bfs_spawn *ctx) { ctx->flags = 0; SLIST_INIT(ctx); -#if _POSIX_SPAWN > 0 - ctx->flags |= BFS_SPAWN_USE_POSIX; +#if BFS_POSIX_SPAWN >= 0 + if (sysoption(SPAWN) > 0) { + ctx->flags |= BFS_SPAWN_USE_POSIX; - errno = posix_spawn_file_actions_init(&ctx->actions); - if (errno != 0) { - return -1; - } + errno = posix_spawn_file_actions_init(&ctx->actions); + if (errno != 0) { + return -1; + } - errno = posix_spawnattr_init(&ctx->attr); - if (errno != 0) { - posix_spawn_file_actions_destroy(&ctx->actions); - return -1; + errno = posix_spawnattr_init(&ctx->attr); + if (errno != 0) { + posix_spawn_file_actions_destroy(&ctx->actions); + return -1; + } } #endif return 0; } -int bfs_spawn_destroy(struct bfs_spawn *ctx) { -#if _POSIX_SPAWN > 0 - posix_spawnattr_destroy(&ctx->attr); - posix_spawn_file_actions_destroy(&ctx->actions); +/** + * Clear the BFS_SPAWN_USE_POSIX flag and free the attributes. + */ +static void bfs_spawn_clear_posix(struct bfs_spawn *ctx) { + if (ctx->flags & BFS_SPAWN_USE_POSIX) { + ctx->flags &= ~BFS_SPAWN_USE_POSIX; + +#if BFS_POSIX_SPAWN >= 0 + posix_spawnattr_destroy(&ctx->attr); + posix_spawn_file_actions_destroy(&ctx->actions); #endif + } +} + +int bfs_spawn_destroy(struct bfs_spawn *ctx) { + bfs_spawn_clear_posix(ctx); for_slist (struct bfs_spawn_action, action, ctx) { free(action); @@ -101,9 +116,9 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) { return 0; } -#if _POSIX_SPAWN > 0 +#if BFS_POSIX_SPAWN >= 0 /** Set some posix_spawnattr flags. */ -attr(maybe_unused) +_maybe_unused static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { short prev; errno = posix_spawnattr_getflags(&ctx->attr, &prev); @@ -121,7 +136,7 @@ static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { return 0; } -#endif // _POSIX_SPAWN > 0 +#endif /** Allocate a spawn action. */ static struct bfs_spawn_action *bfs_spawn_action(enum bfs_spawn_op op) { @@ -143,7 +158,7 @@ int bfs_spawn_addopen(struct bfs_spawn *ctx, int fd, const char *path, int flags return -1; } -#if _POSIX_SPAWN > 0 +#if BFS_POSIX_SPAWN >= 0 if (ctx->flags & BFS_SPAWN_USE_POSIX) { errno = posix_spawn_file_actions_addopen(&ctx->actions, fd, path, flags, mode); if (errno != 0) { @@ -167,7 +182,7 @@ int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { return -1; } -#if _POSIX_SPAWN > 0 +#if BFS_POSIX_SPAWN >= 0 if (ctx->flags & BFS_SPAWN_USE_POSIX) { errno = posix_spawn_file_actions_addclose(&ctx->actions, fd); if (errno != 0) { @@ -188,7 +203,7 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { return -1; } -#if _POSIX_SPAWN > 0 +#if BFS_POSIX_SPAWN >= 0 if (ctx->flags & BFS_SPAWN_USE_POSIX) { errno = posix_spawn_file_actions_adddup2(&ctx->actions, oldfd, newfd); if (errno != 0) { @@ -228,7 +243,7 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { # define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir_np #endif -#if _POSIX_SPAWN > 0 && defined(BFS_POSIX_SPAWN_FCHDIR) +#if BFS_POSIX_SPAWN >= 0 && defined(BFS_POSIX_SPAWN_ADDFCHDIR) if (ctx->flags & BFS_SPAWN_USE_POSIX) { errno = BFS_POSIX_SPAWN_ADDFCHDIR(&ctx->actions, fd); if (errno != 0) { @@ -237,7 +252,7 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { } } #else - ctx->flags &= ~BFS_SPAWN_USE_POSIX; + bfs_spawn_clear_posix(ctx); #endif action->in_fd = fd; @@ -261,7 +276,7 @@ int bfs_spawn_setrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit goto fail; } #else - ctx->flags &= ~BFS_SPAWN_USE_POSIX; + bfs_spawn_clear_posix(ctx); #endif action->resource = resource; @@ -482,7 +497,7 @@ fail: return -1; } -#if _POSIX_SPAWN > 0 +#if BFS_POSIX_SPAWN >= 0 /** bfs_spawn() implementation using posix_spawn(). */ static pid_t bfs_posix_spawn(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) { @@ -516,10 +531,11 @@ static bool bfs_use_posix_spawn(const struct bfs_resolver *res, const struct bfs return true; } -#endif // _POSIX_SPAWN > 0 +#endif // BFS_POSIX_SPAWN >= 0 /** Actually exec() the new process. */ -static noreturn void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { +_noreturn +static void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { xclose(pipefd[0]); for_slist (const struct bfs_spawn_action, action, ctx) { @@ -612,15 +628,19 @@ static pid_t bfs_fork_spawn(struct bfs_resolver *res, const struct bfs_spawn *ct goto fail; } +#if BFS_HAS__FORK + pid_t pid = _Fork(); +#else pid_t pid = fork(); +#endif if (pid == 0) { // Child bfs_spawn_exec(res, ctx, argv, envp, pipefd); } // Restore the original signal mask - int ret = pthread_sigmask(SIG_SETMASK, &old_mask, NULL); - bfs_everify(ret == 0, "pthread_sigmask()"); + errno = pthread_sigmask(SIG_SETMASK, &old_mask, NULL); + bfs_everify(errno == 0, "pthread_sigmask()"); if (pid < 0) { // fork() failed @@ -648,7 +668,7 @@ fail: /** Call the right bfs_spawn() implementation. */ static pid_t bfs_spawn_impl(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) { -#if _POSIX_SPAWN > 0 +#if BFS_POSIX_SPAWN >= 0 if (bfs_use_posix_spawn(res, ctx)) { return bfs_posix_spawn(res, ctx, argv, envp); } diff --git a/src/xspawn.h b/src/xspawn.h index 6a8f54a..9cfbaf7 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -8,12 +8,17 @@ #ifndef BFS_XSPAWN_H #define BFS_XSPAWN_H -#include "prelude.h" #include <sys/resource.h> #include <sys/types.h> #include <unistd.h> -#if _POSIX_SPAWN > 0 +#ifdef _POSIX_SPAWN +# define BFS_POSIX_SPAWN _POSIX_SPAWN +#else +# define BFS_POSIX_SPAWN (-1) +#endif + +#if BFS_POSIX_SPAWN >= 0 # include <spawn.h> #endif @@ -38,7 +43,7 @@ struct bfs_spawn { struct bfs_spawn_action *head; struct bfs_spawn_action **tail; -#if _POSIX_SPAWN > 0 +#if BFS_POSIX_SPAWN >= 0 /** posix_spawn() context, for when we can use it. */ posix_spawn_file_actions_t actions; posix_spawnattr_t attr; diff --git a/src/xtime.c b/src/xtime.c index 186651b..dd9b60f 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -1,11 +1,12 @@ // Copyright © Tavian Barnes <tavianator@tavianator.com> // SPDX-License-Identifier: 0BSD -#include "prelude.h" #include "xtime.h" + +#include "bfs.h" #include "bfstd.h" #include "diag.h" -#include "sanity.h" + #include <errno.h> #include <limits.h> #include <sys/time.h> @@ -36,7 +37,7 @@ int xmktime(struct tm *tm, time_t *timep) { } // FreeBSD is missing an interceptor -#if BFS_HAS_TIMEGM && !(__FreeBSD__ && SANITIZE_MEMORY) +#if BFS_HAS_TIMEGM && !(__FreeBSD__ && __SANITIZE_MEMORY__) int xtimegm(struct tm *tm, time_t *timep) { time_t time = timegm(tm); @@ -350,17 +351,3 @@ invalid: error: return -1; } - -int xgettime(struct timespec *result) { -#if _POSIX_TIMERS > 0 - return clock_gettime(CLOCK_REALTIME, result); -#else - struct timeval tv; - int ret = gettimeofday(&tv, NULL); - if (ret == 0) { - result->tv_sec = tv.tv_sec; - result->tv_nsec = tv.tv_usec * 1000L; - } - return ret; -#endif -} diff --git a/src/xtime.h b/src/xtime.h index fb60ae4..a3547a7 100644 --- a/src/xtime.h +++ b/src/xtime.h @@ -46,14 +46,4 @@ int xtimegm(struct tm *tm, time_t *timep); */ int xgetdate(const char *str, struct timespec *result); -/** - * Get the current time. - * - * @param[out] result - * A pointer to the result. - * @return - * 0 on success, -1 on failure. - */ -int xgettime(struct timespec *result); - #endif // BFS_XTIME_H |