diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/atomic.h | 2 | ||||
-rw-r--r-- | src/bar.c | 9 | ||||
-rw-r--r-- | src/bfstd.c | 39 | ||||
-rw-r--r-- | src/bfstd.h | 29 | ||||
-rw-r--r-- | src/bftw.c | 4 | ||||
-rw-r--r-- | src/bftw.h | 2 | ||||
-rw-r--r-- | src/bit.h | 100 | ||||
-rw-r--r-- | src/color.c | 2 | ||||
-rw-r--r-- | src/ctx.c | 5 | ||||
-rw-r--r-- | src/ctx.h | 2 | ||||
-rw-r--r-- | src/diag.c | 2 | ||||
-rw-r--r-- | src/eval.c | 72 | ||||
-rw-r--r-- | src/expr.h | 6 | ||||
-rw-r--r-- | src/fsade.h | 4 | ||||
-rw-r--r-- | src/ioq.c | 26 | ||||
-rw-r--r-- | src/list.h | 24 | ||||
-rw-r--r-- | src/main.c | 1 | ||||
-rw-r--r-- | src/opt.c | 173 | ||||
-rw-r--r-- | src/parse.c | 261 | ||||
-rw-r--r-- | src/prelude.h | 13 | ||||
-rw-r--r-- | src/printf.c | 43 | ||||
-rw-r--r-- | src/sighook.c | 55 | ||||
-rw-r--r-- | src/stat.c | 2 | ||||
-rw-r--r-- | src/trie.c | 2 | ||||
-rw-r--r-- | src/typo.c | 2 | ||||
-rw-r--r-- | src/version.c | 32 | ||||
-rw-r--r-- | src/xregex.c | 63 | ||||
-rw-r--r-- | src/xregex.h | 4 | ||||
-rw-r--r-- | src/xspawn.c | 13 | ||||
-rw-r--r-- | src/xtime.c | 18 |
30 files changed, 698 insertions, 312 deletions
diff --git a/src/atomic.h b/src/atomic.h index 360de20..ad5303b 100644 --- a/src/atomic.h +++ b/src/atomic.h @@ -109,7 +109,7 @@ # define spin_loop() __builtin_ia32_pause() #elif __has_builtin(__builtin_arm_yield) # define spin_loop() __builtin_arm_yield() -#elif __has_builtin(__builtin_riscv_pause) +#elif BFS_HAS_BUILTIN_RISCV_PAUSE # define spin_loop() __builtin_riscv_pause() #else # define spin_loop() ((void)0) @@ -144,14 +144,7 @@ struct bfs_bar *bfs_bar_show(void) { return NULL; } - char term[L_ctermid]; - ctermid(term); - if (strlen(term) == 0) { - errno = ENOTTY; - goto fail; - } - - bar->fd = open(term, O_RDWR | O_CLOEXEC); + bar->fd = open_cterm(O_RDWR | O_CLOEXEC); if (bar->fd < 0) { goto fail; } diff --git a/src/bfstd.c b/src/bfstd.c index ce2218c..6d244ca 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -184,6 +184,16 @@ char *xgetdelim(FILE *file, char delim) { } } +int open_cterm(int flags) { + char path[L_ctermid]; + if (ctermid(path) == NULL || strlen(path) == 0) { + errno = ENOTTY; + return -1; + } + + return open(path, flags); +} + const char *xgetprogname(void) { const char *cmd = NULL; #if BFS_HAS_GETPROGNAME @@ -199,6 +209,35 @@ const char *xgetprogname(void) { return cmd; } +int xstrtoll(const char *str, char **end, int base, long long *value) { + // strtoll() skips leading spaces, but we want to reject them + if (xisspace(str[0])) { + errno = EINVAL; + return -1; + } + + // If end is NULL, make sure the entire string is valid + bool entire = !end; + char *endp; + if (!end) { + end = &endp; + } + + errno = 0; + long long result = strtoll(str, end, base); + if (errno != 0) { + return -1; + } + + if (*end == str || (entire && **end != '\0')) { + errno = EINVAL; + return -1; + } + + *value = result; + return 0; +} + /** Compile and execute a regular expression for xrpmatch(). */ static int xrpregex(nl_item item, const char *response) { const char *pattern = nl_langinfo(item); diff --git a/src/bfstd.h b/src/bfstd.h index 8055c55..afb5282 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -158,6 +158,16 @@ FILE *xfopen(const char *path, int flags); */ char *xgetdelim(FILE *file, char delim); +/** + * Open the controlling terminal. + * + * @param flags + * The open() flags. + * @return + * An open file descriptor, or -1 on failure. + */ +int open_cterm(int flags); + // #include <stdlib.h> /** @@ -169,6 +179,23 @@ char *xgetdelim(FILE *file, char delim); const char *xgetprogname(void); /** + * Wrapper for strtoll() that forbids leading spaces. + * + * @param str + * The string to parse. + * @param end + * If non-NULL, will hold a pointer to the first invalid character. + * If NULL, the entire string must be valid. + * @param base + * The base for the conversion, or 0 to auto-detect. + * @param value + * Will hold the parsed integer value, on success. + * @return + * 0 on success, -1 on failure. + */ +int xstrtoll(const char *str, char **end, int base, long long *value); + +/** * Process a yes/no prompt. * * @return 1 for yes, 0 for no, and -1 for unknown. @@ -349,7 +376,7 @@ size_t xread(int fd, void *buf, size_t nbytes); * writes. * * @return - The number of bytes written. A value != nbytes indicates an error. + * The number of bytes written. A value != nbytes indicates an error. */ size_t xwrite(int fd, const void *buf, size_t nbytes); @@ -913,7 +913,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg size_t qdepth = 4096; size_t nthreads = args->nthreads; -#if BFS_USE_LIBURING +#if BFS_WITH_LIBURING // io_uring uses one fd per ring, ioq uses one ring per thread if (nthreads >= nopenfd - 1) { nthreads = nopenfd - 2; @@ -1676,6 +1676,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { ftwbuf->visit = visit; ftwbuf->type = BFS_UNKNOWN; ftwbuf->error = state->direrror; + ftwbuf->loopoff = 0; ftwbuf->at_fd = AT_FDCWD; ftwbuf->at_path = ftwbuf->path; bftw_stat_init(&ftwbuf->stat_bufs, &state->stat_buf, &state->lstat_buf); @@ -1733,6 +1734,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) { ftwbuf->type = BFS_ERROR; ftwbuf->error = ELOOP; + ftwbuf->loopoff = ancestor->nameoff + ancestor->namelen; return; } } @@ -56,6 +56,8 @@ struct BFTW { enum bfs_type type; /** The errno that occurred, if type == BFS_ERROR. */ int error; + /** For filesystem loops, the length of the loop prefix. */ + size_t loopoff; /** A parent file descriptor for the *at() family of calls. */ int at_fd; @@ -12,7 +12,7 @@ #include <limits.h> #include <stdint.h> -#if __STDC_VERSION__ >= C23 +#if BFS_HAS_STDBIT_H # include <stdbit.h> #endif @@ -173,11 +173,7 @@ # define ENDIAN_NATIVE 0 #endif -#if __STDC_VERSION__ >= C23 -# define bswap_u16 stdc_memreverse8u16 -# define bswap_u32 stdc_memreverse8u32 -# define bswap_u64 stdc_memreverse8u64 -#elif __GNUC__ +#if __GNUC__ # define bswap_u16 __builtin_bswap16 # define bswap_u32 __builtin_bswap32 # define bswap_u64 __builtin_bswap64 @@ -222,25 +218,17 @@ static inline uint8_t bswap_u8(uint8_t n) { // Select an overload based on an unsigned integer type #define UINT_SELECT(n, name) \ _Generic((n), \ - char: name##_uc, \ - signed char: name##_uc, \ unsigned char: name##_uc, \ - signed short: name##_us, \ unsigned short: name##_us, \ - signed int: name##_ui, \ unsigned int: name##_ui, \ - signed long: name##_ul, \ unsigned long: name##_ul, \ - signed long long: name##_ull, \ unsigned long long: name##_ull) // C23 polyfill: bit utilities -#if __STDC_VERSION__ >= C23 +#if __STDC_VERSION_STDBIT_H__ >= C23 # define count_ones stdc_count_ones # define count_zeros stdc_count_zeros -# define rotate_left stdc_rotate_left -# define rotate_right stdc_rotate_right # define leading_zeros stdc_leading_zeros # define leading_ones stdc_leading_ones # define trailing_zeros stdc_trailing_zeros @@ -273,31 +261,31 @@ static inline uint8_t bswap_u8(uint8_t n) { #define BUILTIN_WIDTH(suffix) BUILTIN_WIDTH##suffix #define COUNT_ONES(type, suffix, width) \ - static inline int count_ones##suffix(type n) { \ + static inline unsigned int count_ones##suffix(type n) { \ return UINT_BUILTIN(popcount, suffix)(n); \ } #define LEADING_ZEROS(type, suffix, width) \ - static inline int leading_zeros##suffix(type n) { \ + static inline unsigned int leading_zeros##suffix(type n) { \ return n \ ? UINT_BUILTIN(clz, suffix)(n) - (BUILTIN_WIDTH(suffix) - width) \ : width; \ } #define TRAILING_ZEROS(type, suffix, width) \ - static inline int trailing_zeros##suffix(type n) { \ + static inline unsigned int trailing_zeros##suffix(type n) { \ return n ? UINT_BUILTIN(ctz, suffix)(n) : (int)width; \ } #define FIRST_TRAILING_ONE(type, suffix, width) \ - static inline int first_trailing_one##suffix(type n) { \ + static inline unsigned int first_trailing_one##suffix(type n) { \ return UINT_BUILTIN(ffs, suffix)(n); \ } #else // !__GNUC__ #define COUNT_ONES(type, suffix, width) \ - static inline int count_ones##suffix(type n) { \ + static inline unsigned int count_ones##suffix(type n) { \ int ret; \ for (ret = 0; n; ++ret) { \ n &= n - 1; \ @@ -306,7 +294,7 @@ static inline uint8_t bswap_u8(uint8_t n) { } #define LEADING_ZEROS(type, suffix, width) \ - static inline int leading_zeros##suffix(type n) { \ + static inline unsigned int leading_zeros##suffix(type n) { \ type bit = (type)1 << (width - 1); \ int ret; \ for (ret = 0; bit && !(n & bit); ++ret, bit >>= 1); \ @@ -314,7 +302,7 @@ static inline uint8_t bswap_u8(uint8_t n) { } #define TRAILING_ZEROS(type, suffix, width) \ - static inline int trailing_zeros##suffix(type n) { \ + static inline unsigned int trailing_zeros##suffix(type n) { \ type bit = 1; \ int ret; \ for (ret = 0; bit && !(n & bit); ++ret, bit <<= 1); \ @@ -322,7 +310,7 @@ static inline uint8_t bswap_u8(uint8_t n) { } #define FIRST_TRAILING_ONE(type, suffix, width) \ - static inline int first_trailing_one##suffix(type n) { \ + static inline unsigned int first_trailing_one##suffix(type n) { \ return n ? trailing_zeros##suffix(n) + 1 : 0; \ } @@ -333,19 +321,9 @@ UINT_OVERLOADS(LEADING_ZEROS) UINT_OVERLOADS(TRAILING_ZEROS) UINT_OVERLOADS(FIRST_TRAILING_ONE) -#define ROTATE_LEFT(type, suffix, width) \ - static inline type rotate_left##suffix(type n, int c) { \ - return (n << c) | (n >> ((width - c) % width)); \ - } - -#define ROTATE_RIGHT(type, suffix, width) \ - static inline type rotate_right##suffix(type n, int c) { \ - return (n >> c) | (n << ((width - c) % width)); \ - } - #define FIRST_LEADING_ONE(type, suffix, width) \ - static inline int first_leading_one##suffix(type n) { \ - return width - leading_zeros##suffix(n); \ + static inline unsigned int first_leading_one##suffix(type n) { \ + return n ? leading_zeros##suffix(n) + 1 : 0; \ } #define HAS_SINGLE_BIT(type, suffix, width) \ @@ -354,17 +332,30 @@ UINT_OVERLOADS(FIRST_TRAILING_ONE) return n - 1 < (n ^ (n - 1)); \ } -UINT_OVERLOADS(ROTATE_LEFT) -UINT_OVERLOADS(ROTATE_RIGHT) +#define BIT_WIDTH(type, suffix, width) \ + static inline unsigned int bit_width##suffix(type n) { \ + return width - leading_zeros##suffix(n); \ + } + +#define BIT_FLOOR(type, suffix, width) \ + static inline type bit_floor##suffix(type n) { \ + return n ? (type)1 << (bit_width##suffix(n) - 1) : 0; \ + } + +#define BIT_CEIL(type, suffix, width) \ + static inline type bit_ceil##suffix(type n) { \ + return (type)1 << bit_width##suffix(n - !!n); \ + } + UINT_OVERLOADS(FIRST_LEADING_ONE) UINT_OVERLOADS(HAS_SINGLE_BIT) +UINT_OVERLOADS(BIT_WIDTH) +UINT_OVERLOADS(BIT_FLOOR) +UINT_OVERLOADS(BIT_CEIL) #define count_ones(n) UINT_SELECT(n, count_ones)(n) #define count_zeros(n) UINT_SELECT(n, count_ones)(~(n)) -#define rotate_left(n, c) UINT_SELECT(n, rotate_left)(n, c) -#define rotate_right(n, c) UINT_SELECT(n, rotate_right)(n, c) - #define leading_zeros(n) UINT_SELECT(n, leading_zeros)(n) #define leading_ones(n) UINT_SELECT(n, leading_zeros)(~(n)) @@ -379,23 +370,26 @@ UINT_OVERLOADS(HAS_SINGLE_BIT) #define has_single_bit(n) UINT_SELECT(n, has_single_bit)(n) -#define BIT_FLOOR(type, suffix, width) \ - static inline type bit_floor##suffix(type n) { \ - return n ? (type)1 << (first_leading_one##suffix(n) - 1) : 0; \ - } +#define bit_width(n) UINT_SELECT(n, bit_width)(n) +#define bit_floor(n) UINT_SELECT(n, bit_floor)(n) +#define bit_ceil(n) UINT_SELECT(n, bit_ceil)(n) -#define BIT_CEIL(type, suffix, width) \ - static inline type bit_ceil##suffix(type n) { \ - return (type)1 << first_leading_one##suffix(n - !!n); \ +#endif // __STDC_VERSION_STDBIT_H__ < C23 + +#define ROTATE_LEFT(type, suffix, width) \ + static inline type rotate_left##suffix(type n, int c) { \ + return (n << c) | (n >> ((width - c) % width)); \ } -UINT_OVERLOADS(BIT_FLOOR) -UINT_OVERLOADS(BIT_CEIL) +#define ROTATE_RIGHT(type, suffix, width) \ + static inline type rotate_right##suffix(type n, int c) { \ + return (n >> c) | (n << ((width - c) % width)); \ + } -#define bit_width(n) first_leading_one(n) -#define bit_floor(n) UINT_SELECT(n, bit_floor)(n) -#define bit_ceil(n) UINT_SELECT(n, bit_ceil)(n) +UINT_OVERLOADS(ROTATE_LEFT) +UINT_OVERLOADS(ROTATE_RIGHT) -#endif // __STDC_VERSION__ < C23 +#define rotate_left(n, c) UINT_SELECT(n, rotate_left)(n, c) +#define rotate_right(n, c) UINT_SELECT(n, rotate_right)(n, c) #endif // BFS_BIT_H diff --git a/src/color.c b/src/color.c index 137d795..81f28bb 100644 --- a/src/color.c +++ b/src/color.c @@ -1171,7 +1171,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, i } int count = 0; - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { if (dstrcat(&cfile->buffer, " ") != 0) { return -1; } @@ -56,6 +56,7 @@ struct bfs_ctx *bfs_ctx_new(void) { goto fail; } ctx->cur_nofile = ctx->orig_nofile; + ctx->raise_nofile = true; ctx->users = bfs_users_new(); if (!ctx->users) { @@ -228,9 +229,7 @@ static int bfs_ctx_fclose(struct bfs_ctx *ctx, struct bfs_ctx_file *ctx_file) { error = errno; } - if (ctx_file->hook) { - sigunhook(ctx_file->hook); - } + sigunhook(ctx_file->hook); // Close the CFILE, except for stdio streams, which are closed later if (cfile != ctx->cout && cfile != ctx->cerr) { @@ -102,6 +102,8 @@ struct bfs_ctx { struct rlimit orig_nofile; /** The current RLIMIT_NOFILE limits. */ struct rlimit cur_nofile; + /** Whether the fd limit should be raised. */ + bool raise_nofile; /** The current time. */ struct timespec now; @@ -159,7 +159,7 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs } } - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { ret |= highlight_expr_recursive(ctx, child, args); } @@ -7,6 +7,7 @@ #include "prelude.h" #include "eval.h" +#include "atomic.h" #include "bar.h" #include "bfstd.h" #include "bftw.h" @@ -22,6 +23,7 @@ #include "printf.h" #include "pwcache.h" #include "sanity.h" +#include "sighook.h" #include "stat.h" #include "trie.h" #include "xregex.h" @@ -402,7 +404,7 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c } } - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { if (eval_exec_finish(child, ctx) != 0) { ret = -1; } @@ -997,6 +999,13 @@ bool eval_xtype(const struct bfs_expr *expr, struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf; enum bfs_stat_flags flags = ftwbuf->stat_flags ^ (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW); enum bfs_type type = bftw_type(ftwbuf, flags); + + // GNU find treats ELOOP as a broken symbolic link for -xtype l + // (but not -L -type l) + if ((flags & BFS_STAT_TRYFOLLOW) && type == BFS_ERROR && errno == ELOOP) { + type = BFS_LNK; + } + if (type == BFS_ERROR) { eval_report_error(state); return false; @@ -1089,7 +1098,7 @@ bool eval_not(const struct bfs_expr *expr, struct bfs_eval *state) { * Evaluate a conjunction. */ bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) { - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { if (!eval_expr(child, state) || state->quit) { return false; } @@ -1102,7 +1111,7 @@ bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) { * Evaluate a disjunction. */ bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) { - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { if (eval_expr(child, state) || state->quit) { return true; } @@ -1117,7 +1126,7 @@ bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) { bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) { bool ret uninit(false); - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { ret = eval_expr(child, state); if (state->quit) { break; @@ -1284,7 +1293,7 @@ static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, enu DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW); DEBUG_FLAG(flags, BFS_STAT_NOSYNC); - fprintf(stderr, ") == %d", err ? 0 : -1); + fprintf(stderr, ") == %d", err == 0 ? 0 : -1); if (err) { fprintf(stderr, " [%d]", err); @@ -1373,6 +1382,11 @@ struct callback_args { struct bfs_bar *bar; /** The time of the last status update. */ struct timespec last_status; + /** SIGINFO hook. */ + struct sighook *info_hook; + /** Flag set by SIGINFO hook. */ + atomic bool info_flag; + /** The number of files visited so far. */ size_t count; @@ -1399,15 +1413,38 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) { state.ret = &args->ret; state.quit = false; + // Check whether SIGINFO was delivered and show/hide the bar + if (exchange(&args->info_flag, false, relaxed)) { + if (args->bar) { + bfs_bar_hide(args->bar); + args->bar = NULL; + } else { + args->bar = bfs_bar_show(); + if (!args->bar) { + bfs_warning(ctx, "Couldn't show status bar: %s.\n", errstr()); + } + } + } + if (args->bar) { eval_status(&state, args->bar, &args->last_status, args->count); } if (ftwbuf->type == BFS_ERROR) { - if (!eval_should_ignore(&state, ftwbuf->error)) { - eval_error(&state, "%s.\n", xstrerror(ftwbuf->error)); - } state.action = BFTW_PRUNE; + + if (ftwbuf->error == ELOOP && ftwbuf->loopoff > 0) { + char *loop = strndup(ftwbuf->path, ftwbuf->loopoff); + if (loop) { + eval_error(&state, "Filesystem loop back to ${di}%pq${rs}\n", loop); + free(loop); + goto done; + } + } else if (eval_should_ignore(&state, ftwbuf->error)) { + goto done; + } + + eval_error(&state, "%s.\n", xstrerror(ftwbuf->error)); goto done; } @@ -1462,10 +1499,19 @@ done: return state.action; } +/** Show/hide the bar in response to SIGINFO. */ +static void eval_siginfo(int sig, siginfo_t *info, void *ptr) { + struct callback_args *args = ptr; + store(&args->info_flag, true, relaxed); +} + /** Raise RLIMIT_NOFILE if possible, and return the new limit. */ static int raise_fdlimit(struct bfs_ctx *ctx) { rlim_t cur = ctx->orig_nofile.rlim_cur; rlim_t max = ctx->orig_nofile.rlim_max; + if (!ctx->raise_nofile) { + max = cur; + } rlim_t target = 64 << 10; if (rlim_cmp(target, max) > 0) { @@ -1594,7 +1640,7 @@ static bool eval_must_buffer(const struct bfs_expr *expr) { return true; } - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { if (eval_must_buffer(child)) { return true; } @@ -1621,6 +1667,13 @@ int bfs_eval(struct bfs_ctx *ctx) { } } +#ifdef SIGINFO + int siginfo = SIGINFO; +#else + int siginfo = SIGUSR1; +#endif + args.info_hook = sighook(siginfo, eval_siginfo, &args, SH_CONTINUE); + struct trie seen; if (ctx->unique) { trie_init(&seen); @@ -1688,6 +1741,7 @@ int bfs_eval(struct bfs_ctx *ctx) { trie_destroy(&seen); } + sigunhook(args.info_hook); bfs_bar_hide(args.bar); return args.ret; @@ -244,4 +244,10 @@ bool bfs_expr_cmp(const struct bfs_expr *expr, long long n); */ void bfs_expr_clear(struct bfs_expr *expr); +/** + * Iterate over the children of an expression. + */ +#define for_expr(child, expr) \ + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) + #endif // BFS_EXPR_H diff --git a/src/fsade.h b/src/fsade.h index eefef9f..4465017 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -13,9 +13,9 @@ #define BFS_CAN_CHECK_ACL (BFS_HAS_ACL_GET_FILE || BFS_HAS_ACL_TRIVIAL) -#define BFS_CAN_CHECK_CAPABILITIES BFS_USE_LIBCAP +#define BFS_CAN_CHECK_CAPABILITIES BFS_WITH_LIBCAP -#define BFS_CAN_CHECK_CONTEXT BFS_USE_LIBSELINUX +#define BFS_CAN_CHECK_CONTEXT BFS_WITH_LIBSELINUX #define BFS_CAN_CHECK_XATTRS (BFS_USE_SYS_EXTATTR_H || BFS_USE_SYS_XATTR_H) @@ -135,7 +135,7 @@ #include <stdlib.h> #include <sys/stat.h> -#if BFS_USE_LIBURING +#if BFS_WITH_LIBURING # include <liburing.h> #endif @@ -459,7 +459,7 @@ static void ioq_batch_push(struct ioqq *ioqq, struct ioq_batch *batch, struct io /** Sentinel stop command. */ static struct ioq_ent IOQ_STOP; -#if BFS_USE_LIBURING +#if BFS_WITH_LIBURING /** * Supported io_uring operations. */ @@ -477,7 +477,7 @@ struct ioq_thread { /** Pointer back to the I/O queue. */ struct ioq *parent; -#if BFS_USE_LIBURING +#if BFS_WITH_LIBURING /** io_uring instance. */ struct io_uring ring; /** Any error that occurred initializing the ring. */ @@ -497,7 +497,7 @@ struct ioq { /** ioq_ent arena. */ struct arena ents; -#if BFS_USE_LIBURING && BFS_USE_STATX +#if BFS_WITH_LIBURING && BFS_USE_STATX /** struct statx arena. */ struct arena xbufs; #endif @@ -559,7 +559,7 @@ static void ioq_dispatch_sync(struct ioq *ioq, struct ioq_ent *ent) { ent->result = -ENOSYS; } -#if BFS_USE_LIBURING +#if BFS_WITH_LIBURING /** io_uring worker state. */ struct ioq_ring_state { @@ -775,7 +775,7 @@ static void ioq_ring_work(struct ioq_thread *thread) { } } -#endif // BFS_USE_LIBURING +#endif // BFS_WITH_LIBURING /** Synchronous syscall loop. */ static void ioq_sync_work(struct ioq_thread *thread) { @@ -811,7 +811,7 @@ static void ioq_sync_work(struct ioq_thread *thread) { static void *ioq_work(void *ptr) { struct ioq_thread *thread = ptr; -#if BFS_USE_LIBURING +#if BFS_WITH_LIBURING if (thread->ring_err == 0) { ioq_ring_work(thread); return NULL; @@ -824,7 +824,7 @@ static void *ioq_work(void *ptr) { /** Initialize io_uring thread state. */ static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) { -#if BFS_USE_LIBURING +#if BFS_WITH_LIBURING struct ioq_thread *prev = NULL; if (thread > ioq->threads) { prev = thread - 1; @@ -890,7 +890,7 @@ static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) { /** Destroy an io_uring. */ static void ioq_ring_exit(struct ioq_thread *thread) { -#if BFS_USE_LIBURING +#if BFS_WITH_LIBURING if (thread->ring_err == 0) { io_uring_queue_exit(&thread->ring); } @@ -926,7 +926,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { ioq->depth = depth; ARENA_INIT(&ioq->ents, struct ioq_ent); -#if BFS_USE_LIBURING && BFS_USE_STATX +#if BFS_WITH_LIBURING && BFS_USE_STATX ARENA_INIT(&ioq->xbufs, struct statx); #endif @@ -1036,7 +1036,7 @@ int ioq_stat(struct ioq *ioq, int dfd, const char *path, enum bfs_stat_flags fla args->flags = flags; args->buf = buf; -#if BFS_USE_LIBURING && BFS_USE_STATX +#if BFS_WITH_LIBURING && BFS_USE_STATX args->xbuf = arena_alloc(&ioq->xbufs); if (!args->xbuf) { ioq_free(ioq, ent); @@ -1060,7 +1060,7 @@ void ioq_free(struct ioq *ioq, struct ioq_ent *ent) { bfs_assert(ioq->size > 0); --ioq->size; -#if BFS_USE_LIBURING && BFS_USE_STATX +#if BFS_WITH_LIBURING && BFS_USE_STATX if (ent->op == IOQ_STAT && ent->stat.xbuf) { arena_free(&ioq->xbufs, ent->stat.xbuf); } @@ -1091,7 +1091,7 @@ void ioq_destroy(struct ioq *ioq) { ioqq_destroy(ioq->ready); ioqq_destroy(ioq->pending); -#if BFS_USE_LIBURING && BFS_USE_STATX +#if BFS_WITH_LIBURING && BFS_USE_STATX arena_destroy(&ioq->xbufs); #endif arena_destroy(&ioq->ents); @@ -324,6 +324,25 @@ LIST_VOID_(SLIST_INSERT_(list, &(list)->head, item, __VA_ARGS__)) /** + * Splice a singly-linked list into another. + * + * @param dest + * The destination list. + * @param cursor + * A pointer to the item to splice after, e.g. &list->head or list->tail. + * @param src + * The source list. + */ +#define SLIST_SPLICE(dest, cursor, src) \ + LIST_VOID_(SLIST_SPLICE_((dest), (cursor), (src))) + +#define SLIST_SPLICE_(dest, cursor, src) \ + *src->tail = *cursor, \ + *cursor = src->head, \ + dest->tail = *dest->tail ? src->tail : dest->tail, \ + SLIST_INIT(src) + +/** * Add an entire singly-linked list to the tail of another. * * @param dest @@ -332,10 +351,7 @@ * The source list. */ #define SLIST_EXTEND(dest, src) \ - SLIST_EXTEND_((dest), (src)) - -#define SLIST_EXTEND_(dest, src) \ - (src->head ? (*dest->tail = src->head, dest->tail = src->tail, SLIST_INIT(src)) : (void)0) + SLIST_SPLICE(dest, (dest)->tail, src) /** * Remove an item from a singly-linked list. @@ -41,6 +41,7 @@ * - thread.h (multi-threading) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) + * - version.c (embeds version information) * - xregex.[ch] (regular expression support) * - xspawn.[ch] (spawns processes) * - xtime.[ch] (date/time handling utilities) @@ -102,39 +102,20 @@ enum pred_type { PRED_TYPES, }; -/** Get the name of a predicate type. */ -static const char *pred_type_name(enum pred_type type) { - switch (type) { - case READABLE_PRED: - return "-readable"; - case WRITABLE_PRED: - return "-writable"; - case EXECUTABLE_PRED: - return "-executable"; - case ACL_PRED: - return "-acl"; - case CAPABLE_PRED: - return "-capable"; - case EMPTY_PRED: - return "-empty"; - case HIDDEN_PRED: - return "-hidden"; - case NOGROUP_PRED: - return "-nogroup"; - case NOUSER_PRED: - return "-nouser"; - case SPARSE_PRED: - return "-sparse"; - case XATTR_PRED: - return "-xattr"; - - case PRED_TYPES: - break; - } - - bfs_bug("Unknown predicate %d", (int)type); - return "???"; -} +/** Predicate type names. */ +static const char *const pred_names[] = { + [READABLE_PRED] = "-readable", + [WRITABLE_PRED] = "-writable", + [EXECUTABLE_PRED] = "-executable", + [ACL_PRED] = "-acl", + [CAPABLE_PRED] = "-capable", + [EMPTY_PRED] = "-empty", + [HIDDEN_PRED] = "-hidden", + [NOGROUP_PRED] = "-nogroup", + [NOUSER_PRED] = "-nouser", + [SPARSE_PRED] = "-sparse", + [XATTR_PRED] = "-xattr", +}; /** * A contrained integer range. @@ -242,29 +223,15 @@ enum range_type { RANGE_TYPES, }; -/** Get the name of a range type. */ -static const char *range_type_name(enum range_type type) { - switch (type) { - case DEPTH_RANGE: - return "-depth"; - case GID_RANGE: - return "-gid"; - case INUM_RANGE: - return "-inum"; - case LINKS_RANGE: - return "-links"; - case SIZE_RANGE: - return "-size"; - case UID_RANGE: - return "-uid"; - - case RANGE_TYPES: - break; - } - - bfs_bug("Unknown range %d", (int)type); - return "???"; -} +/** Range type names. */ +static const char *const range_names[] = { + [DEPTH_RANGE] = "-depth", + [GID_RANGE] = "-gid", + [INUM_RANGE] = "-inum", + [LINKS_RANGE] = "-links", + [SIZE_RANGE] = "-size", + [UID_RANGE] = "-uid", +}; /** * The data flow analysis domain. @@ -333,27 +300,27 @@ static void df_init_top(struct df_domain *value) { /** Check for the top element. */ static bool df_is_top(const struct df_domain *value) { - for (int i = 0; i < PRED_TYPES; ++i) { - if (value->preds[i] != PRED_TOP) { - return false; - } - } + for (int i = 0; i < PRED_TYPES; ++i) { + if (value->preds[i] != PRED_TOP) { + return false; + } + } - for (int i = 0; i < RANGE_TYPES; ++i) { - if (!range_is_top(&value->ranges[i])) { - return false; - } - } + for (int i = 0; i < RANGE_TYPES; ++i) { + if (!range_is_top(&value->ranges[i])) { + return false; + } + } - if (value->types != ~0U) { - return false; - } + if (value->types != ~0U) { + return false; + } - if (value->xtypes != ~0U) { - return false; - } + if (value->xtypes != ~0U) { + return false; + } - return true; + return true; } /** Compute the union of two fact sets. */ @@ -503,7 +470,7 @@ typedef bool dump_fn(struct bfs_opt *opt, const char *format, ...); /** Print a df_pred. */ static void pred_dump(dump_fn *dump, struct bfs_opt *opt, const struct df_domain *value, enum pred_type type) { - dump(opt, "${blu}%s${rs}: ", pred_type_name(type)); + dump(opt, "${blu}%s${rs}: ", pred_names[type]); FILE *file = opt->ctx->cerr->file; switch (value->preds[type]) { @@ -524,7 +491,7 @@ static void pred_dump(dump_fn *dump, struct bfs_opt *opt, const struct df_domain /** Print a df_range. */ static void range_dump(dump_fn *dump, struct bfs_opt *opt, const struct df_domain *value, enum range_type type) { - dump(opt, "${blu}%s${rs}: ", range_type_name(type)); + dump(opt, "${blu}%s${rs}: ", range_names[type]); FILE *file = opt->ctx->cerr->file; const struct df_range *range = &value->ranges[type]; @@ -1088,7 +1055,7 @@ static struct bfs_expr *annotate_and(struct bfs_opt *opt, struct bfs_expr *expr, expr->cost = 0.0; expr->probability = 1.0; - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { expr->pure &= child->pure; expr->always_true &= child->always_true; expr->always_false |= child->always_false; @@ -1107,7 +1074,7 @@ static struct bfs_expr *annotate_or(struct bfs_opt *opt, struct bfs_expr *expr, expr->cost = 0.0; float false_prob = 1.0; - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { expr->pure &= child->pure; expr->always_true |= child->always_true; expr->always_false &= child->always_false; @@ -1124,7 +1091,7 @@ static struct bfs_expr *annotate_comma(struct bfs_opt *opt, struct bfs_expr *exp expr->pure = true; expr->cost = 0.0; - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { expr->pure &= child->pure; expr->always_true = child->always_true; expr->always_false = child->always_false; @@ -1790,7 +1757,7 @@ static struct bfs_expr *data_flow_leave(struct bfs_opt *opt, struct bfs_expr *ex if (df_is_bottom(&opt->after_false)) { if (!expr->pure) { expr->always_true = true; - expr->probability = 0.0; + expr->probability = 1.0; } else if (expr->eval_fn != eval_true) { opt_warning(opt, expr, "This expression is always true.\n\n"); opt_debug(opt, "pure, always true\n"); @@ -1919,7 +1886,7 @@ static struct bfs_expr *simplify_not(struct bfs_opt *opt, struct bfs_expr *expr, static struct bfs_expr *lift_andor_not(struct bfs_opt *opt, struct bfs_expr *expr) { // Only lift negations if it would reduce the number of (-not) expressions size_t added = 0, removed = 0; - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { if (child->eval_fn == eval_not) { ++removed; } else { @@ -1968,7 +1935,7 @@ static struct bfs_expr *first_ignorable(struct bfs_opt *opt, struct bfs_expr *ex } struct bfs_expr *ret = NULL; - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { if (!child->pure) { ret = NULL; } else if (!ret) { @@ -2176,17 +2143,20 @@ static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) { return expr; } -/** Estimate the odds of an expression calling stat(). */ -static float expr_stat_odds(struct bfs_expr *expr) { - if (expr->calls_stat) { +/** An expression predicate. */ +typedef bool expr_pred(const struct bfs_expr *expr); + +/** Estimate the odds that a matching expression will be evaluated. */ +static float estimate_odds(const struct bfs_expr *expr, expr_pred *pred) { + if (pred(expr)) { return 1.0; } - float nostat_odds = 1.0; + float nonmatch_odds = 1.0; float reached_odds = 1.0; - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { - float child_odds = expr_stat_odds(child); - nostat_odds *= 1.0 - reached_odds * child_odds; + for_expr (child, expr) { + float child_odds = estimate_odds(child, pred); + nonmatch_odds *= 1.0 - reached_odds * child_odds; if (expr->eval_fn == eval_and) { reached_odds *= child->probability; @@ -2195,7 +2165,12 @@ static float expr_stat_odds(struct bfs_expr *expr) { } } - return 1.0 - nostat_odds; + return 1.0 - nonmatch_odds; +} + +/** Whether an expression calls stat(). */ +static bool calls_stat(const struct bfs_expr *expr) { + return expr->calls_stat; } /** Estimate the odds of calling stat(). */ @@ -2204,15 +2179,20 @@ static float estimate_stat_odds(struct bfs_ctx *ctx) { return 1.0; } - float nostat_odds = 1.0 - expr_stat_odds(ctx->exclude); + float nostat_odds = 1.0 - estimate_odds(ctx->exclude, calls_stat); float reached_odds = 1.0 - ctx->exclude->probability; - float expr_odds = expr_stat_odds(ctx->expr); + float expr_odds = estimate_odds(ctx->expr, calls_stat); nostat_odds *= 1.0 - reached_odds * expr_odds; return 1.0 - nostat_odds; } +/** Matches -(exec|ok) ... \; */ +static bool single_exec(const struct bfs_expr *expr) { + return expr->eval_fn == eval_exec && !(expr->exec->flags & BFS_EXEC_MULTI); +} + int bfs_optimize(struct bfs_ctx *ctx) { bfs_ctx_dump(ctx, DEBUG_OPT); @@ -2291,6 +2271,17 @@ int bfs_optimize(struct bfs_ctx *ctx) { opt_leave(&opt, "eager stat cost: ${ylw}%g${rs}\n", eager_cost); } +#ifndef POSIX_SPAWN_SETRLIMIT + // If bfs_spawn_setrlimit() would force us to use fork() over + // posix_spawn(), the extra cost may outweigh the benefit of a + // higher RLIMIT_NOFILE + float single_exec_odds = estimate_odds(ctx->expr, single_exec); + if (single_exec_odds >= 0.5) { + opt_enter(&opt, "single ${blu}-exec${rs} odds: ${ylw}%g${rs}\n", single_exec_odds); + ctx->raise_nofile = false; + opt_leave(&opt, "not raising RLIMIT_NOFILE\n"); + } +#endif } opt_leave(&opt, NULL); diff --git a/src/parse.c b/src/parse.c index 8a8eb41..9035344 100644 --- a/src/parse.c +++ b/src/parse.c @@ -115,21 +115,28 @@ struct bfs_parser { }; /** - * Possible token types. + * Token types and flags. */ -enum token_type { +enum token_info { /** A flag. */ - T_FLAG, + T_FLAG = 1, /** A root path. */ - T_PATH, + T_PATH = 2, /** An option. */ - T_OPTION, + T_OPTION = 3, /** A test. */ - T_TEST, + T_TEST = 4, /** An action. */ - T_ACTION, + T_ACTION = 5, /** An operator. */ - T_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, }; /** @@ -160,7 +167,6 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, */ attr(printf(2, 3)) static void parse_error(const struct bfs_parser *parser, const char *format, ...) { - int error = errno; const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; @@ -170,7 +176,6 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ... va_list args; va_start(args, format); - errno = error; bfs_verror(parser->ctx, format, args); va_end(args); } @@ -180,7 +185,6 @@ static void parse_error(const struct bfs_parser *parser, const char *format, ... */ attr(printf(4, 5)) static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_t argc, const char *format, ...) { - int error = errno; const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; @@ -190,7 +194,6 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_ va_list args; va_start(args, format); - errno = error; bfs_verror(ctx, format, args); va_end(args); } @@ -200,7 +203,6 @@ static void parse_argv_error(const struct bfs_parser *parser, char **argv, size_ */ attr(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, ...) { - int error = errno; const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; @@ -211,7 +213,6 @@ static void parse_conflict_error(const struct bfs_parser *parser, char **argv1, va_list args; va_start(args, format); - errno = error; bfs_verror(ctx, format, args); va_end(args); } @@ -221,14 +222,12 @@ static void parse_conflict_error(const struct bfs_parser *parser, char **argv1, */ attr(printf(3, 4)) static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) { - int error = errno; const struct bfs_ctx *ctx = parser->ctx; bfs_expr_error(ctx, expr); va_list args; va_start(args, format); - errno = error; bfs_verror(ctx, format, args); va_end(args); } @@ -238,7 +237,6 @@ static void parse_expr_error(const struct bfs_parser *parser, const struct bfs_e */ attr(printf(2, 3)) static bool parse_warning(const struct bfs_parser *parser, const char *format, ...) { - int error = errno; const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; @@ -250,7 +248,6 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, . va_list args; va_start(args, format); - errno = error; bool ret = bfs_vwarning(parser->ctx, format, args); va_end(args); return ret; @@ -261,7 +258,6 @@ static bool parse_warning(const struct bfs_parser *parser, const char *format, . */ attr(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, ...) { - int error = errno; const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; @@ -274,7 +270,6 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1 va_list args; va_start(args, format); - errno = error; bool ret = bfs_vwarning(ctx, format, args); va_end(args); return ret; @@ -285,7 +280,6 @@ static bool parse_conflict_warning(const struct bfs_parser *parser, char **argv1 */ attr(printf(3, 4)) static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs_expr *expr, const char *format, ...) { - int error = errno; const struct bfs_ctx *ctx = parser->ctx; if (!bfs_expr_warning(ctx, expr)) { @@ -294,7 +288,6 @@ static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs va_list args; va_start(args, format); - errno = error; bool ret = bfs_vwarning(ctx, format, args); va_end(args); return ret; @@ -414,7 +407,9 @@ 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_type type, size_t argc) { +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) { parser->expr_started = true; } @@ -517,20 +512,14 @@ enum int_flags { * Parse an integer. */ static const char *parse_int(const struct bfs_parser *parser, char **arg, const char *str, void *result, enum int_flags flags) { - // strtoll() skips leading spaces, but we want to reject them - if (xisspace(str[0])) { - goto bad; - } - int base = flags & IF_BASE_MASK; if (base == 0) { base = 10; } char *endptr; - errno = 0; - long long value = strtoll(str, &endptr, base); - if (errno != 0) { + long long value; + if (xstrtoll(str, &endptr, base, &value) != 0) { if (errno == ERANGE) { goto range; } else { @@ -538,13 +527,6 @@ static const char *parse_int(const struct bfs_parser *parser, char **arg, const } } - // https://github.com/llvm/llvm-project/issues/64946 - sanitize_init(&endptr); - - if (endptr == str) { - goto bad; - } - if (!(flags & IF_PARTIAL_OK) && *endptr != '\0') { goto bad; } @@ -657,9 +639,11 @@ static struct bfs_expr *parse_nullary_flag(struct bfs_parser *parser) { */ static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) { const char *arg = parser->argv[0]; + char flag = arg[strlen(arg) - 1]; + const char *value = parser->argv[1]; if (!value) { - parse_error(parser, "${cyn}%s${rs} needs a value.\n", arg); + parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag); return NULL; } @@ -667,6 +651,29 @@ static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) { } /** + * Parse a prefix flag like -O3, -j8, etc. + */ +static struct bfs_expr *parse_prefix_flag(struct bfs_parser *parser, char flag, bool allow_separate, const char **value) { + const char *arg = parser->argv[0]; + + const char *suffix = strchr(arg, flag) + 1; + if (*suffix) { + *value = suffix; + return parse_nullary_flag(parser); + } + + suffix = parser->argv[1]; + if (allow_separate && suffix) { + *value = suffix; + } else { + parse_error(parser, "${cyn}-%c${rs} needs a value.\n", flag); + return NULL; + } + + return parse_unary_flag(parser); +} + +/** * Parse a single option. */ static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) { @@ -811,7 +818,8 @@ static bool parse_debug_flag(const char *flag, size_t len, const char *expected) static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_ctx *ctx = parser->ctx; - struct bfs_expr *expr = parse_unary_flag(parser); + const char *flags; + struct bfs_expr *expr = parse_prefix_flag(parser, 'D', true, &flags); if (!expr) { cfprintf(ctx->cerr, "\n"); debug_help(ctx->cerr); @@ -820,7 +828,7 @@ static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg bool unrecognized = false; - for (const char *flag = expr->argv[1], *next; flag; flag = next) { + for (const char *flag = flags, *next; flag; flag = next) { size_t len = strcspn(flag, ","); if (flag[len]) { next = flag + len + 1; @@ -868,21 +876,22 @@ static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg * Parse -On. */ static struct bfs_expr *parse_optlevel(struct bfs_parser *parser, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_flag(parser); + const char *arg; + struct bfs_expr *expr = parse_prefix_flag(parser, 'O', false, &arg); if (!expr) { return NULL; } int *optlevel = &parser->ctx->optlevel; - if (strcmp(expr->argv[0], "-Ofast") == 0) { + if (strcmp(arg, "fast") == 0) { *optlevel = 4; - } else if (!parse_int(parser, expr->argv, expr->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { + } else if (!parse_int(parser, expr->argv, arg, optlevel, IF_INT | IF_UNSIGNED)) { return NULL; } if (*optlevel > 4) { - parse_expr_warning(parser, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", expr->argv[0] + 2); + parse_expr_warning(parser, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", arg); } return expr; @@ -1613,13 +1622,14 @@ static struct bfs_expr *parse_inum(struct bfs_parser *parser, int arg1, int arg2 * Parse -j<n>. */ static struct bfs_expr *parse_jobs(struct bfs_parser *parser, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_flag(parser); + const char *arg; + struct bfs_expr *expr = parse_prefix_flag(parser, 'j', false, &arg); if (!expr) { return NULL; } unsigned int n; - if (!parse_int(parser, expr->argv, expr->argv[0] + 2, &n, IF_INT | IF_UNSIGNED)) { + if (!parse_int(parser, expr->argv, arg, &n, IF_INT | IF_UNSIGNED)) { return NULL; } @@ -2253,16 +2263,27 @@ static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int // See https://www.gnu.org/software/gnulib/manual/html_node/Predefined-Syntaxes.html const char *type = expr->argv[1]; if (strcmp(type, "posix-basic") == 0 + || strcmp(type, "posix-minimal-basic") == 0 || strcmp(type, "ed") == 0 || strcmp(type, "sed") == 0) { parser->regex_type = BFS_REGEX_POSIX_BASIC; } else if (strcmp(type, "posix-extended") == 0) { parser->regex_type = BFS_REGEX_POSIX_EXTENDED; -#if BFS_USE_ONIGURUMA +#if BFS_WITH_ONIGURUMA + } else if (strcmp(type, "awk") == 0 + || strcmp(type, "posix-awk") == 0) { + parser->regex_type = BFS_REGEX_AWK; + } else if (strcmp(type, "gnu-awk") == 0) { + parser->regex_type = BFS_REGEX_GNU_AWK; } else if (strcmp(type, "emacs") == 0) { parser->regex_type = BFS_REGEX_EMACS; } else if (strcmp(type, "grep") == 0) { parser->regex_type = BFS_REGEX_GREP; + } else if (strcmp(type, "egrep") == 0 + || strcmp(type, "posix-egrep") == 0) { + parser->regex_type = BFS_REGEX_EGREP; + } else if (strcmp(type, "findutils-default") == 0) { + parser->regex_type = BFS_REGEX_GNU_FIND; #endif } else if (strcmp(type, "help") == 0) { parser->just_info = true; @@ -2277,14 +2298,23 @@ static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int list_types: cfprintf(cfile, "Supported types are:\n\n"); - cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n"); - cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n"); - cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n"); -#if BFS_USE_ONIGURUMA - cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n"); - cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n"); + cfprintf(cfile, " ${bld}posix-basic${rs}: POSIX basic regular expressions (BRE)\n"); + cfprintf(cfile, " ${bld}ed${rs}: Like ${grn}ed${rs} (same as ${bld}posix-basic${rs})\n"); + cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n\n"); + + cfprintf(cfile, " ${bld}posix-extended${rs}: POSIX extended regular expressions (ERE)\n\n"); + +#if BFS_WITH_ONIGURUMA + cfprintf(cfile, " [${bld}posix-${rs}]${bld}awk${rs}: Like ${grn}awk${rs}\n"); + cfprintf(cfile, " ${bld}gnu-awk${rs}: Like GNU ${grn}awk${rs}\n\n"); + + cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n\n"); + + cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n"); + cfprintf(cfile, " [${bld}posix-${rs}]${bld}egrep${rs}: Like ${grn}grep${rs} ${cyn}-E${rs}\n\n"); + + cfprintf(cfile, " ${bld}findutils-default${rs}: Like GNU ${grn}find${rs}\n"); #endif - cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n"); return NULL; } @@ -2322,13 +2352,13 @@ static struct bfs_expr *parse_search_strategy(struct bfs_parser *parser, int arg struct bfs_ctx *ctx = parser->ctx; CFILE *cfile = ctx->cerr; - struct bfs_expr *expr = parse_unary_flag(parser); + const char *arg; + struct bfs_expr *expr = parse_prefix_flag(parser, 'S', true, &arg); if (!expr) { cfprintf(cfile, "\n"); goto list_strategies; } - const char *arg = expr->argv[1]; if (strcmp(arg, "bfs") == 0) { ctx->strategy = BFTW_BFS; } else if (strcmp(arg, "dfs") == 0) { @@ -2928,12 +2958,23 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2 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); - printf("%s\n", BFS_HOMEPAGE); + printf("Copyright © Tavian Barnes and the bfs contributors\n"); + printf("No rights reserved (https://opensource.org/license/0BSD)\n\n"); + + printf("CONFIG := %s\n", bfs_config); + printf("CC := %s\n", bfs_cc); + printf("CPPFLAGS := %s\n", bfs_cppflags); + printf("CFLAGS := %s\n", bfs_cflags); + printf("LDFLAGS := %s\n", bfs_ldflags); + printf("LDLIBS := %s\n", bfs_ldlibs); + + printf("\n%s\n", BFS_HOMEPAGE); parser->just_info = true; return NULL; } +/** Parser callback function type. */ typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2); /** @@ -2941,11 +2982,10 @@ typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2) */ struct table_entry { char *arg; - enum token_type type; + enum token_info info; parse_fn *parse; int arg1; int arg2; - bool prefix; }; /** @@ -2959,13 +2999,13 @@ static const struct table_entry parse_table[] = { {"-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, parse_debug}, + {"-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, parse_optlevel, 0, 0, true}, + {"-O", T_FLAG | T_PREFIX, parse_optlevel}, {"-P", T_FLAG, parse_follow, 0, false}, - {"-S", T_FLAG, parse_search_strategy}, + {"-S", T_FLAG | T_PREFIX, parse_search_strategy}, {"-X", T_FLAG, parse_xargs_safe}, {"-a", T_OPERATOR}, {"-acl", T_TEST, parse_acl}, @@ -2991,7 +3031,7 @@ static const struct table_entry parse_table[] = { {"-execdir", T_ACTION, parse_exec, BFS_EXEC_CHDIR}, {"-executable", T_TEST, parse_access, X_OK}, {"-exit", T_ACTION, parse_exit}, - {"-f", T_FLAG, parse_f}, + {"-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}, @@ -3012,7 +3052,7 @@ static const struct table_entry parse_table[] = { {"-ipath", T_TEST, parse_path, true}, {"-iregex", T_TEST, parse_regex, BFS_REGEX_ICASE}, {"-iwholename", T_TEST, parse_path, true}, - {"-j", T_FLAG, parse_jobs, 0, 0, true}, + {"-j", T_FLAG | T_PREFIX, parse_jobs}, {"-limit", T_ACTION, parse_limit}, {"-links", T_TEST, parse_links}, {"-lname", T_TEST, parse_lname, false}, @@ -3026,7 +3066,7 @@ static const struct table_entry parse_table[] = { {"-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, parse_newerxy, 0, 0, true}, + {"-newer", T_TEST | T_PREFIX, parse_newerxy}, {"-nocolor", T_OPTION, parse_color, false}, {"-nogroup", T_TEST, parse_nogroup}, {"-nohidden", T_TEST, parse_nohidden}, @@ -3079,7 +3119,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->prefix) { + if (entry->info & T_PREFIX) { match = strncmp(arg, entry->arg, strlen(entry->arg)) == 0; } else { match = strcmp(arg, entry->arg) == 0; @@ -3092,6 +3132,85 @@ static const struct table_entry *table_lookup(const char *arg) { return NULL; } +/** 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]) { + return entry; + } + } + + return NULL; +} + +/** Check for a multi-flag argument like -LEXO2. */ +static bool is_flag_group(const char *arg) { + // We enforce that at least one flag in a flag group must be a capital + // letter, to avoid ambiguity with primary expressions + bool has_upper = false; + + // Flags that take an argument must appear last + bool needs_arg = false; + + for (size_t i = 1; arg[i]; ++i) { + char c = arg[i]; + if (c >= 'A' && c <= 'Z') { + has_upper = true; + } + + if (needs_arg) { + return false; + } + + const struct table_entry *entry = flag_lookup(c); + if (!entry || !entry->parse) { + return false; + } + + if (entry->info & T_PREFIX) { + // The rest is the flag's argument + break; + } + + if (entry->info & T_NEEDS_ARG) { + needs_arg = true; + } + } + + return has_upper; +} + +/** Parse a multi-flag argument. */ +static struct bfs_expr *parse_flag_group(struct bfs_parser *parser) { + struct bfs_expr *expr = NULL; + + char **start = parser->argv; + char **end = start; + const char *arg = start[0]; + + for (size_t i = 1; arg[i]; ++i) { + parser->argv = start; + + const struct table_entry *entry = flag_lookup(arg[i]); + expr = entry->parse(parser, entry->arg1, entry->arg2); + + if (parser->argv > end) { + end = parser->argv; + } + + if (!expr || entry->info & T_PREFIX) { + break; + } + } + + if (expr) { + bfs_assert(parser->argv == end, "Didn't eat enough tokens"); + } + + return expr; +} + /** Search for a fuzzy match in the parse table. */ static const struct table_entry *table_lookup_fuzzy(const char *arg) { const struct table_entry *best = NULL; @@ -3130,11 +3249,15 @@ static struct bfs_expr *parse_primary(struct bfs_parser *parser) { } } + if (is_flag_group(arg)) { + return parse_flag_group(parser); + } + match = table_lookup_fuzzy(arg); CFILE *cerr = parser->ctx->cerr; parse_error(parser, "Unknown argument; did you mean "); - switch (match->type) { + switch (match->info & T_TYPE) { case T_FLAG: cfprintf(cerr, "${cyn}%s${rs}?", match->arg); break; @@ -3458,7 +3581,7 @@ static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag ++rparens; } else { cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]); - for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + for_expr (child, expr) { int parens = child->next ? 0 : rparens + 1; dump_expr_multiline(ctx, flag, child, indent + 1, parens); } diff --git a/src/prelude.h b/src/prelude.h index 3521fe8..bca16b2 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -40,6 +40,13 @@ // when the version number changes extern const char bfs_version[]; +extern const char bfs_config[]; +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 @@ -50,6 +57,9 @@ extern const char bfs_version[]; #if __has_include(<paths.h>) # define BFS_HAS_PATHS_H true #endif +#if __has_include(<stdbit.h>) +# define BFS_HAS_STDBIT_H true +#endif #if __has_include(<sys/extattr.h>) # define BFS_HAS_SYS_EXTATTR_H true #endif @@ -76,6 +86,7 @@ extern const char bfs_version[]; #define BFS_HAS_MNTENT_H __GLIBC__ #define BFS_HAS_PATHS_H true +#define BFS_HAS_STDBIT_H (__STDC_VERSION__ >= C23) #define BFS_HAS_SYS_EXTATTR_H __FreeBSD__ #define BFS_HAS_SYS_MKDEV_H false #define BFS_HAS_SYS_PARAM_H true @@ -133,7 +144,7 @@ extern const char bfs_version[]; /** * Get the length of an array. */ -#define countof(array) (sizeof(array) / sizeof(0[array])) +#define countof(...) (sizeof(__VA_ARGS__) / sizeof(0[__VA_ARGS__])) /** * False sharing/destructive interference/largest cache line size. diff --git a/src/printf.c b/src/printf.c index be09ebd..6b07c54 100644 --- a/src/printf.c +++ b/src/printf.c @@ -505,30 +505,25 @@ static int bfs_printf_u(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } static const char *bfs_printf_type(enum bfs_type type) { - switch (type) { - case BFS_BLK: - return "b"; - case BFS_CHR: - return "c"; - case BFS_DIR: - return "d"; - case BFS_DOOR: - return "D"; - case BFS_FIFO: - return "p"; - case BFS_LNK: - return "l"; - case BFS_PORT: - return "P"; - case BFS_REG: - return "f"; - case BFS_SOCK: - return "s"; - case BFS_WHT: - return "w"; - default: - return "U"; + const char *const names[] = { + [BFS_BLK] = "b", + [BFS_CHR] = "c", + [BFS_DIR] = "d", + [BFS_DOOR] = "D", + [BFS_FIFO] = "p", + [BFS_LNK] = "l", + [BFS_PORT] = "P", + [BFS_REG] = "f", + [BFS_SOCK] = "s", + [BFS_WHT] = "w", + }; + + const char *name = NULL; + if ((size_t)type < countof(names)) { + name = names[type]; } + + return name ? name : "U"; } /** %y: type */ @@ -544,7 +539,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF int error = 0; if (type == BFS_ERROR) { - if (errno_is_like(ELOOP)) { + if (errno == ELOOP) { str = "L"; } else if (errno_is_like(ENOENT)) { str = "N"; diff --git a/src/sighook.c b/src/sighook.c index ff5b96f..6d6ff01 100644 --- a/src/sighook.c +++ b/src/sighook.c @@ -52,6 +52,8 @@ struct arc { /** Initialize an arc. */ static void arc_init(struct arc *arc) { + bfs_verify(atomic_is_lock_free(&arc->refs)); + atomic_init(&arc->refs, 0); arc->ptr = NULL; @@ -166,6 +168,8 @@ static void *RCU_NULL = &RCU_NULL; /** Initialize an RCU block. */ static void rcu_init(struct rcu *rcu) { + bfs_verify(atomic_is_lock_free(&rcu->active)); + atomic_init(&rcu->active, 0); arc_init(&rcu->slots[0]); arc_init(&rcu->slots[1]); @@ -323,11 +327,16 @@ static int rcu_sigtable_add(struct rcu *rcu, struct sighook *hook) { return 0; } -/** The global table of signal hooks. */ -static struct rcu rcu_sighooks; -/** The global table of atsigexit() hooks. */ +/** The sharded table of signal hooks. */ +static struct rcu rcu_sighooks[64]; +/** The table of atsigexit() hooks. */ static struct rcu rcu_exithooks; +/** Get the table for a particular signal. */ +static struct rcu *sigshard(int sig) { + return &rcu_sighooks[sig % countof(rcu_sighooks)]; +} + /** Mutex for initialization and RCU writer exclusion. */ static pthread_mutex_t sigmutex = PTHREAD_MUTEX_INITIALIZER; @@ -475,7 +484,8 @@ static void sigdispatch(int sig, siginfo_t *info, void *context) { int error = errno; // Run the normal hooks - enum sigflags flags = run_hooks(&rcu_sighooks, sig, info); + struct rcu *shard = sigshard(sig); + enum sigflags flags = run_hooks(shard, sig, info); // Run the atsigexit() hooks, if we're exiting if (!(flags & SH_CONTINUE) && is_fatal(sig)) { @@ -501,8 +511,12 @@ static int siginit(int sig) { || sigemptyset(&action.sa_mask) != 0) { return -1; } - rcu_init(&rcu_sighooks); + + for (size_t i = 0; i < countof(rcu_sighooks); ++i) { + rcu_init(&rcu_sighooks[i]); + } rcu_init(&rcu_exithooks); + initialized = true; } @@ -552,7 +566,8 @@ struct sighook *sighook(int sig, sighook_fn *fn, void *arg, enum sigflags flags) goto done; } - ret = sighook_impl(&rcu_sighooks, sig, fn, arg, flags); + struct rcu *shard = sigshard(sig); + ret = sighook_impl(shard, sig, fn, arg, flags); done: mutex_unlock(&sigmutex); return ret; @@ -561,32 +576,38 @@ done: struct sighook *atsigexit(sighook_fn *fn, void *arg) { mutex_lock(&sigmutex); - struct sighook *ret = NULL; - for (size_t i = 0; i < countof(FATAL_SIGNALS); ++i) { - if (siginit(FATAL_SIGNALS[i]) != 0) { - goto done; - } + // Ignore errors; atsigexit() is best-effort anyway and things + // like sanitizer runtimes or valgrind may reserve signals for + // their own use + siginit(FATAL_SIGNALS[i]); } #ifdef SIGRTMIN for (int i = SIGRTMIN; i <= SIGRTMAX; ++i) { - if (siginit(i) != 0) { - goto done; - } + siginit(i); } #endif - ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0); -done: + struct sighook *ret = sighook_impl(&rcu_exithooks, 0, fn, arg, 0); mutex_unlock(&sigmutex); return ret; } void sigunhook(struct sighook *hook) { + if (!hook) { + return; + } + mutex_lock(&sigmutex); - struct rcu *rcu = hook->sig ? &rcu_sighooks : &rcu_exithooks; + struct 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); @@ -51,7 +51,7 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { return "modification time"; } - bfs_bug("Unrecognized stat field"); + bfs_bug("Unrecognized stat field %d", (int)field); return "???"; } @@ -621,7 +621,7 @@ static void trie_free_singletons(struct trie *trie, uintptr_t ptr) { struct trie_node *node = trie_decode_node(ptr); // Make sure the bitmap is a power of two, i.e. it has just one child - bfs_assert(has_single_bit(node->bitmap)); + bfs_assert(has_single_bit((size_t)node->bitmap)); ptr = node->children[0]; trie_node_free(trie, node, 1); @@ -125,7 +125,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 = 12; + const int insert_cost = (40 + 12 + 1) / 2; size_t rows = strlen(actual) + 1; size_t cols = strlen(expected) + 1; diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..9e5c82f --- /dev/null +++ b/src/version.c @@ -0,0 +1,32 @@ +// Copyright © Tavian Barnes <tavianator@tavianator.com> +// SPDX-License-Identifier: 0BSD + +#include "prelude.h" + +const char bfs_version[] = { +#include "version.i" +}; + +const char bfs_config[] = { +#include "config.i" +}; + +const char bfs_cc[] = { +#include "cc.i" +}; + +const char bfs_cppflags[] = { +#include "cppflags.i" +}; + +const char bfs_cflags[] = { +#include "cflags.i" +}; + +const char bfs_ldflags[] = { +#include "ldflags.i" +}; + +const char bfs_ldlibs[] = { +#include "ldlibs.i" +}; diff --git a/src/xregex.c b/src/xregex.c index c2711bc..2d089b2 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -13,7 +13,7 @@ #include <stdlib.h> #include <string.h> -#if BFS_USE_ONIGURUMA +#if BFS_WITH_ONIGURUMA # include <langinfo.h> # include <oniguruma.h> #else @@ -21,7 +21,7 @@ #endif struct bfs_regex { -#if BFS_USE_ONIGURUMA +#if BFS_WITH_ONIGURUMA unsigned char *pattern; OnigRegex impl; int err; @@ -32,11 +32,17 @@ struct bfs_regex { #endif }; -#if BFS_USE_ONIGURUMA +#if BFS_WITH_ONIGURUMA static int bfs_onig_status; static OnigEncoding bfs_onig_enc; +static OnigSyntaxType bfs_onig_syntax_awk; +static OnigSyntaxType bfs_onig_syntax_gnu_awk; +static OnigSyntaxType bfs_onig_syntax_emacs; +static OnigSyntaxType bfs_onig_syntax_egrep; +static OnigSyntaxType bfs_onig_syntax_gnu_find; + /** pthread_once() callback. */ static void bfs_onig_once(void) { // Fall back to ASCII by default @@ -103,6 +109,35 @@ static void bfs_onig_once(void) { if (bfs_onig_status != ONIG_NORMAL) { bfs_onig_enc = NULL; } + + // Compute the GNU extensions + OnigSyntaxType *ere = ONIG_SYNTAX_POSIX_EXTENDED; + OnigSyntaxType *gnu = ONIG_SYNTAX_GNU_REGEX; + unsigned int gnu_op = gnu->op & ~ere->op; + unsigned int gnu_op2 = gnu->op2 & ~ere->op2; + unsigned int gnu_behavior = gnu->behavior & ~ere->behavior; + + onig_copy_syntax(&bfs_onig_syntax_awk, ONIG_SYNTAX_POSIX_EXTENDED); + bfs_onig_syntax_awk.behavior |= ONIG_SYN_ALLOW_INVALID_INTERVAL; + bfs_onig_syntax_awk.behavior |= ONIG_SYN_BACKSLASH_ESCAPE_IN_CC; + + onig_copy_syntax(&bfs_onig_syntax_gnu_awk, &bfs_onig_syntax_awk); + bfs_onig_syntax_gnu_awk.op |= gnu_op; + bfs_onig_syntax_gnu_awk.op2 |= gnu_op2; + bfs_onig_syntax_gnu_awk.behavior |= gnu_behavior; + bfs_onig_syntax_gnu_awk.behavior &= ~ONIG_SYN_CONTEXT_INDEP_REPEAT_OPS; + bfs_onig_syntax_gnu_awk.behavior &= ~ONIG_SYN_CONTEXT_INVALID_REPEAT_OPS; + + // https://github.com/kkos/oniguruma/issues/296 + onig_copy_syntax(&bfs_onig_syntax_emacs, ONIG_SYNTAX_EMACS); + bfs_onig_syntax_emacs.op2 |= ONIG_SYN_OP2_QMARK_GROUP_EFFECT; + + onig_copy_syntax(&bfs_onig_syntax_egrep, ONIG_SYNTAX_POSIX_EXTENDED); + bfs_onig_syntax_egrep.behavior |= ONIG_SYN_ALLOW_INVALID_INTERVAL; + bfs_onig_syntax_egrep.behavior &= ~ONIG_SYN_CONTEXT_INVALID_REPEAT_OPS; + + onig_copy_syntax(&bfs_onig_syntax_gnu_find, &bfs_onig_syntax_emacs); + bfs_onig_syntax_gnu_find.options |= ONIG_OPTION_MULTILINE; } /** Initialize Oniguruma. */ @@ -121,7 +156,7 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ return -1; } -#if BFS_USE_ONIGURUMA +#if BFS_WITH_ONIGURUMA // onig_error_code_to_str() says // // don't call this after the pattern argument of onig_new() is freed @@ -143,12 +178,24 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ case BFS_REGEX_POSIX_EXTENDED: syntax = ONIG_SYNTAX_POSIX_EXTENDED; break; + case BFS_REGEX_AWK: + syntax = &bfs_onig_syntax_awk; + break; + case BFS_REGEX_GNU_AWK: + syntax = &bfs_onig_syntax_gnu_awk; + break; case BFS_REGEX_EMACS: - syntax = ONIG_SYNTAX_EMACS; + syntax = &bfs_onig_syntax_emacs; break; case BFS_REGEX_GREP: syntax = ONIG_SYNTAX_GREP; break; + case BFS_REGEX_EGREP: + syntax = &bfs_onig_syntax_egrep; + break; + case BFS_REGEX_GNU_FIND: + syntax = &bfs_onig_syntax_gnu_find; + break; } bfs_assert(syntax, "Invalid regex type"); @@ -204,7 +251,7 @@ fail: int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags flags) { size_t len = strlen(str); -#if BFS_USE_ONIGURUMA +#if BFS_WITH_ONIGURUMA const unsigned char *ustr = (const unsigned char *)str; const unsigned char *end = ustr + len; @@ -263,7 +310,7 @@ int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags void bfs_regfree(struct bfs_regex *regex) { if (regex) { -#if BFS_USE_ONIGURUMA +#if BFS_WITH_ONIGURUMA onig_free(regex->impl); free(regex->pattern); #else @@ -278,7 +325,7 @@ char *bfs_regerror(const struct bfs_regex *regex) { return strdup(xstrerror(ENOMEM)); } -#if BFS_USE_ONIGURUMA +#if BFS_WITH_ONIGURUMA unsigned char *str = malloc(ONIG_MAX_ERROR_MESSAGE_LEN); if (str) { onig_error_code_to_str(str, regex->err, ®ex->einfo); diff --git a/src/xregex.h b/src/xregex.h index 998a2b0..750db24 100644 --- a/src/xregex.h +++ b/src/xregex.h @@ -15,8 +15,12 @@ struct bfs_regex; enum bfs_regex_type { BFS_REGEX_POSIX_BASIC, BFS_REGEX_POSIX_EXTENDED, + BFS_REGEX_AWK, + BFS_REGEX_GNU_AWK, BFS_REGEX_EMACS, BFS_REGEX_GREP, + BFS_REGEX_EGREP, + BFS_REGEX_GNU_FIND, }; /** diff --git a/src/xspawn.c b/src/xspawn.c index 33e5a4a..2c64011 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -426,8 +426,17 @@ static int bfs_resolve_early(struct bfs_resolver *res, const char *exe, const st }; if (bfs_can_skip_resolve(res, ctx)) { - res->done = true; - return 0; + // 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; + } } res->path = getenv("PATH"); diff --git a/src/xtime.c b/src/xtime.c index 2808455..186651b 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -206,6 +206,23 @@ static int xgetpart(const char **str, size_t n, int *result) { } int xgetdate(const char *str, struct timespec *result) { + // Handle @epochseconds + if (str[0] == '@') { + long long value; + if (xstrtoll(str + 1, NULL, 10, &value) != 0) { + goto error; + } + + time_t time = (time_t)value; + if ((long long)time != value) { + errno = ERANGE; + goto error; + } + + result->tv_sec = time; + goto done; + } + struct tm tm = { .tm_isdst = -1, }; @@ -324,6 +341,7 @@ end: } } +done: result->tv_nsec = 0; return 0; |