diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/alloc.c | 2 | ||||
-rw-r--r-- | src/alloc.h | 22 | ||||
-rw-r--r-- | src/bar.c | 18 | ||||
-rw-r--r-- | src/bfs.h | 120 | ||||
-rw-r--r-- | src/bfstd.c | 132 | ||||
-rw-r--r-- | src/bfstd.h | 63 | ||||
-rw-r--r-- | src/bftw.c | 6 | ||||
-rw-r--r-- | src/color.c | 218 | ||||
-rw-r--r-- | src/color.h | 4 | ||||
-rw-r--r-- | src/diag.c | 2 | ||||
-rw-r--r-- | src/diag.h | 118 | ||||
-rw-r--r-- | src/dstring.c | 1 | ||||
-rw-r--r-- | src/dstring.h | 56 | ||||
-rw-r--r-- | src/eval.c | 6 | ||||
-rw-r--r-- | src/exec.c | 2 | ||||
-rw-r--r-- | src/fsade.c | 8 | ||||
-rw-r--r-- | src/ioq.c | 6 | ||||
-rw-r--r-- | src/list.h | 236 | ||||
-rw-r--r-- | src/mtab.c | 2 | ||||
-rw-r--r-- | src/opt.c | 31 | ||||
-rw-r--r-- | src/parse.c | 101 | ||||
-rw-r--r-- | src/prelude.h | 28 | ||||
-rw-r--r-- | src/printf.c | 8 | ||||
-rw-r--r-- | src/sanity.h | 14 | ||||
-rw-r--r-- | src/sighook.c | 2 | ||||
-rw-r--r-- | src/thread.c | 9 | ||||
-rw-r--r-- | src/trie.c | 17 | ||||
-rw-r--r-- | src/trie.h | 1 | ||||
-rw-r--r-- | src/xspawn.c | 82 |
29 files changed, 801 insertions, 514 deletions
diff --git a/src/alloc.c b/src/alloc.c index f505eda..3cf9026 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -173,7 +173,7 @@ void arena_init(struct arena *arena, size_t align, size_t size) { } /** Allocate a new slab. */ -_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 1fafbab..4f21ed0 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -131,8 +131,8 @@ static inline size_t flex_size(size_t align, size_t offset, size_t size, size_t * @return * The allocated memory, or NULL on failure. */ -_malloc(free, 1) -_aligned_alloc(1, 2) +[[_malloc(free, 1)]] +[[_aligned_alloc(1, 2)]] void *alloc(size_t align, size_t size); /** @@ -145,8 +145,8 @@ void *alloc(size_t align, size_t size); * @return * The allocated memory, or NULL on failure. */ -_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. */ @@ -187,8 +187,8 @@ void *zalloc(size_t align, size_t size); * @return * The reallocated memory, or NULL on failure. */ -_aligned_alloc(2, 4) -_nodiscard +[[_nodiscard]] +[[_aligned_alloc(2, 4)]] void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size); /** Reallocate memory for an array. */ @@ -214,7 +214,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. */ -_nodiscard +[[_nodiscard]] void *reserve(void *ptr, size_t align, size_t size, size_t count); /** @@ -272,7 +272,7 @@ void arena_free(struct arena *arena, void *ptr); /** * Allocate an object out of the arena. */ -_malloc(arena_free, 2) +[[_malloc(arena_free, 2)]] void *arena_alloc(struct arena *arena); /** @@ -353,7 +353,7 @@ void varena_free(struct varena *varena, void *ptr, size_t count); * @return * The allocated struct, or NULL on failure. */ -_malloc(varena_free, 2) +[[_malloc(varena_free, 2)]] void *varena_alloc(struct varena *varena, size_t count); /** @@ -370,7 +370,7 @@ void *varena_alloc(struct varena *varena, size_t count); * @return * The resized struct, or NULL on failure. */ -_nodiscard +[[_nodiscard]] void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count); /** @@ -385,7 +385,7 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t * @return * The resized struct, or NULL on failure. */ -_nodiscard +[[_nodiscard]] void *varena_grow(struct varena *varena, void *ptr, size_t *count); /** @@ -18,7 +18,6 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> -#include <sys/ioctl.h> #include <termios.h> #include <unistd.h> @@ -33,25 +32,14 @@ struct bfs_bar { /** Get the terminal size, if possible. */ static int bfs_bar_getsize(struct bfs_bar *bar) { -#if BFS_HAS_TCGETWINSIZE || defined(TIOCGWINSZ) struct winsize ws; - -# if BFS_HAS_TCGETWINSIZE - int ret = tcgetwinsize(bar->fd, &ws); -# else - int ret = ioctl(bar->fd, TIOCGWINSZ, &ws); -# endif - if (ret != 0) { - return ret; + if (xtcgetwinsize(bar->fd, &ws) != 0) { + return -1; } store(&bar->width, ws.ws_col, relaxed); store(&bar->height, ws.ws_row, relaxed); return 0; -#else - errno = ENOTSUP; - return -1; -#endif } /** Write a string to the status bar (async-signal-safe). */ @@ -139,7 +127,7 @@ static void bfs_bar_sigexit(int sig, siginfo_t *info, void *arg) { } /** printf() to the status bar with a single write(). */ -_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); @@ -8,6 +8,9 @@ #ifndef BFS_H #define BFS_H +#include <assert.h> // For __GLIBC__ +#include <stddef.h> // For offsetof + // Standard versions /** Possible __STDC_VERSION__ values. */ @@ -53,15 +56,36 @@ 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. + * Given `ptr = &t->member`, return `t`. + */ +#define container_of(ptr, type, member) \ + (container_of_typecheck(ptr, type, member), \ + (type *)((char *)ptr - offsetof(type, member))) + +#define container_of_typecheck(ptr, type, field) \ + (void)sizeof(ptr - &((type *)NULL)->field) + +/** + * A preprocessor conditional. + * + * BFS_VA_IF(A)(B)(C) => B + * BFS_VA_IF( )(B)(C) => C */ -#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__])) +#define BFS_VA_IF(...) BFS_VA_IF_AB ## __VA_OPT__(C) +// BFS_VA_IF(A)(B)(C) => BFS_VA_IF_ABC(B)(C) +// BFS_VA_IF( )(B)(C) => BFS_VA_IF_AB(B)(C) + +#define BFS_VA_IF_ABC(...) __VA_ARGS__ BFS_VA_IGNORE +// BFS_VA_IF_ABC(B)(C) => B BFS_VA_IGNORE(C) + +#define BFS_VA_IF_AB(...) BFS_VA_REPEAT +// BFS_VA_IF_AB(B)(C) => BFS_VA_REPEAT(C) + +#define BFS_VA_IGNORE(...) +#define BFS_VA_REPEAT(...) __VA_ARGS__ /** * False sharing/destructive interference/largest cache line size. @@ -89,19 +113,12 @@ extern const char bfs_ldlibs[]; // 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)) +#if __has_c_attribute(maybe_unused) +# define _maybe_unused maybe_unused +#elif __has_c_attribute(gnu::unused) +# define _maybe_unused gnu::unused #else # define _maybe_unused #endif @@ -109,8 +126,10 @@ extern const char bfs_ldlibs[]; /** * Warn if a value is unused. */ -#if __has_attribute(warn_unused_result) -# define _nodiscard __attribute__((warn_unused_result)) +#if __has_c_attribute(nodiscard) +# define _nodiscard nodiscard +#elif __has_c_attribute(gnu::warn_unused_result) +# define _nodiscard gnu::warn_unused_result #else # define _nodiscard #endif @@ -118,35 +137,38 @@ extern const char bfs_ldlibs[]; /** * Hint to avoid inlining a function. */ -#if __has_attribute(noinline) -# define _noinline __attribute__((noinline)) +#if __has_c_attribute(gnu::noinline) +# define _noinline gnu::noinline #else # define _noinline #endif /** - * Marks a non-returning function. + * Hint that a function is unlikely to be called. */ -#if __STDC_VERSION__ >= C23 -# define _noreturn [[noreturn]] +#if __has_c_attribute(gnu::cold) +# define _cold _noinline, gnu::cold #else -# define _noreturn _Noreturn +# define _cold _noinline #endif /** - * Hint that a function is unlikely to be called. + * Marks a non-returning function. */ -#if __has_attribute(cold) -# define _cold _noinline __attribute__((cold)) +#if __has_c_attribute(noreturn) +# define _noreturn noreturn +#elif __has_c_attribute(gnu::noreturn) +# define _noreturn gnu::noreturn #else -# define _cold _noinline +# define _noreturn #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))) +#if __has_c_attribute(gnu::format) +# define _printf(fmt, args) gnu::format(printf, fmt, args) #else # define _printf(fmt, args) #endif @@ -154,8 +176,8 @@ extern const char bfs_ldlibs[]; /** * Annotates functions that potentially modify and return format strings. */ -#if __has_attribute(format_arg) -# define _format_arg(arg) __attribute__((format_arg(arg))) +#if __has_c_attribute(gnu::format_arg) +# define _format_arg(arg) gnu::format_arg(arg) #else # define _format_arg(arg) #endif @@ -163,11 +185,11 @@ extern const char bfs_ldlibs[]; /** * Annotates allocator-like functions. */ -#if __has_attribute(malloc) +#if __has_c_attribute(gnu::malloc) # if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC -# define _malloc(...) _nodiscard __attribute__((malloc(__VA_ARGS__))) +# define _malloc(...) _nodiscard, gnu::malloc(__VA_ARGS__) # else -# define _malloc(...) _nodiscard __attribute__((malloc)) +# define _malloc(...) _nodiscard, gnu::malloc # endif #else # define _malloc(...) _nodiscard @@ -176,8 +198,8 @@ extern const char bfs_ldlibs[]; /** * Specifies that a function returns allocations with a given alignment. */ -#if __has_attribute(alloc_align) -# define _alloc_align(param) __attribute__((alloc_align(param))) +#if __has_c_attribute(gnu::alloc_align) +# define _alloc_align(param) gnu::alloc_align(param) #else # define _alloc_align(param) #endif @@ -185,8 +207,8 @@ extern const char bfs_ldlibs[]; /** * Specifies that a function returns allocations with a given size. */ -#if __has_attribute(alloc_size) -# define _alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) +#if __has_c_attribute(gnu::alloc_size) +# define _alloc_size(...) gnu::alloc_size(__VA_ARGS__) #else # define _alloc_size(...) #endif @@ -194,7 +216,7 @@ extern const char bfs_ldlibs[]; /** * Shorthand for _alloc_align() and _alloc_size(). */ -#define _aligned_alloc(align, ...) _alloc_align(align) _alloc_size(__VA_ARGS__) +#define _aligned_alloc(align, ...) _alloc_align(align), _alloc_size(__VA_ARGS__) /** * Check if function multiversioning via GNU indirect functions (ifunc) is supported. @@ -202,7 +224,10 @@ extern const char bfs_ldlibs[]; * 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__ +# if __has_c_attribute(gnu::target_clones) \ + && (__GLIBC__ || __FreeBSD__) \ + && !__SANITIZE_THREAD__ \ + && !__SANITIZE_TYPE__ # define BFS_USE_TARGET_CLONES true # else # define BFS_USE_TARGET_CLONES false @@ -213,12 +238,23 @@ extern const char bfs_ldlibs[]; * Apply the target_clones attribute, if available. */ #if BFS_USE_TARGET_CLONES -# define _target_clones(...) __attribute__((target_clones(__VA_ARGS__))) +# define _target_clones(...) gnu::target_clones(__VA_ARGS__) #else # define _target_clones(...) #endif /** + * Mark the size of a flexible array member. + */ +#if __has_c_attribute(clang::counted_by) +# define _counted_by(...) clang::counted_by(__VA_ARGS__) +#elif __has_c_attribute(gnu::counted_by) +# define _counted_by(...) gnu::counted_by(__VA_ARGS__) +#else +# define _counted_by(...) +#endif + +/** * Optimization hint to not unroll a loop. */ #if BFS_HAS_PRAGMA_NOUNROLL diff --git a/src/bfstd.c b/src/bfstd.c index 82663eb..b78af7a 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -23,10 +23,12 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <sys/ioctl.h> #include <sys/resource.h> #include <sys/stat.h> #include <sys/types.h> #include <sys/wait.h> +#include <termios.h> #include <unistd.h> #include <wchar.h> @@ -187,16 +189,6 @@ char *xgetdelim(FILE *file, char delim) { } } -int open_cterm(int flags) { - char path[L_ctermid]; - if (ctermid(path) == NULL || strlen(path) == 0) { - errno = ENOTTY; - return -1; - } - - return open(path, flags); -} - const char *xgetprogname(void) { const char *cmd = NULL; #if BFS_HAS_GETPROGNAME @@ -243,6 +235,36 @@ static int xstrtox_epilogue(const char *str, char **end, char *endp) { return 0; } +int xstrtos(const char *str, char **end, int base, short *value) { + long n; + if (xstrtol(str, end, base, &n) != 0) { + return -1; + } + + if (n < SHRT_MIN || n > SHRT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +int xstrtoi(const char *str, char **end, int base, int *value) { + long n; + if (xstrtol(str, end, base, &n) != 0) { + return -1; + } + + if (n < INT_MIN || n > INT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + int xstrtol(const char *str, char **end, int base, long *value) { if (xstrtox_prologue(str) != 0) { return -1; @@ -283,6 +305,70 @@ int xstrtod(const char *str, char **end, double *value) { return xstrtox_epilogue(str, end, endp); } +int xstrtous(const char *str, char **end, int base, unsigned short *value) { + unsigned long n; + if (xstrtoul(str, end, base, &n) != 0) { + return -1; + } + + if (n > USHRT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +int xstrtoui(const char *str, char **end, int base, unsigned int *value) { + unsigned long n; + if (xstrtoul(str, end, base, &n) != 0) { + return -1; + } + + if (n > UINT_MAX) { + errno = ERANGE; + return -1; + } + + *value = n; + return 0; +} + +/** Common epilogue for xstrtou*() wrappers. */ +static int xstrtoux_epilogue(const char *str, char **end, char *endp) { + if (xstrtox_epilogue(str, end, endp) != 0) { + return -1; + } + + if (str[0] == '-') { + errno = ERANGE; + return -1; + } + + return 0; +} + +int xstrtoul(const char *str, char **end, int base, unsigned long *value) { + if (xstrtox_prologue(str) != 0) { + return -1; + } + + char *endp; + *value = strtoul(str, &endp, base); + return xstrtoux_epilogue(str, end, endp); +} + +int xstrtoull(const char *str, char **end, int base, unsigned long long *value) { + if (xstrtox_prologue(str) != 0) { + return -1; + } + + char *endp; + *value = strtoull(str, &endp, base); + return xstrtoux_epilogue(str, end, endp); +} + /** Compile and execute a regular expression for xrpmatch(). */ static int xrpregex(nl_item item, const char *response) { const char *pattern = nl_langinfo(item); @@ -558,6 +644,32 @@ pid_t xwaitpid(pid_t pid, int *status, int flags) { return ret; } +int open_cterm(int flags) { + char path[L_ctermid]; + if (ctermid(path) == NULL || strlen(path) == 0) { + errno = ENOTTY; + return -1; + } + + return open(path, flags); +} + +int xtcgetwinsize(int fd, struct winsize *ws) { +#if BFS_HAS_TCGETWINSIZE + return tcgetwinsize(fd, ws); +#else + return ioctl(fd, TIOCGWINSZ, ws); +#endif +} + +int xtcsetwinsize(int fd, const struct winsize *ws) { +#if BFS_HAS_TCSETWINSIZE + return tcsetwinsize(fd, ws); +#else + return ioctl(fd, TIOCSWINSZ, ws); +#endif +} + int dup_cloexec(int fd) { #ifdef F_DUPFD_CLOEXEC return fcntl(fd, F_DUPFD_CLOEXEC, 0); diff --git a/src/bfstd.h b/src/bfstd.h index 28f473e..15dd949 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -158,16 +158,6 @@ FILE *xfopen(const char *path, int flags); */ char *xgetdelim(FILE *file, char delim); -/** - * Open the controlling terminal. - * - * @flags - * The open() flags. - * @return - * An open file descriptor, or -1 on failure. - */ -int open_cterm(int flags); - // #include <stdlib.h> /** @@ -179,6 +169,16 @@ int open_cterm(int flags); const char *xgetprogname(void); /** + * Like xstrtol(), but for short. + */ +int xstrtos(const char *str, char **end, int base, short *value); + +/** + * Like xstrtol(), but for int. + */ +int xstrtoi(const char *str, char **end, int base, int *value); + +/** * Wrapper for strtol() that forbids leading spaces. */ int xstrtol(const char *str, char **end, int base, long *value); @@ -189,6 +189,26 @@ int xstrtol(const char *str, char **end, int base, long *value); int xstrtoll(const char *str, char **end, int base, long long *value); /** + * Like xstrtoul(), but for unsigned short. + */ +int xstrtous(const char *str, char **end, int base, unsigned short *value); + +/** + * Like xstrtoul(), but for unsigned int. + */ +int xstrtoui(const char *str, char **end, int base, unsigned int *value); + +/** + * Wrapper for strtoul() that forbids leading spaces, negatives. + */ +int xstrtoul(const char *str, char **end, int base, unsigned long *value); + +/** + * Wrapper for strtoull() that forbids leading spaces, negatives. + */ +int xstrtoull(const char *str, char **end, int base, unsigned long long *value); + +/** * Wrapper for strtof() that forbids leading spaces. */ int xstrtof(const char *str, char **end, float *value); @@ -342,6 +362,29 @@ int xminor(dev_t dev); */ pid_t xwaitpid(pid_t pid, int *status, int flags); +#include <sys/ioctl.h> // May be necessary for struct winsize +#include <termios.h> + +/** + * Open the controlling terminal. + * + * @flags + * The open() flags. + * @return + * An open file descriptor, or -1 on failure. + */ +int open_cterm(int flags); + +/** + * tcgetwinsize()/ioctl(TIOCGWINSZ) wrapper. + */ +int xtcgetwinsize(int fd, struct winsize *ws); + +/** + * tcsetwinsize()/ioctl(TIOCSWINSZ) wrapper. + */ +int xtcsetwinsize(int fd, const struct winsize *ws); + // #include <unistd.h> /** @@ -253,6 +253,7 @@ struct bftw_file { /** The length of the file's name. */ size_t namelen; /** The file's name. */ + // [[_counted_by(namelen + 1)]] char name[]; }; @@ -1439,7 +1440,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; } - _fallthrough; + [[fallthrough]]; default: #if __linux__ @@ -1485,7 +1486,8 @@ fail: /** Check if we should stat() a file asynchronously. */ static bool bftw_should_ioq_stat(struct bftw_state *state, struct bftw_file *file) { - // To avoid surprising users too much, process the roots in order + // POSIX wants the root paths to be processed in order + // See https://www.austingroupbugs.net/view.php?id=1859 if (file->depth == 0) { return false; } diff --git a/src/color.c b/src/color.c index 588dbac..2d0fc9c 100644 --- a/src/color.c +++ b/src/color.c @@ -32,6 +32,7 @@ struct esc_seq { /** The length of the escape sequence. */ size_t len; /** The escape sequence itself, without a terminating NUL. */ + [[_counted_by(len)]] char seq[]; }; @@ -48,6 +49,7 @@ struct ext_color { /** Whether the comparison should be case-sensitive. */ bool case_sensitive; /** The extension to match (NUL-terminated). */ + // [[_counted_by(len + 1)]] char ext[]; }; @@ -103,6 +105,8 @@ struct colors { struct esc_seq *pipe; struct esc_seq *socket; + struct esc_seq *dataless; + /** A mapping from color names (fi, di, ln, etc.) to struct fields. */ struct trie names; @@ -161,26 +165,32 @@ static int cat_esc(dchar **dstr, const struct esc_seq *seq) { return dstrxcat(dstr, seq->seq, seq->len); } -/** Set a named escape sequence. */ -static int set_esc(struct colors *colors, const char *name, dchar *value) { - struct esc_seq **field = get_esc(colors, name); - if (!field) { - return 0; +/** Set an escape sequence field. */ +static int set_esc_field(struct colors *colors, struct esc_seq **field, const dchar *value) { + struct esc_seq *seq = NULL; + if (value) { + seq = new_esc(colors, value, dstrlen(value)); + if (!seq) { + return -1; + } } if (*field) { free_esc(colors, *field); - *field = NULL; } + *field = seq; - if (value) { - *field = new_esc(colors, value, dstrlen(value)); - if (!*field) { - return -1; - } + return 0; +} + +/** Set a named escape sequence. */ +static int set_esc(struct colors *colors, const char *name, const dchar *value) { + struct esc_seq **field = get_esc(colors, name); + if (!field) { + return 0; } - return 0; + return set_esc_field(colors, field, value); } /** Reverse a string, to turn suffix matches into prefix matches. */ @@ -607,6 +617,109 @@ fail: return ret; } +/** Parse the FreeBSD $LSCOLORS format. */ +static int parse_bsd_ls_colors(struct colors *colors, const char *lscolors) { + static const char *fg_codes[256] = { + // 0-7: deprecated aliases for a-h + ['0'] = "30", ['1'] = "31", ['2'] = "32", ['3'] = "33", + ['4'] = "34", ['5'] = "35", ['6'] = "36", ['7'] = "37", + // a-h: first 8 ANSI foreground colors + ['a'] = "30", ['b'] = "31", ['c'] = "32", ['d'] = "33", + ['e'] = "34", ['f'] = "35", ['g'] = "36", ['h'] = "37", + // x: default foreground + ['x'] = "39", + // A-H: bold foreground colors + ['A'] = "1;30", ['B'] = "1;31", ['C'] = "1;32", ['D'] = "1;33", + ['E'] = "1;34", ['F'] = "1;35", ['G'] = "1;36", ['H'] = "1;37", + // X: bold default foreground + ['X'] = "1;39", + }; + + static const char *bg_codes[256] = { + // 0-7: deprecated aliases for a-h + ['0'] = "40", ['1'] = "41", ['2'] = "42", ['3'] = "43", + ['4'] = "44", ['5'] = "45", ['6'] = "46", ['7'] = "47", + // a-h: first 8 ANSI background colors + ['a'] = "40", ['b'] = "41", ['c'] = "42", ['d'] = "43", + ['e'] = "44", ['f'] = "45", ['g'] = "46", ['h'] = "47", + // x: default background + ['x'] = "49", + // A-H: background colors + underline + ['A'] = "4;40", ['B'] = "4;41", ['C'] = "4;42", ['D'] = "4;43", + ['E'] = "4;44", ['F'] = "4;45", ['G'] = "4;46", ['H'] = "4;47", + // X: default background + underline + ['X'] = "4;49", + }; + + // Please refer to https://man.freebsd.org/cgi/man.cgi?ls(1)#ENVIRONMENT + char complete_colors[] = "exfxcxdxbxegedabagacadah"; + + // For short $LSCOLORS, use the default colors for the rest + size_t max = strlen(complete_colors); + size_t len = strnlen(lscolors, max); + memcpy(complete_colors, lscolors, len); + + struct esc_seq **keys[] = { + &colors->directory, + &colors->link, + &colors->socket, + &colors->pipe, + &colors->executable, + &colors->blockdev, + &colors->chardev, + &colors->setuid, + &colors->setgid, + &colors->sticky_other_writable, + &colors->other_writable, + &colors->dataless, + }; + + dchar *buf = dstralloc(8); + if (!buf) { + return -1; + } + + int ret = -1; + for (size_t i = 0; i < countof(keys); ++i) { + uint8_t fg = complete_colors[2 * i]; + uint8_t bg = complete_colors[2 * i + 1]; + + const char *fg_code = fg_codes[fg]; + const char *bg_code = bg_codes[bg]; + + dstrshrink(buf, 0); + if (fg_code) { + if (dstrcat(&buf, fg_code) != 0) { + goto fail; + } + } + if (fg_code && bg_code) { + if (dstrcat(&buf, ";") != 0) { + goto fail; + } + } + if (bg_code) { + if (dstrcat(&buf, bg_code) != 0) { + goto fail; + } + } + + const dchar *value = dstrlen(buf) > 0 ? buf : NULL; + if (set_esc_field(colors, keys[i], value) != 0) { + goto fail; + } + } + + ret = 0; +fail: + dstrfree(buf); + return ret; +} + +static bool str_isset(const char *str) { + return str && *str; +} + struct colors *parse_colors(void) { struct colors *colors = ALLOC(struct colors); if (!colors) { @@ -672,16 +785,28 @@ struct colors *parse_colors(void) { fail = fail || init_esc(colors, "pi", "33", &colors->pipe); fail = fail || init_esc(colors, "so", "01;35", &colors->socket); + colors->dataless = NULL; + if (fail) { goto fail; } - if (parse_gnu_ls_colors(colors, getenv("LS_COLORS")) != 0) { - goto fail; - } - if (parse_gnu_ls_colors(colors, getenv("BFS_COLORS")) != 0) { - goto fail; + const char *gnu_colors = getenv("LS_COLORS"); + const char *bfs_colors = getenv("BFS_COLORS"); + const char *bsd_colors = getenv("LSCOLORS"); + if (str_isset(gnu_colors) || str_isset(bfs_colors)) { + if (parse_gnu_ls_colors(colors, gnu_colors) != 0) { + goto fail; + } + if (parse_gnu_ls_colors(colors, bfs_colors) != 0) { + goto fail; + } + } else if (str_isset(bsd_colors)) { + if (parse_bsd_ls_colors(colors, bsd_colors) != 0) { + goto fail; + } } + if (build_iext_trie(colors) != 0) { goto fail; } @@ -949,6 +1074,34 @@ static bool cpath_is_broken(const struct cpath *cpath) { } } +/** Check if we need a statbuf to colorize a file. */ +static bool must_stat(const struct colors *colors, enum bfs_type type) { + switch (type) { + case BFS_REG: + if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) { + return true; + } + +#ifdef ST_DATALESS + if (colors->dataless) { + return true; + } +#endif + + return false; + + case BFS_DIR: + if (colors->sticky_other_writable || colors->other_writable || colors->sticky) { + return true; + } + + return false; + + default: + return false; + } +} + /** Get the color for a file. */ static const struct esc_seq *file_color(const struct colors *colors, const struct cpath *cpath) { enum bfs_type type; @@ -963,17 +1116,17 @@ static const struct esc_seq *file_color(const struct colors *colors, const struc } const struct bfs_stat *statbuf = NULL; + if (must_stat(colors, type)) { + statbuf = cpath_stat(cpath); + if (!statbuf) { + goto error; + } + } + const struct esc_seq *color = NULL; switch (type) { case BFS_REG: - if (colors->setuid || colors->setgid || colors->executable || colors->multi_hard) { - statbuf = cpath_stat(cpath); - if (!statbuf) { - goto error; - } - } - if (colors->setuid && (statbuf->mode & 04000)) { color = colors->setuid; } else if (colors->setgid && (statbuf->mode & 02000)) { @@ -986,6 +1139,12 @@ static const struct esc_seq *file_color(const struct colors *colors, const struc color = colors->multi_hard; } +#ifdef SF_DATALESS + if (!color && colors->dataless && (statbuf->attrs & SF_DATALESS)) { + color = colors->dataless; + } +#endif + if (!color) { const char *name = cpath->path + cpath->nameoff; size_t namelen = cpath->valid - cpath->nameoff; @@ -999,13 +1158,6 @@ static const struct esc_seq *file_color(const struct colors *colors, const struc break; case BFS_DIR: - if (colors->sticky_other_writable || colors->other_writable || colors->sticky) { - statbuf = cpath_stat(cpath); - if (!statbuf) { - goto error; - } - } - if (colors->sticky_other_writable && (statbuf->mode & 01002) == 01002) { color = colors->sticky_other_writable; } else if (colors->other_writable && (statbuf->mode & 00002)) { @@ -1237,7 +1389,7 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { } /** Format some colored output to the buffer. */ -_printf(2, 3) +[[_printf(2, 3)]] static int cbuff(CFILE *cfile, const char *format, ...); /** Print an expression's name, for diagnostics. */ @@ -1321,7 +1473,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i return 0; } -_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 aac8b33..d5e437c 100644 --- a/src/color.h +++ b/src/color.h @@ -103,13 +103,13 @@ int cfclose(CFILE *cfile); * @return * 0 on success, -1 on failure. */ -_printf(2, 3) +[[_printf(2, 3)]] int cfprintf(CFILE *cfile, const char *format, ...); /** * cfprintf() variant that takes a va_list. */ -_printf(2, 0) +[[_printf(2, 0)]] int cvfprintf(CFILE *cfile, const char *format, va_list args); /** @@ -33,7 +33,7 @@ void bfs_diagf(const char *format, ...) { va_end(args); } -_noreturn +[[_noreturn]] void bfs_abortf(const char *format, ...) { va_list args; va_start(args, format); @@ -18,6 +18,7 @@ * * bfs: func@src/file.c:0: Message */ +// Use (format) ? "..." : "" so the format string is required #define BFS_DIAG_FORMAT_(format) \ ((format) ? "%s: %s@%s:%d: " format "%s" : "") @@ -25,57 +26,49 @@ * Add arguments to match a BFS_DIAG_FORMAT string. */ #define BFS_DIAG_ARGS_(...) \ - xgetprogname(), __func__, __FILE__, __LINE__, __VA_ARGS__ "\n" + xgetprogname(), __func__, __FILE__, __LINE__, __VA_ARGS__ __VA_OPT__(,) "\n" /** * Print a low-level diagnostic message to standard error. */ -_printf(1, 2) +[[_printf(1, 2)]] void bfs_diagf(const char *format, ...); /** * Unconditional diagnostic message. */ -#define bfs_diag(...) \ - bfs_diag_(__VA_ARGS__, ) - -#define bfs_diag_(format, ...) \ +#define bfs_diag(format, ...) \ bfs_diagf(BFS_DIAG_FORMAT_(format), BFS_DIAG_ARGS_(__VA_ARGS__)) /** * Print a diagnostic message including the last error. */ -#define bfs_ediag(...) \ - bfs_ediag_(__VA_ARGS__, ) - -#define bfs_ediag_(format, ...) \ - bfs_diag_(format "%s%s", __VA_ARGS__ (sizeof("" format) > 1 ? ": " : ""), errstr(), ) +#define bfs_ediag(format, ...) \ + BFS_VA_IF(format) \ + (bfs_diag(format ": %s", __VA_ARGS__ __VA_OPT__(,) errstr())) \ + (bfs_diag("%s", errstr())) /** * Print a message to standard error and abort. */ -_cold -_printf(1, 2) -_noreturn +[[_cold]] +[[_printf(1, 2)]] +[[_noreturn]] void bfs_abortf(const char *format, ...); /** * Unconditional abort with a message. */ -#define bfs_abort(...) \ - bfs_abort_(__VA_ARGS__, ) - -#define bfs_abort_(format, ...) \ +#define bfs_abort(format, ...) \ bfs_abortf(BFS_DIAG_FORMAT_(format), BFS_DIAG_ARGS_(__VA_ARGS__)) /** * Abort with a message including the last error. */ -#define bfs_eabort(...) \ - bfs_eabort_(__VA_ARGS__, ) - -#define bfs_eabort_(format, ...) \ - ((format) ? bfs_abort_(format ": %s", __VA_ARGS__ errstr(), ) : (void)0) +#define bfs_eabort(format, ...) \ + BFS_VA_IF(__VA_ARGS__) \ + (bfs_abort(format ": %s", __VA_ARGS__ __VA_OPT__(,) errstr())) \ + (bfs_abort("%s", errstr())) /** * Abort in debug builds; no-op in release builds. @@ -89,43 +82,20 @@ void bfs_abortf(const char *format, ...); #endif /** - * Get the default assertion message, if no format string was specified. - */ -#define BFS_DIAG_MSG_(format, str) \ - (sizeof(format) > 1 ? "" : str) - -/** * Unconditional assert. */ -#define bfs_verify(...) \ - bfs_verify_(#__VA_ARGS__, __VA_ARGS__, "", ) - -#define bfs_verify_(str, cond, format, ...) \ - ((cond) ? (void)0 : bfs_verify__(format, BFS_DIAG_MSG_(format, str), __VA_ARGS__)) - -#define bfs_verify__(format, ...) \ - bfs_abortf( \ - sizeof(format) > 1 \ - ? BFS_DIAG_FORMAT_("%s" format "%s") \ - : BFS_DIAG_FORMAT_("Assertion failed: `%s`"), \ - BFS_DIAG_ARGS_(__VA_ARGS__)) +#define bfs_verify(cond, ...) \ + ((cond) ? (void)0 : BFS_VA_IF(__VA_ARGS__) \ + (bfs_abort(__VA_ARGS__)) \ + (bfs_abort("Assertion failed: `%s`", #cond))) /** * Unconditional assert, including the last error. */ -#define bfs_everify(...) \ - bfs_everify_(#__VA_ARGS__, __VA_ARGS__, "", ) - - -#define bfs_everify_(str, cond, format, ...) \ - ((cond) ? (void)0 : bfs_everify__(format, BFS_DIAG_MSG_(format, str), __VA_ARGS__)) - -#define bfs_everify__(format, ...) \ - bfs_abortf( \ - sizeof(format) > 1 \ - ? BFS_DIAG_FORMAT_("%s" format "%s: %s") \ - : BFS_DIAG_FORMAT_("Assertion failed: `%s`: %s"), \ - BFS_DIAG_ARGS_(__VA_ARGS__ errstr(), )) +#define bfs_everify(cond, ...) \ + ((cond) ? (void)0 : BFS_VA_IF(__VA_ARGS__) \ + (bfs_eabort(__VA_ARGS__)) \ + (bfs_eabort("Assertion failed: `%s`", #cond))) /** * Assert in debug builds; no-op in release builds. @@ -171,14 +141,14 @@ const char *debug_flag_name(enum debug_flags flag); /** * Like perror(), but decorated like bfs_error(). */ -_cold +[[_cold]] void bfs_perror(const struct bfs_ctx *ctx, const char *str); /** * Shorthand for printing error messages. */ -_cold -_printf(2, 3) +[[_cold]] +[[_printf(2, 3)]] void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -186,8 +156,8 @@ void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a warning was printed. */ -_cold -_printf(2, 3) +[[_cold]] +[[_printf(2, 3)]] bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -195,71 +165,71 @@ bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a debug message was printed. */ -_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. */ -_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. */ -_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. */ -_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. */ -_cold +[[_cold]] void bfs_error_prefix(const struct bfs_ctx *ctx); /** * Print the warning message prefix. */ -_cold +[[_cold]] bool bfs_warning_prefix(const struct bfs_ctx *ctx); /** * Print the debug message prefix. */ -_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. */ -_cold +[[_cold]] void bfs_argv_error(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in an error message. */ -_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. */ -_cold +[[_cold]] bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in a warning message. */ -_cold +[[_cold]] bool bfs_expr_warning(const struct bfs_ctx *ctx, const struct bfs_expr *expr); #endif // BFS_DIAG_H diff --git a/src/dstring.c b/src/dstring.c index 0f08679..5addb77 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -23,6 +23,7 @@ struct dstring { /** Length of the string, *excluding* the terminating NUL. */ size_t len; /** The string itself. */ + [[_counted_by(cap)]] alignas(dchar) char str[]; }; diff --git a/src/dstring.h b/src/dstring.h index ce7ef86..8765f6e 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -16,14 +16,14 @@ /** Marker type for dynamic strings. */ #if BFS_LINT && __clang__ -// Abuse __attribute__(aligned) to make a type that allows +// Abuse [[gnu::aligned]] to make a type that allows // // dchar * -> char * // // conversions, but warns (with Clang's -Walign-mismatch) on // // char * -> dchar * -typedef __attribute__((aligned(alignof(size_t)))) char dchar; +typedef char dchar [[gnu::aligned(alignof(size_t))]]; #else typedef char dchar; #endif @@ -42,7 +42,7 @@ void dstrfree(dchar *dstr); * @cap * The initial capacity of the string. */ -_malloc(dstrfree, 1) +[[_malloc(dstrfree, 1)]] dchar *dstralloc(size_t cap); /** @@ -51,7 +51,7 @@ dchar *dstralloc(size_t cap); * @str * The NUL-terminated string to copy. */ -_malloc(dstrfree, 1) +[[_malloc(dstrfree, 1)]] dchar *dstrdup(const char *str); /** @@ -62,7 +62,7 @@ dchar *dstrdup(const char *str); * @n * The maximum number of characters to copy from str. */ -_malloc(dstrfree, 1) +[[_malloc(dstrfree, 1)]] dchar *dstrndup(const char *str, size_t n); /** @@ -71,7 +71,7 @@ dchar *dstrndup(const char *str, size_t n); * @dstr * The dynamic string to copy. */ -_malloc(dstrfree, 1) +[[_malloc(dstrfree, 1)]] dchar *dstrddup(const dchar *dstr); /** @@ -82,7 +82,7 @@ dchar *dstrddup(const dchar *dstr); * @len * The length of the string, which may include internal NUL bytes. */ -_malloc(dstrfree, 1) +[[_malloc(dstrfree, 1)]] dchar *dstrxdup(const char *str, size_t len); /** @@ -117,7 +117,7 @@ int dstreserve(dchar **dstr, size_t cap); * @return * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstresize(dchar **dstr, size_t len); /** @@ -139,7 +139,7 @@ void dstrshrink(dchar *dstr, size_t len); * The string to append. * @return 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrcat(dchar **dest, const char *src); /** @@ -154,7 +154,7 @@ int dstrcat(dchar **dest, const char *src); * @return * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrncat(dchar **dest, const char *src, size_t n); /** @@ -167,7 +167,7 @@ int dstrncat(dchar **dest, const char *src, size_t n); * @return * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrdcat(dchar **dest, const dchar *src); /** @@ -182,7 +182,7 @@ int dstrdcat(dchar **dest, const dchar *src); * @return * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrxcat(dchar **dest, const char *src, size_t len); /** @@ -195,7 +195,7 @@ int dstrxcat(dchar **dest, const char *src, size_t len); * @return * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrapp(dchar **str, char c); /** @@ -208,7 +208,7 @@ int dstrapp(dchar **str, char c); * @returns * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrcpy(dchar **dest, const char *str); /** @@ -221,7 +221,7 @@ int dstrcpy(dchar **dest, const char *str); * @returns * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrdcpy(dchar **dest, const dchar *str); /** @@ -236,7 +236,7 @@ int dstrdcpy(dchar **dest, const dchar *str); * @returns * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrncpy(dchar **dest, const char *str, size_t n); /** @@ -251,7 +251,7 @@ int dstrncpy(dchar **dest, const char *str, size_t n); * @returns * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrxcpy(dchar **dest, const char *str, size_t len); /** @@ -264,8 +264,8 @@ int dstrxcpy(dchar **dest, const char *str, size_t len); * @return * The created string, or NULL on failure. */ -_nodiscard -_printf(1, 2) +[[_nodiscard]] +[[_printf(1, 2)]] dchar *dstrprintf(const char *format, ...); /** @@ -278,8 +278,8 @@ dchar *dstrprintf(const char *format, ...); * @return * The created string, or NULL on failure. */ -_nodiscard -_printf(1, 0) +[[_nodiscard]] +[[_printf(1, 0)]] dchar *dstrvprintf(const char *format, va_list args); /** @@ -294,8 +294,8 @@ dchar *dstrvprintf(const char *format, va_list args); * @return * 0 on success, -1 on failure. */ -_nodiscard -_printf(2, 3) +[[_nodiscard]] +[[_printf(2, 3)]] int dstrcatf(dchar **str, const char *format, ...); /** @@ -310,8 +310,8 @@ int dstrcatf(dchar **str, const char *format, ...); * @return * 0 on success, -1 on failure. */ -_nodiscard -_printf(2, 0) +[[_nodiscard]] +[[_printf(2, 0)]] int dstrvcatf(dchar **str, const char *format, va_list args); /** @@ -326,7 +326,7 @@ int dstrvcatf(dchar **str, const char *format, va_list args); * @return * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrescat(dchar **dest, const char *str, enum wesc_flags flags); /** @@ -343,13 +343,13 @@ int dstrescat(dchar **dest, const char *str, enum wesc_flags flags); * @return * 0 on success, -1 on failure. */ -_nodiscard +[[_nodiscard]] int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags); /** * Repeat a string n times. */ -_nodiscard +[[_nodiscard]] dchar *dstrepeat(const char *str, size_t n); #endif // BFS_DSTRING_H @@ -67,7 +67,7 @@ struct bfs_eval { /** * Print an error message. */ -_printf(2, 3) +[[_printf(2, 3)]] static void eval_error(struct bfs_eval *state, const char *format, ...) { const struct bfs_ctx *ctx = state->ctx; @@ -279,10 +279,10 @@ bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) { switch (expr->time_unit) { case BFS_DAYS: diff /= 60 * 24; - _fallthrough; + [[fallthrough]]; case BFS_MINUTES: diff /= 60; - _fallthrough; + [[fallthrough]]; case BFS_SECONDS: break; } @@ -24,7 +24,7 @@ #include <unistd.h> /** Print some debugging info. */ -_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; diff --git a/src/fsade.c b/src/fsade.c index dfdf125..3d4c8ba 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -47,7 +47,7 @@ * 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. */ -_maybe_unused +[[_maybe_unused]] static const char *fake_at(const struct BFTW *ftwbuf) { static atomic int proc_works = -1; @@ -81,7 +81,7 @@ fail: return ftwbuf->path; } -_maybe_unused +[[_maybe_unused]] static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { dstrfree((dchar *)path); @@ -91,7 +91,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. */ -_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 @@ -171,7 +171,7 @@ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { } /** Unified interface for acl_get_tag_type(). */ -_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); @@ -203,6 +203,7 @@ struct ioqq { cache_align atomic size_t tail; /** The circular buffer itself. */ + // [[_counted_by(slot_mask + 1)]] cache_align ioq_slot slots[]; }; @@ -275,7 +276,7 @@ static struct ioq_monitor *ioq_slot_monitor(struct ioqq *ioqq, ioq_slot *slot) { } /** Atomically wait for a slot to change. */ -_noinline +[[_noinline]] static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) { uintptr_t ret; @@ -323,7 +324,7 @@ done: } /** Wake up any threads waiting on a slot. */ -_noinline +[[_noinline]] static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); @@ -593,6 +594,7 @@ struct ioq { /** The number of background threads. */ size_t nthreads; /** The background threads themselves. */ + [[_counted_by(nthreads)]] struct ioq_thread threads[]; }; @@ -82,6 +82,7 @@ #ifndef BFS_LIST_H #define BFS_LIST_H +#include "bfs.h" #include "diag.h" #include <stddef.h> @@ -121,76 +122,21 @@ * The item to initialize. * @node (optional) * If specified, use item->node.next rather than item->next. - * - * --- - * - * We play some tricks with variadic macros to handle the optional parameter: - * - * SLIST_ITEM_INIT(item) => item->next = NULL - * SLIST_ITEM_INIT(item, node) => item->node.next = NULL - * - * The first trick is that - * - * #define SLIST_ITEM_INIT(item, ...) - * - * won't work because both commas are required (until C23; see N3033). As a - * workaround, we dispatch to another macro and add a trailing comma. - * - * SLIST_ITEM_INIT(item) => SLIST_ITEM_INIT_(item, ) - * SLIST_ITEM_INIT(item, node) => SLIST_ITEM_INIT_(item, node, ) */ -#define SLIST_ITEM_INIT(...) \ - SLIST_ITEM_INIT_(__VA_ARGS__, ) +#define SLIST_ITEM_INIT(item, ...) \ + SLIST_ITEM_INIT_((item), LIST_NEXT_(__VA_ARGS__)) -/** - * Now we need a way to generate either ->next or ->node.next depending on - * whether the node parameter was passed. The approach is based on - * - * #define FOO(...) BAR(__VA_ARGS__, 1, 2, ) - * #define BAR(x, y, z, ...) z - * - * FOO(a) => 2 - * FOO(a, b) => 1 - * - * The LIST_NEXT_() macro uses this technique: - * - * LIST_NEXT_() => LIST_NODE_(next, ) - * LIST_NEXT_(node, ) => LIST_NODE_(next, node, ) - */ -#define LIST_NEXT_(...) \ - LIST_NODE_(next, __VA_ARGS__) +#define SLIST_ITEM_INIT_(item, next) \ + LIST_VOID_(item->next = NULL) /** - * LIST_NODE_() dispatches to yet another macro: + * Get the projection for an item's next pointer. * - * LIST_NODE_(next, ) => LIST_NODE__(next, , . , , ) - * LIST_NODE_(next, node, ) => LIST_NODE__(next, node, , . , , ) - */ -#define LIST_NODE_(dir, ...) \ - LIST_NODE__(dir, __VA_ARGS__, . , , ) - -/** - * And finally, LIST_NODE__() adds the node and the dot if necessary. - * - * dir node ignored dot - * v v v v - * LIST_NODE__(next, , . , , ) => next - * LIST_NODE__(next, node, , . , , ) => node . next - * ^ ^ ^ ^ - * dir node ignored dot + * LIST_NEXT_() => next + * LIST_NEXT_(node) => node.next */ -#define LIST_NODE__(dir, node, ignored, dot, ...) \ - node dot dir - -/** - * SLIST_ITEM_INIT_() uses LIST_NEXT_() to generate the right name for the list - * node, and finally delegates to the actual implementation. - */ -#define SLIST_ITEM_INIT_(item, ...) \ - SLIST_ITEM_INIT__((item), LIST_NEXT_(__VA_ARGS__)) - -#define SLIST_ITEM_INIT__(item, next) \ - LIST_VOID_(item->next = NULL) +#define LIST_NEXT_(node) \ + BFS_VA_IF(node)(node.next)(next) /** * Type-checking macro for singly-linked lists. @@ -219,13 +165,6 @@ (!SLIST_HEAD(list)) /** - * Like container_of(), but using the head pointer instead of offsetof() since - * we don't have the type around. - */ -#define SLIST_CONTAINER_(tail, head, next) \ - (void *)((char *)tail - ((char *)&head->next - (char *)head)) - -/** * Get the tail of a singly-linked list. * * @list @@ -235,14 +174,11 @@ * @return * The last item in the list. */ -#define SLIST_TAIL(...) \ - SLIST_TAIL_(__VA_ARGS__, ) +#define SLIST_TAIL(list, ...) \ + SLIST_TAIL_((list), LIST_NEXT_(__VA_ARGS__)) -#define SLIST_TAIL_(list, ...) \ - SLIST_TAIL__((list), LIST_NEXT_(__VA_ARGS__)) - -#define SLIST_TAIL__(list, next) \ - (list->head ? SLIST_CONTAINER_(list->tail, list->head, next) : NULL) +#define SLIST_TAIL_(list, next) \ + (list->head ? container_of(list->tail, typeof(*list->head), next) : NULL) /** * Check if an item is attached to a singly-linked list. @@ -256,13 +192,10 @@ * @return * Whether the item is attached to the list. */ -#define SLIST_ATTACHED(list, ...) \ - SLIST_ATTACHED_(list, __VA_ARGS__, ) - -#define SLIST_ATTACHED_(list, item, ...) \ - SLIST_ATTACHED__((list), (item), LIST_NEXT_(__VA_ARGS__)) +#define SLIST_ATTACHED(list, item, ...) \ + SLIST_ATTACHED_((list), (item), LIST_NEXT_(__VA_ARGS__)) -#define SLIST_ATTACHED__(list, item, next) \ +#define SLIST_ATTACHED_(list, item, next) \ (item->next || list->tail == &item->next) /** @@ -279,14 +212,11 @@ * @return * A cursor for the next item. */ -#define SLIST_INSERT(list, cursor, ...) \ - SLIST_INSERT_(list, cursor, __VA_ARGS__, ) - -#define SLIST_INSERT_(list, cursor, item, ...) \ - SLIST_INSERT__((list), (cursor), (item), LIST_NEXT_(__VA_ARGS__)) +#define SLIST_INSERT(list, cursor, item, ...) \ + SLIST_INSERT_((list), (cursor), (item), LIST_NEXT_(__VA_ARGS__)) -#define SLIST_INSERT__(list, cursor, item, next) \ - (bfs_assert(!SLIST_ATTACHED__(list, item, next)), \ +#define SLIST_INSERT_(list, cursor, item, next) \ + (bfs_assert(!SLIST_ATTACHED_(list, item, next)), \ item->next = *cursor, \ *cursor = item, \ list->tail = item->next ? list->tail : &item->next, \ @@ -302,11 +232,8 @@ * @node (optional) * If specified, use item->node.next rather than item->next. */ -#define SLIST_APPEND(list, ...) \ - SLIST_APPEND_(list, __VA_ARGS__, ) - -#define SLIST_APPEND_(list, item, ...) \ - LIST_VOID_(SLIST_INSERT_(list, (list)->tail, item, __VA_ARGS__)) +#define SLIST_APPEND(list, item, ...) \ + LIST_VOID_(SLIST_INSERT(list, (list)->tail, item, __VA_ARGS__)) /** * Add an item to the head of a singly-linked list. @@ -318,11 +245,8 @@ * @node (optional) * If specified, use item->node.next rather than item->next. */ -#define SLIST_PREPEND(list, ...) \ - SLIST_PREPEND_(list, __VA_ARGS__, ) - -#define SLIST_PREPEND_(list, item, ...) \ - LIST_VOID_(SLIST_INSERT_(list, &(list)->head, item, __VA_ARGS__)) +#define SLIST_PREPEND(list, item, ...) \ + LIST_VOID_(SLIST_INSERT(list, &(list)->head, item, __VA_ARGS__)) /** * Splice a singly-linked list into another. @@ -366,15 +290,12 @@ * @return * The removed item. */ -#define SLIST_REMOVE(list, ...) \ - SLIST_REMOVE_(list, __VA_ARGS__, ) +#define SLIST_REMOVE(list, cursor, ...) \ + SLIST_REMOVE_((list), (cursor), LIST_NEXT_(__VA_ARGS__)) -#define SLIST_REMOVE_(list, cursor, ...) \ - SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__)) - -#define SLIST_REMOVE__(list, cursor, next) \ +#define SLIST_REMOVE_(list, cursor, next) \ (list->tail = (*cursor)->next ? list->tail : cursor, \ - slist_remove_(*cursor, cursor, &(*cursor)->next, sizeof(*cursor))) + (typeof(*cursor))slist_remove_(*cursor, cursor, &(*cursor)->next, sizeof(*cursor))) // Helper for SLIST_REMOVE() static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t size) { @@ -396,14 +317,8 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @return * The popped item, or NULL if the list was empty. */ -#define SLIST_POP(...) \ - SLIST_POP_(__VA_ARGS__, ) - -#define SLIST_POP_(list, ...) \ - SLIST_POP__((list), __VA_ARGS__) - -#define SLIST_POP__(list, ...) \ - (list->head ? SLIST_REMOVE_(list, &list->head, __VA_ARGS__) : NULL) +#define SLIST_POP(list, ...) \ + ((list)->head ? SLIST_REMOVE(list, &(list)->head, __VA_ARGS__) : NULL) /** * Loop over the items in a singly-linked list. @@ -417,15 +332,12 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @node (optional) * If specified, use head->node.next rather than head->next. */ -#define for_slist(type, item, ...) \ - for_slist_(type, item, __VA_ARGS__, ) +#define for_slist(type, item, list, ...) \ + for_slist_(type, item, (list), LIST_NEXT_(__VA_ARGS__)) -#define for_slist_(type, item, list, ...) \ - for_slist__(type, item, (list), LIST_NEXT_(__VA_ARGS__)) - -#define for_slist__(type, item, list, next) \ - for (type *item = list->head, *_next; \ - item && (SLIST_CHECK_(list), _next = item->next, true); \ +#define for_slist_(type, item, list, next) \ + for (type *item = SLIST_HEAD(list), *_next; \ + item && (_next = item->next, true); \ item = _next) /** @@ -440,8 +352,8 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @node (optional) * If specified, use head->node.next rather than head->next. */ -#define drain_slist(type, item, ...) \ - for (type *item; (item = SLIST_POP(__VA_ARGS__));) +#define drain_slist(type, item, list, ...) \ + for (type *item; (item = SLIST_POP(list, __VA_ARGS__));) /** * Initialize a doubly-linked list. @@ -456,11 +368,11 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si LIST_VOID_(list->head = list->tail = NULL) /** - * LIST_PREV_() => prev - * LIST_PREV_(node, ) => node.prev + * LIST_PREV_() => prev + * LIST_PREV_(node) => node.prev */ -#define LIST_PREV_(...) \ - LIST_NODE_(prev, __VA_ARGS__) +#define LIST_PREV_(node) \ + BFS_VA_IF(node)(node.prev)(prev) /** * Initialize a doubly-linked list item. @@ -470,13 +382,10 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @node (optional) * If specified, use item->node.next rather than item->next. */ -#define LIST_ITEM_INIT(...) \ - LIST_ITEM_INIT_(__VA_ARGS__, ) - -#define LIST_ITEM_INIT_(item, ...) \ - LIST_ITEM_INIT__((item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) +#define LIST_ITEM_INIT(item, ...) \ + LIST_ITEM_INIT_((item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) -#define LIST_ITEM_INIT__(item, prev, next) \ +#define LIST_ITEM_INIT_(item, prev, next) \ LIST_VOID_(item->prev = item->next = NULL) /** @@ -489,10 +398,7 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * Check if a doubly-linked list is empty. */ #define LIST_EMPTY(list) \ - LIST_EMPTY_((list)) - -#define LIST_EMPTY_(list) \ - (LIST_CHECK_(list), !list->head) + (LIST_CHECK_(list), !(list)->head) /** * Add an item to the tail of a doubly-linked list. @@ -504,8 +410,8 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @node (optional) * If specified, use item->node.{prev,next} rather than item->{prev,next}. */ -#define LIST_APPEND(list, ...) \ - LIST_INSERT(list, (list)->tail, __VA_ARGS__) +#define LIST_APPEND(list, item, ...) \ + LIST_INSERT(list, (list)->tail, item, __VA_ARGS__) /** * Add an item to the head of a doubly-linked list. @@ -517,8 +423,8 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @node (optional) * If specified, use item->node.{prev,next} rather than item->{prev,next}. */ -#define LIST_PREPEND(list, ...) \ - LIST_INSERT(list, NULL, __VA_ARGS__) +#define LIST_PREPEND(list, item, ...) \ + LIST_INSERT(list, NULL, item, __VA_ARGS__) /** * Check if an item is attached to a doubly-linked list. @@ -532,13 +438,10 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @return * Whether the item is attached to the list. */ -#define LIST_ATTACHED(list, ...) \ - LIST_ATTACHED_(list, __VA_ARGS__, ) - -#define LIST_ATTACHED_(list, item, ...) \ - LIST_ATTACHED__((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) +#define LIST_ATTACHED(list, item, ...) \ + LIST_ATTACHED_((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) -#define LIST_ATTACHED__(list, item, prev, next) \ +#define LIST_ATTACHED_(list, item, prev, next) \ (item->prev || item->next || list->head == item || list->tail == item) /** @@ -553,14 +456,11 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @node (optional) * If specified, use item->node.{prev,next} rather than item->{prev,next}. */ -#define LIST_INSERT(list, cursor, ...) \ - LIST_INSERT_(list, cursor, __VA_ARGS__, ) - -#define LIST_INSERT_(list, cursor, item, ...) \ - LIST_INSERT__((list), (cursor), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) +#define LIST_INSERT(list, cursor, item, ...) \ + LIST_INSERT_((list), (cursor), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) -#define LIST_INSERT__(list, cursor, item, prev, next) LIST_VOID_( \ - bfs_assert(!LIST_ATTACHED__(list, item, prev, next)), \ +#define LIST_INSERT_(list, cursor, item, prev, next) LIST_VOID_( \ + bfs_assert(!LIST_ATTACHED_(list, item, prev, next)), \ item->prev = cursor, \ item->next = cursor ? cursor->next : list->head, \ *(item->prev ? &item->prev->next : &list->head) = item, \ @@ -576,13 +476,10 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @node (optional) * If specified, use item->node.{prev,next} rather than item->{prev,next}. */ -#define LIST_REMOVE(list, ...) \ - LIST_REMOVE_(list, __VA_ARGS__, ) +#define LIST_REMOVE(list, item, ...) \ + LIST_REMOVE_((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) -#define LIST_REMOVE_(list, item, ...) \ - LIST_REMOVE__((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) - -#define LIST_REMOVE__(list, item, prev, next) LIST_VOID_( \ +#define LIST_REMOVE_(list, item, prev, next) LIST_VOID_( \ *(item->prev ? &item->prev->next : &list->head) = item->next, \ *(item->next ? &item->next->prev : &list->tail) = item->prev, \ item->prev = item->next = NULL) @@ -599,15 +496,12 @@ static inline void *slist_remove_(void *ret, void *cursor, void *next, size_t si * @node (optional) * If specified, use head->node.next rather than head->next. */ -#define for_list(type, item, ...) \ - for_list_(type, item, __VA_ARGS__, ) - -#define for_list_(type, item, list, ...) \ - for_list__(type, item, (list), LIST_NEXT_(__VA_ARGS__)) +#define for_list(type, item, list, ...) \ + for_list_(type, item, (list), LIST_NEXT_(__VA_ARGS__)) -#define for_list__(type, item, list, next) \ - for (type *item = list->head, *_next; \ - item && (LIST_CHECK_(list), _next = item->next, true); \ +#define for_list_(type, item, list, next) \ + for (type *item = (LIST_CHECK_(list), list->head), *_next; \ + item && (_next = item->next, true); \ item = _next) #endif // BFS_LIST_H @@ -69,7 +69,7 @@ struct bfs_mtab { /** * Add an entry to the mount table. */ -_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; @@ -374,7 +374,7 @@ struct bfs_opt { }; /** Log an optimization. */ -_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) { @@ -392,7 +392,7 @@ static bool opt_debug(struct bfs_opt *opt, const char *format, ...) { } /** Log a recursive call. */ -_printf(2, 3) +[[_printf(2, 3)]] static bool opt_enter(struct bfs_opt *opt, const char *format, ...) { int depth = opt->depth; if (depth > 0) { @@ -412,7 +412,7 @@ static bool opt_enter(struct bfs_opt *opt, const char *format, ...) { } /** Log a recursive return. */ -_printf(2, 3) +[[_printf(2, 3)]] static bool opt_leave(struct bfs_opt *opt, const char *format, ...) { bool debug = false; int depth = opt->depth; @@ -436,7 +436,7 @@ static bool opt_leave(struct bfs_opt *opt, const char *format, ...) { } /** Log a shallow visit. */ -_printf(2, 3) +[[_printf(2, 3)]] static bool opt_visit(struct bfs_opt *opt, const char *format, ...) { int depth = opt->depth; if (depth > 0) { @@ -456,7 +456,7 @@ static bool opt_visit(struct bfs_opt *opt, const char *format, ...) { } /** Log the deletion of an expression. */ -_printf(2, 3) +[[_printf(2, 3)]] static bool opt_delete(struct bfs_opt *opt, const char *format, ...) { int depth = opt->depth; @@ -618,7 +618,7 @@ static bool is_const(const struct bfs_expr *expr) { } /** Warn about an expression. */ -_printf(3, 4) +[[_printf(3, 4)]] static bool opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, const char *format, ...) { if (!opt->warn) { return false; @@ -1623,14 +1623,19 @@ static void data_flow_icmp(struct bfs_opt *opt, const struct bfs_expr *expr, enu /** Transfer function for -{execut,read,writ}able. */ static struct bfs_expr *data_flow_access(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { - if (expr->num & R_OK) { + switch (expr->num) { + case R_OK: data_flow_pred(opt, READABLE_PRED, true); - } - if (expr->num & W_OK) { + break; + case W_OK: data_flow_pred(opt, WRITABLE_PRED, true); - } - if (expr->num & X_OK) { + break; + case X_OK: data_flow_pred(opt, EXECUTABLE_PRED, true); + break; + default: + bfs_bug("Unknown access() mode %lld", expr->num); + break; } return expr; @@ -1655,7 +1660,7 @@ static struct bfs_expr *data_flow_gid(struct bfs_opt *opt, struct bfs_expr *expr gid_t gid = range->min; bool nogroup = !bfs_getgrgid(opt->ctx->groups, gid); if (errno == 0) { - data_flow_pred(opt, NOGROUP_PRED, nogroup); + constrain_pred(&opt->after_true.preds[NOGROUP_PRED], nogroup); } } @@ -1729,7 +1734,7 @@ static struct bfs_expr *data_flow_uid(struct bfs_opt *opt, struct bfs_expr *expr uid_t uid = range->min; bool nouser = !bfs_getpwuid(opt->ctx->users, uid); if (errno == 0) { - data_flow_pred(opt, NOUSER_PRED, nouser); + constrain_pred(&opt->after_true.preds[NOUSER_PRED], nouser); } } diff --git a/src/parse.c b/src/parse.c index 9c39d6b..febab7f 100644 --- a/src/parse.c +++ b/src/parse.c @@ -138,7 +138,7 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, /** * Print an error message during parsing. */ -_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; @@ -156,7 +156,7 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ... /** * Print an error about some command line arguments. */ -_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; @@ -174,7 +174,7 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_ /** * Print an error about conflicting command line arguments. */ -_printf(4, 5) +[[_printf(4, 5)]] static void parse_conflict_error(const struct bfs_parser *parser, const struct bfs_expr *expr1, const struct bfs_expr *expr2, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -193,7 +193,7 @@ static void parse_conflict_error(const struct bfs_parser *parser, const struct b /** * Print an error about an expression. */ -_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; @@ -208,7 +208,7 @@ static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_e /** * Print a warning message during parsing. */ -_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; @@ -229,7 +229,7 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, . /** * Print a warning about conflicting command line arguments. */ -_printf(4, 5) +[[_printf(4, 5)]] static bool parse_conflict_warning(const struct bfs_parser *parser, const struct bfs_expr *expr1, const struct bfs_expr *expr2, const char *format, ...) { const struct bfs_ctx *ctx = parser->ctx; @@ -251,7 +251,7 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, const struct /** * Print a warning about an expression. */ -_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; @@ -995,16 +995,16 @@ static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg switch (*tail) { case 'w': time *= 7; - _fallthrough; + [[fallthrough]]; case 'd': time *= 24; - _fallthrough; + [[fallthrough]]; case 'h': time *= 60; - _fallthrough; + [[fallthrough]]; case 'm': time *= 60; - _fallthrough; + [[fallthrough]]; case 's': break; default: @@ -1247,6 +1247,41 @@ static struct bfs_expr *parse_empty(struct bfs_parser *parser, int arg1, int arg return expr; } +/** Check for unsafe relative paths in $PATH. */ +static const char *unsafe_path(const struct bfs_exec *execbuf) { + if (!(execbuf->flags & BFS_EXEC_CHDIR)) { + // Not -execdir or -okdir + return NULL; + } + + const char *exe = execbuf->tmpl_argv[0]; + if (strchr(exe, '/')) { + // No $PATH lookups for /foo or foo/bar + return NULL; + } + + if (strstr(exe, "{}")) { + // Substituted paths always contain a / + return NULL; + } + + const char *path = getenv("PATH"); + while (path) { + if (path[0] != '/') { + // Relative $PATH component! + return path; + } + + path = strchr(path, ':'); + if (path) { + ++path; + } + } + + // No relative components in $PATH + return NULL; +} + /** * Parse -exec(dir)?/-ok(dir)?. */ @@ -1269,29 +1304,21 @@ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg // For pipe() in bfs_spawn() expr->ephemeral_fds = 2; - if (execbuf->flags & BFS_EXEC_CHDIR) { - // Check for relative paths in $PATH - const char *path = getenv("PATH"); - while (path) { - if (*path != '/') { - size_t len = strcspn(path, ":"); - char *comp = strndup(path, len); - if (comp) { - parse_expr_error(parser, expr, - "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); - free(comp); - } else { - parse_perror(parser, "strndup()"); - } - return NULL; - } - - path = strchr(path, ':'); - if (path) { - ++path; - } + const char *unsafe = unsafe_path(execbuf); + if (unsafe) { + size_t len = strcspn(unsafe, ":"); + char *comp = strndup(unsafe, len); + if (comp) { + parse_expr_error(parser, expr, + "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); + free(comp); + } else { + parse_perror(parser, "strndup()"); } + return NULL; + } + if (execbuf->flags & BFS_EXEC_CHDIR) { // To dup() the parent directory if (execbuf->flags & BFS_EXEC_MULTI) { ++expr->persistent_fds; @@ -1946,7 +1973,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct who = 0; mask = 0777; state = MODE_WHO; - _fallthrough; + [[fallthrough]]; case MODE_WHO: switch (*i) { @@ -1973,7 +2000,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; - _fallthrough; + [[fallthrough]]; case MODE_PLUS: expr->file_mode |= file_change; expr->dir_mode |= dir_change; @@ -1983,7 +2010,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct expr->dir_mode &= ~dir_change; break; } - _fallthrough; + [[fallthrough]]; case MODE_ACTION: if (who == 0) { @@ -2068,7 +2095,7 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct break; case 'x': file_change |= mask & 0111; - _fallthrough; + [[fallthrough]]; case 'X': dir_change |= mask & 0111; break; @@ -2131,7 +2158,7 @@ static struct bfs_expr *parse_perm(struct bfs_parser *parser, int field, int arg ++mode; break; } - _fallthrough; + [[fallthrough]]; default: expr->mode_cmp = BFS_MODE_EQUAL; break; diff --git a/src/prelude.h b/src/prelude.h index de89a6c..51f1505 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -77,23 +77,26 @@ /** _Bool => bool, true, false */ #include <stdbool.h> -/** - * C23 deprecates `noreturn void` in favour of `[[noreturn]] void`, so we expose - * _noreturn instead with the other attributes in "bfs.h". - */ -// #include <stdnoreturn.h> - /** Part of <threads.h>, but we don't use anything else from it. */ #define thread_local _Thread_local +/** Get the type of an expression. */ +#define typeof __typeof__ +/** Get the unqualified type of an expression. */ +#define typeof_unqual __typeof_unqual__ + #endif // !C23 -// Feature detection +// Future C standard backports -// https://clang.llvm.org/docs/LanguageExtensions.html#has-attribute -#ifndef __has_attribute -# define __has_attribute(attr) false -#endif +/** + * Get the length of an array. + * + * https://www.open-std.org/JTC1/SC22/WG14/www/docs/n3469.htm + */ +#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__])) + +// Feature detection // https://clang.llvm.org/docs/LanguageExtensions.html#has-builtin #ifndef __has_builtin @@ -126,5 +129,8 @@ #if __has_feature(thread_sanitizer) && !defined(__SANITIZE_THREAD__) # define __SANITIZE_THREAD__ true #endif +#if __has_feature(type_sanitizer) && !defined(__SANITIZE_TYPE__) +# define __SANITIZE_TYPE__ true +#endif #endif // BFS_PRELUDE_H diff --git a/src/printf.c b/src/printf.c index 30ec201..f9fca64 100644 --- a/src/printf.c +++ b/src/printf.c @@ -91,7 +91,7 @@ static bool should_color(CFILE *cfile, const struct bfs_fmt *fmt) { (void)ret /** Return a dynamic format string. */ -_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); @@ -99,7 +99,7 @@ static const char *dyn_fmt(const char *str, const char *fake) { } /** Wrapper for fprintf(). */ -_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); @@ -562,7 +562,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } /** %Z: SELinux context */ -_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) { @@ -708,7 +708,7 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha case '+': case ' ': must_be_numeric = true; - _fallthrough; + [[fallthrough]]; case '-': if (strchr(fmt.str, c)) { bfs_expr_error(ctx, expr); diff --git a/src/sanity.h b/src/sanity.h index be77eef..cc8043f 100644 --- a/src/sanity.h +++ b/src/sanity.h @@ -8,16 +8,18 @@ #ifndef BFS_SANITY_H #define BFS_SANITY_H +#include "bfs.h" #include <stddef.h> -// Call macro(ptr, size) or macro(ptr, sizeof(*ptr)) -#define SANITIZE_CALL(...) \ - SANITIZE_CALL_(__VA_ARGS__, ) +/** Get the default size for a sanitize macro call. */ +#define SANITIZE_SIZE_(ptr, size) \ + BFS_VA_IF(size)(size)(sizeof(*ptr)) -#define SANITIZE_CALL_(macro, ptr, ...) \ - SANITIZE_CALL__(macro, ptr, __VA_ARGS__ sizeof(*(ptr)), ) +// Call macro(ptr, size) or macro(ptr, sizeof(*ptr)) +#define SANITIZE_CALL(macro, ptr, ...) \ + SANITIZE_CALL_(macro, ptr, SANITIZE_SIZE_(ptr, __VA_ARGS__)) -#define SANITIZE_CALL__(macro, ptr, size, ...) \ +#define SANITIZE_CALL_(macro, ptr, size) \ macro(ptr, size) #if __SANITIZE_ADDRESS__ diff --git a/src/sighook.c b/src/sighook.c index a87bed5..42bd811 100644 --- a/src/sighook.c +++ b/src/sighook.c @@ -423,7 +423,7 @@ static bool is_fatal(int sig) { } /** Reraise a fatal signal. */ -_noreturn +[[_noreturn]] static void reraise(siginfo_t *info) { int sig = info->si_signo; diff --git a/src/thread.c b/src/thread.c index b3604f8..8607bca 100644 --- a/src/thread.c +++ b/src/thread.c @@ -24,13 +24,12 @@ } \ } while (0) -#define THREAD_INFALLIBLE(...) \ - THREAD_INFALLIBLE_(__VA_ARGS__, 0, ) +#define THREAD_INFALLIBLE(expr, ...) \ + THREAD_INFALLIBLE_(expr, BFS_VA_IF(__VA_ARGS__)(__VA_ARGS__)(0)) -#define THREAD_INFALLIBLE_(expr, allowed, ...) \ +#define THREAD_INFALLIBLE_(expr, allowed) \ int err = expr; \ - bfs_verify(err == 0 || err == allowed, "%s: %s", #expr, xstrerror(err)); \ - (void)0 + bfs_verify(err == 0 || err == allowed, "%s: %s", #expr, xstrerror(err)) int thread_create(pthread_t *thread, const pthread_attr_t *attr, thread_fn *fn, void *arg) { THREAD_FALLIBLE(pthread_create(thread, attr, fn, arg)); @@ -129,6 +129,7 @@ struct trie_node { * tag to distinguish internal nodes from leaves. This is safe as long * as all dynamic allocations are aligned to more than a single byte. */ + // [[_counted_by(count_ones(bitmap))]] uintptr_t children[]; }; @@ -192,7 +193,7 @@ static unsigned char trie_leaf_nibble(const struct trie_leaf *leaf, size_t offse } /** Get the number of children of an internal node. */ -_trie_clones +[[_trie_clones]] static unsigned int trie_node_size(const struct trie_node *node) { return count_ones((unsigned int)node->bitmap); } @@ -204,7 +205,7 @@ static unsigned int trie_node_size(const struct trie_node *node) { * 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; @@ -233,7 +234,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) { @@ -257,7 +258,7 @@ void *trie_get_mem(const struct trie *trie, const void *key, size_t length) { return leaf ? leaf->value : NULL; } -_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); @@ -302,7 +303,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) { @@ -456,7 +457,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 = trie_node_size(node); @@ -579,7 +580,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); @@ -707,7 +708,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; @@ -21,6 +21,7 @@ struct trie_leaf { /** The length of the key in bytes. */ size_t length; /** The key itself, stored inline. */ + [[_counted_by(length)]] char key[]; }; diff --git a/src/xspawn.c b/src/xspawn.c index 3fa4e60..efa2219 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -119,7 +119,7 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) { #if BFS_POSIX_SPAWN >= 0 /** Set some posix_spawnattr flags. */ -_maybe_unused +[[_maybe_unused]] static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { short prev; errno = posix_spawnattr_getflags(&ctx->attr, &prev); @@ -232,12 +232,28 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { */ #define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD__) +/** + * NetBSD even resolves the executable before file actions with posix_spawn()! + */ +#define BFS_POSIX_SPAWN_AFTER_FCHDIR !__NetBSD__ + int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR); if (!action) { return -1; } +#if __APPLE__ + // macOS has a bug that causes EBADF when an fchdir() action refers to a + // file opened by the file actions + for_slist (struct bfs_spawn_action, prev, ctx) { + if (fd == prev->out_fd) { + bfs_spawn_clear_posix(ctx); + break; + } + } +#endif + #if BFS_HAS_POSIX_SPAWN_ADDFCHDIR # define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir #elif BFS_HAS_POSIX_SPAWN_ADDFCHDIR_NP @@ -401,18 +417,40 @@ static bool bfs_resolve_relative(const struct bfs_resolver *res) { return false; } +/** Check if the actions include fchdir(). */ +static bool bfs_spawn_will_chdir(const struct bfs_spawn *ctx) { + if (ctx) { + for_slist (const struct bfs_spawn_action, action, ctx) { + if (action->op == BFS_SPAWN_FCHDIR) { + return true; + } + } + } + + return false; +} + +/** Check if we can call xfaccessat() before file actions. */ +static bool bfs_can_access_early(const struct bfs_resolver *res, const struct bfs_spawn *ctx) { + if (res->exe[0] == '/') { + return true; + } + + if (bfs_spawn_will_chdir(ctx)) { + return false; + } + + return true; +} + /** Check if we can resolve the executable before file actions. */ static bool bfs_can_resolve_early(const struct bfs_resolver *res, const struct bfs_spawn *ctx) { if (!bfs_resolve_relative(res)) { return true; } - if (ctx) { - for_slist (const struct bfs_spawn_action, action, ctx) { - if (action->op == BFS_SPAWN_FCHDIR) { - return false; - } - } + if (bfs_spawn_will_chdir(ctx)) { + return false; } return true; @@ -442,17 +480,19 @@ static int bfs_resolve_early(struct bfs_resolver *res, const char *exe, const st }; if (bfs_can_skip_resolve(res, ctx)) { - // Do this check eagerly, even though posix_spawn()/execv() also - // would, because: - // - // - faccessat() is faster than fork()/clone() + execv() - // - posix_spawn() is not guaranteed to report ENOENT - if (xfaccessat(AT_FDCWD, exe, X_OK) == 0) { - res->done = true; - return 0; - } else { - return -1; + if (bfs_can_access_early(res, ctx)) { + // Do this check eagerly, even though posix_spawn()/execv() also + // would, because: + // + // - faccessat() is faster than fork()/clone() + execv() + // - posix_spawn() is not guaranteed to report ENOENT + if (xfaccessat(AT_FDCWD, exe, X_OK) != 0) { + return -1; + } } + + res->done = true; + return 0; } res->path = getenv("PATH"); @@ -529,13 +569,19 @@ static bool bfs_use_posix_spawn(const struct bfs_resolver *res, const struct bfs } #endif +#if !BFS_POSIX_SPAWN_AFTER_FCHDIR + if (res->exe[0] != '/' && bfs_spawn_will_chdir(ctx)) { + return false; + } +#endif + return true; } #endif // BFS_POSIX_SPAWN >= 0 /** Actually exec() the new process. */ -_noreturn +[[_noreturn]] static void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, const sigset_t *mask, int pipefd[2]) { xclose(pipefd[0]); |