From d9b4950e8e42ac6a608457d3903817a5560107bd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 16 Nov 2022 14:58:31 -0500 Subject: README: Use `console` syntax highlighting more --- README.md | 55 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 33 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index c395342..643668c 100644 --- a/README.md +++ b/README.md @@ -71,9 +71,16 @@ haystack `find` will explore the entire `deep` directory tree before it ever gets to the `shallow` one that contains what you're looking for. +On the other hand, `bfs` lists files from shallowest to deepest, so you never have to wait for it to explore an entire unrelated subtree. -
-$ find haystack
+
+
+
+
+
+
+
+
+
bfsfind
+ +```console +$ find haystack haystack haystack/deep haystack/deep/1 @@ -82,23 +89,26 @@ haystack/deep/1/2/3 haystack/deep/1/2/3/4 ... haystack/shallow -haystack/shallow/needle - +haystack/shallow/needle +``` -On the other hand, `bfs` lists files from shallowest to deepest, so you never have to wait for it to explore an entire unrelated subtree. + -
-$ bfs haystack
+```console
+$ bfs haystack
 haystack
 haystack/deep
 haystack/shallow
 haystack/deep/1
-haystack/shallow/needle
-haystack/deep/1/2
-haystack/deep/1/2/3
-haystack/deep/1/2/3/4
+haystack/shallow/needle
 ...
-
+``` + +
@@ -154,21 +164,22 @@ haystack/needle For example, `bfs` will detect and suggest corrections for typos: -
+```console
 $ bfs -nam needle
-bfs: error: bfs -nam needle
-bfs: error:     ~~~~
-bfs: error: Unknown argument; did you mean -name?
-
+bfs: error: bfs -nam needle +bfs: error: ~~~~ +bfs: error: Unknown argument; did you mean -name? +``` `bfs` also includes a powerful static analysis to help catch mistakes: -
+```console
 $ bfs -print -name 'needle'
-bfs: warning: bfs -print -name needle
-bfs: warning:            ~~~~~~~~~~~~
-bfs: warning: The result of this expression is ignored.
-
+bfs: warning: bfs -print -name needle +bfs: warning: ~~~~~~~~~~~~ +bfs: warning: The result of this expression is ignored. +``` +
-- cgit v1.2.3 From 906b0e9742fc3cb7d0361e8a4350832f8d97ec15 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 16 Nov 2022 15:01:34 -0500 Subject: README: Add acl dependency to Fedora --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 643668c..513b8a2 100644 --- a/README.md +++ b/README.md @@ -269,7 +269,7 @@ Here's how to install them on some common platforms: # apt install acl libacl1-dev attr libattr1-dev libcap2-bin libcap-dev libonig-dev Fedora -# dnf install libacl-devel libattr-devel libcap-devel oniguruma-devel +# dnf install acl libacl-devel libattr-devel libcap-devel oniguruma-devel NixOS # nix-env -i acl attr libcap oniguruma -- cgit v1.2.3 From 3604eeddb8a317c0745e92680d0405c645fbe247 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 16 Nov 2022 15:02:14 -0500 Subject: trie: Limit target_clones to glibc Musl doesn't support ifuncs. --- src/trie.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/trie.c b/src/trie.c index 4bbdf13..1434356 100644 --- a/src/trie.c +++ b/src/trie.c @@ -103,16 +103,16 @@ #include #include -#if __has_attribute(target_clones) && (__i386__ || __x86_64__) +#if CHAR_BIT != 8 +# error "This trie implementation assumes 8-bit bytes." +#endif + +#if __GLIBC__ && __has_attribute(target_clones) && (__i386__ || __x86_64__) # define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) #else # define TARGET_CLONES_POPCNT #endif -#if CHAR_BIT != 8 -# error "This trie implementation assumes 8-bit bytes." -#endif - /** Number of bits for the sparse array bitmap, aka the range of a nibble. */ #define BITMAP_BITS 16 /** The number of remaining bits in a word, to hold the offset. */ -- cgit v1.2.3 From da02defb91c3a1bda0ea7e653d81f997f1c8884a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 17 Nov 2022 16:02:57 -0500 Subject: expr: Don't use reftime for -ls reftime is part of a different union than the print actions are supposed to use. --- src/ctx.c | 5 +++++ src/ctx.h | 4 ++++ src/eval.c | 2 +- src/parse.c | 10 ++-------- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/src/ctx.c b/src/ctx.c index b9d15bb..0403299 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -23,6 +23,7 @@ #include "pwcache.h" #include "stat.h" #include "trie.h" +#include "xtime.h" #include #include #include @@ -109,6 +110,10 @@ struct bfs_ctx *bfs_ctx_new(void) { goto fail; } + if (xgettime(&ctx->now) != 0) { + goto fail; + } + return ctx; fail: diff --git a/src/ctx.h b/src/ctx.h index d32db59..6755d02 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -26,6 +26,7 @@ #include #include #include +#include /** * Various debugging flags. @@ -127,6 +128,9 @@ struct bfs_ctx { rlim_t nofile_soft; /** The initial RLIMIT_NOFILE hard limit. */ rlim_t nofile_hard; + + /** The current time. */ + struct timespec now; }; /** diff --git a/src/eval.c b/src/eval.c index dd147c9..4c9d807 100644 --- a/src/eval.c +++ b/src/eval.c @@ -708,7 +708,7 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { } time_t time = statbuf->mtime.tv_sec; - time_t now = expr->reftime.tv_sec; + time_t now = ctx->now.tv_sec; time_t six_months_ago = now - 6*30*24*60*60; time_t tomorrow = now + 24*60*60; struct tm tm; diff --git a/src/parse.c b/src/parse.c index fc30cd2..a1e32fd 100644 --- a/src/parse.c +++ b/src/parse.c @@ -284,7 +284,7 @@ struct parser_state { /** An "-ok"-type expression, if any. */ const struct bfs_expr *ok_expr; - /** The current time. */ + /** The current time (maybe modified by -daystart). */ struct timespec now; }; @@ -1527,7 +1527,6 @@ static struct bfs_expr *parse_fls(struct parser_state *state, int arg1, int arg2 expr_set_always_true(expr); expr->cost = PRINT_COST; - expr->reftime = state->now; return expr; fail: @@ -1772,7 +1771,6 @@ static struct bfs_expr *parse_ls(struct parser_state *state, int arg1, int arg2) } init_print_expr(state, expr); - expr->reftime = state->now; return expr; } @@ -3892,6 +3890,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .files0_arg = NULL, .files0_stdin_arg = NULL, .ok_expr = NULL, + .now = ctx->now, }; if (strcmp(xbasename(state.command), "find") == 0) { @@ -3899,11 +3898,6 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { ctx->strategy = BFTW_DFS; } - if (xgettime(&state.now) != 0) { - parse_perror(&state, "xgettime()"); - goto fail; - } - ctx->exclude = &bfs_false; ctx->expr = parse_whole_expr(&state); if (!ctx->expr) { -- cgit v1.2.3 From 8b24de3882ff5a3e33b82ab20bb4eadf134cf559 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 19 Nov 2022 11:00:15 -0500 Subject: tests: Avoid syslog() using a low fd on macOS --- tests/tests.sh | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 8d13aca..26c993f 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -22,11 +22,11 @@ umask 022 export LC_ALL=C export TZ=UTC0 -export ASAN_OPTIONS="abort_on_error=1" -export LSAN_OPTIONS="abort_on_error=1" -export MSAN_OPTIONS="abort_on_error=1" -export TSAN_OPTIONS="abort_on_error=1" -export UBSAN_OPTIONS="abort_on_error=1" +export ASAN_OPTIONS="abort_on_error=1:log_to_syslog=0" +export LSAN_OPTIONS="abort_on_error=1:log_to_syslog=0" +export MSAN_OPTIONS="abort_on_error=1:log_to_syslog=0" +export TSAN_OPTIONS="abort_on_error=1:log_to_syslog=0" +export UBSAN_OPTIONS="abort_on_error=1:log_to_syslog=0" export LS_COLORS="" unset BFS_COLORS @@ -53,6 +53,17 @@ fi UNAME=$(uname) +if [ "$UNAME" = Darwin ]; then + # ASan on macOS likes to report + # + # malloc: nano zone abandoned due to inability to preallocate reserved vm space. + # + # to syslog, which as a side effect opens a socket which might take the + # place of one of the standard streams if the process is launched with it + # closed. This environment variable avoids the message. + export MallocNanoZone=0 +fi + if command -v capsh &>/dev/null; then if capsh --has-p=cap_dac_override &>/dev/null || capsh --has-p=cap_dac_read_search &>/dev/null; then if [ -n "${BFS_TRIED_DROP:-}" ]; then -- cgit v1.2.3 From a201b3e237e1994e0ee3668b25287fc9fdd574f9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 21 Nov 2022 10:43:26 -0500 Subject: config: Support target_clones on more platforms --- src/config.h | 7 +++++++ src/trie.c | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index 5bd49de..0822e3c 100644 --- a/src/config.h +++ b/src/config.h @@ -186,4 +186,11 @@ # define BFS_FORMATTER(fmt, args) #endif +/** + * Check if function multiversioning via GNU indirect functions (ifunc) is supported. + */ +#if !defined(BFS_TARGET_CLONES) && __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__ || __NetBSD__) +# define BFS_TARGET_CLONES true +#endif + #endif // BFS_CONFIG_H diff --git a/src/trie.c b/src/trie.c index 1434356..caa38a7 100644 --- a/src/trie.c +++ b/src/trie.c @@ -107,7 +107,7 @@ # error "This trie implementation assumes 8-bit bytes." #endif -#if __GLIBC__ && __has_attribute(target_clones) && (__i386__ || __x86_64__) +#if BFS_TARGET_CLONES && (__i386__ || __x86_64__) # define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) #else # define TARGET_CLONES_POPCNT -- cgit v1.2.3 From ba3ea2a9148da4c7ab0718e5ec077620ee29e88d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 29 Nov 2022 10:03:30 -0500 Subject: Dump expression trees in a multi-line format --- src/parse.c | 121 +++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 71 insertions(+), 50 deletions(-) diff --git a/src/parse.c b/src/parse.c index a1e32fd..f7c114d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -3682,6 +3682,49 @@ fail: return NULL; } +static const char *bftw_strategy_name(enum bftw_strategy strategy) { + switch (strategy) { + case BFTW_BFS: + return "bfs"; + case BFTW_DFS: + return "dfs"; + case BFTW_IDS: + return "ids"; + case BFTW_EDS: + return "eds"; + } + + assert(!"Invalid strategy"); + return "???"; +} + +static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag, const struct bfs_expr *expr, int indent, int rparens) { + bfs_debug_prefix(ctx, flag); + + for (int i = 0; i < indent; ++i) { + cfprintf(ctx->cerr, " "); + } + + if (bfs_expr_has_children(expr)) { + cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]); + if (expr->lhs) { + dump_expr_multiline(ctx, flag, expr->lhs, indent + 1, 0); + } + dump_expr_multiline(ctx, flag, expr->rhs, indent + 1, rparens + 1); + } else { + if (flag == DEBUG_RATES) { + cfprintf(ctx->cerr, "%pE", expr); + } else { + cfprintf(ctx->cerr, "%pe", expr); + } + + for (int i = 0; i < rparens; ++i) { + cfprintf(ctx->cerr, ")"); + } + cfprintf(ctx->cerr, "\n"); + } +} + void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { if (!bfs_debug_prefix(ctx, flag)) { return; @@ -3689,51 +3732,35 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { CFILE *cerr = ctx->cerr; - cfprintf(cerr, "${ex}%s${rs} ", ctx->argv[0]); + cfprintf(cerr, "${ex}%s${rs}", ctx->argv[0]); if (ctx->flags & BFTW_FOLLOW_ALL) { - cfprintf(cerr, "${cyn}-L${rs} "); + cfprintf(cerr, " ${cyn}-L${rs}"); } else if (ctx->flags & BFTW_FOLLOW_ROOTS) { - cfprintf(cerr, "${cyn}-H${rs} "); + cfprintf(cerr, " ${cyn}-H${rs}"); } else { - cfprintf(cerr, "${cyn}-P${rs} "); + cfprintf(cerr, " ${cyn}-P${rs}"); } if (ctx->xargs_safe) { - cfprintf(cerr, "${cyn}-X${rs} "); + cfprintf(cerr, " ${cyn}-X${rs}"); } if (ctx->flags & BFTW_SORT) { - cfprintf(cerr, "${cyn}-s${rs} "); + cfprintf(cerr, " ${cyn}-s${rs}"); } if (ctx->optlevel != 3) { - cfprintf(cerr, "${cyn}-O${bld}%d${rs} ", ctx->optlevel); + cfprintf(cerr, " ${cyn}-O${bld}%d${rs}", ctx->optlevel); } - const char *strategy = NULL; - switch (ctx->strategy) { - case BFTW_BFS: - strategy = "bfs"; - break; - case BFTW_DFS: - strategy = "dfs"; - break; - case BFTW_IDS: - strategy = "ids"; - break; - case BFTW_EDS: - strategy = "eds"; - break; - } - assert(strategy); - cfprintf(cerr, "${cyn}-S${rs} ${bld}%s${rs} ", strategy); + cfprintf(cerr, " ${cyn}-S${rs} ${bld}%s${rs}", bftw_strategy_name(ctx->strategy)); enum debug_flags debug = ctx->debug; if (debug == DEBUG_ALL) { - cfprintf(cerr, "${cyn}-D${rs} ${bld}all${rs} "); + cfprintf(cerr, " ${cyn}-D${rs} ${bld}all${rs}"); } else if (debug) { - cfprintf(cerr, "${cyn}-D${rs} "); + cfprintf(cerr, " ${cyn}-D${rs} "); for (enum debug_flags i = 1; DEBUG_ALL & i; i <<= 1) { if (debug & i) { cfprintf(cerr, "${bld}%s${rs}", debug_flag_name(i)); @@ -3743,61 +3770,55 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { } } } - cfprintf(cerr, " "); } for (size_t i = 0; i < darray_length(ctx->paths); ++i) { const char *path = ctx->paths[i]; char c = path[0]; if (c == '-' || c == '(' || c == ')' || c == '!' || c == ',') { - cfprintf(cerr, "${cyn}-f${rs} "); + cfprintf(cerr, " ${cyn}-f${rs}"); } - cfprintf(cerr, "${mag}%s${rs} ", path); + cfprintf(cerr, " ${mag}%s${rs}", path); } if (ctx->cout->colors) { - cfprintf(cerr, "${blu}-color${rs} "); + cfprintf(cerr, " ${blu}-color${rs}"); } else { - cfprintf(cerr, "${blu}-nocolor${rs} "); + cfprintf(cerr, " ${blu}-nocolor${rs}"); } if (ctx->flags & BFTW_POST_ORDER) { - cfprintf(cerr, "${blu}-depth${rs} "); + cfprintf(cerr, " ${blu}-depth${rs}"); } if (ctx->ignore_races) { - cfprintf(cerr, "${blu}-ignore_readdir_race${rs} "); + cfprintf(cerr, " ${blu}-ignore_readdir_race${rs}"); } if (ctx->mindepth != 0) { - cfprintf(cerr, "${blu}-mindepth${rs} ${bld}%d${rs} ", ctx->mindepth); + cfprintf(cerr, " ${blu}-mindepth${rs} ${bld}%d${rs}", ctx->mindepth); } if (ctx->maxdepth != INT_MAX) { - cfprintf(cerr, "${blu}-maxdepth${rs} ${bld}%d${rs} ", ctx->maxdepth); + cfprintf(cerr, " ${blu}-maxdepth${rs} ${bld}%d${rs}", ctx->maxdepth); } if (ctx->flags & BFTW_SKIP_MOUNTS) { - cfprintf(cerr, "${blu}-mount${rs} "); + cfprintf(cerr, " ${blu}-mount${rs}"); } if (ctx->status) { - cfprintf(cerr, "${blu}-status${rs} "); + cfprintf(cerr, " ${blu}-status${rs}"); } if (ctx->unique) { - cfprintf(cerr, "${blu}-unique${rs} "); + cfprintf(cerr, " ${blu}-unique${rs}"); } if ((ctx->flags & (BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) == BFTW_PRUNE_MOUNTS) { - cfprintf(cerr, "${blu}-xdev${rs} "); + cfprintf(cerr, " ${blu}-xdev${rs}"); } - if (flag == DEBUG_RATES) { - if (ctx->exclude != &bfs_false) { - cfprintf(cerr, "(${red}-exclude${rs} %pE) ", ctx->exclude); - } - cfprintf(cerr, "%pE", ctx->expr); - } else { - if (ctx->exclude != &bfs_false) { - cfprintf(cerr, "(${red}-exclude${rs} %pe) ", ctx->exclude); - } - cfprintf(cerr, "%pe", ctx->expr); + fputs("\n", stderr); + + if (ctx->exclude != &bfs_false) { + bfs_debug(ctx, flag, "(${red}-exclude${rs}\n"); + dump_expr_multiline(ctx, flag, ctx->exclude, 1, 1); } - fputs("\n", stderr); + dump_expr_multiline(ctx, flag, ctx->expr, 0, 0); } /** -- cgit v1.2.3 From 6961c9a4c2fe8612db222bfd1693e38f7a43a2cd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 29 Nov 2022 16:07:17 -0500 Subject: expr: Remove the synthetic flag Only diagnostics cares about this, and we can just check if the pointers are equal. --- src/diag.c | 14 ++++++++------ src/expr.h | 2 -- src/opt.c | 5 ----- src/parse.c | 9 --------- 4 files changed, 8 insertions(+), 22 deletions(-) diff --git a/src/diag.c b/src/diag.c index 80d988f..c5e139e 100644 --- a/src/diag.c +++ b/src/diag.c @@ -114,12 +114,14 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs bool ret = false; - if (!expr->synthetic) { - size_t i = expr->argv - ctx->argv; - for (size_t j = 0; j < expr->argc; ++j) { - assert(i + j < ctx->argc); - args[i + j] = true; - ret = true; + for (size_t i = 0; i < ctx->argc; ++i) { + if (&ctx->argv[i] == expr->argv) { + for (size_t j = 0; j < expr->argc; ++j) { + assert(i + j < ctx->argc); + args[i + j] = true; + ret = true; + } + break; } } diff --git a/src/expr.h b/src/expr.h index 22f569e..2de8958 100644 --- a/src/expr.h +++ b/src/expr.h @@ -110,8 +110,6 @@ struct bfs_expr { bool always_true; /** Whether this expression always evaluates to false. */ bool always_false; - /** Whether this expression doesn't appear on the command line. */ - bool synthetic; /** Estimated cost. */ float cost; diff --git a/src/opt.c b/src/opt.c index fa2e66c..f7f80d7 100644 --- a/src/opt.c +++ b/src/opt.c @@ -366,10 +366,6 @@ static struct bfs_expr *negate_expr(struct bfs_expr *rhs, char **argv) { return NULL; } - if (argv == &fake_not_arg) { - expr->synthetic = true; - } - expr->lhs = NULL; expr->rhs = rhs; return expr; @@ -404,7 +400,6 @@ static struct bfs_expr *de_morgan(const struct opt_state *state, struct bfs_expr expr->eval_fn = eval_and; expr->argv = &fake_and_arg; } - expr->synthetic = true; expr->lhs = negate_expr(expr->lhs, argv); expr->rhs = negate_expr(expr->rhs, argv); diff --git a/src/parse.c b/src/parse.c index f7c114d..2136bb5 100644 --- a/src/parse.c +++ b/src/parse.c @@ -79,7 +79,6 @@ struct bfs_expr bfs_true = { .argv = &fake_true_arg, .pure = true, .always_true = true, - .synthetic = true, .cost = FAST_COST, .probability = 1.0, }; @@ -90,7 +89,6 @@ struct bfs_expr bfs_false = { .argv = &fake_false_arg, .pure = true, .always_false = true, - .synthetic = true, .cost = FAST_COST, .probability = 0.0, }; @@ -129,7 +127,6 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { expr->pure = false; expr->always_true = false; expr->always_false = false; - expr->synthetic = false; expr->cost = FAST_COST; expr->probability = 0.5; expr->evaluations = 0; @@ -186,10 +183,6 @@ static struct bfs_expr *new_binary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *l expr->rhs = rhs; assert(bfs_expr_has_children(expr)); - if (argv == &fake_and_arg || argv == &fake_or_arg) { - expr->synthetic = true; - } - expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; if (lhs->ephemeral_fds > rhs->ephemeral_fds) { expr->ephemeral_fds = lhs->ephemeral_fds; @@ -2011,7 +2004,6 @@ static struct bfs_expr *parse_nohidden(struct parser_state *state, int arg1, int hidden->probability = 0.01; hidden->pure = true; - hidden->synthetic = true; if (parse_exclude(state, hidden) != 0) { return NULL; @@ -3639,7 +3631,6 @@ static struct bfs_expr *parse_whole_expr(struct parser_state *state) { goto fail; } init_print_expr(state, print); - print->synthetic = true; expr = new_binary_expr(eval_and, expr, print, &fake_and_arg); if (!expr) { -- cgit v1.2.3 From 1d68b43f1554ee82b3ac5772534a5ecbd57855a7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 30 Nov 2022 10:51:45 -0500 Subject: expr: Remove the singleton bfs_{true,false} expressions --- src/color.c | 4 + src/expr.h | 5 - src/opt.c | 68 ++++++++------ src/parse.c | 306 +++++++++++++++++++++++++++++++++--------------------------- 4 files changed, 209 insertions(+), 174 deletions(-) diff --git a/src/color.c b/src/color.c index 98fe9f2..94fcac8 100644 --- a/src/color.c +++ b/src/color.c @@ -877,6 +877,10 @@ static int cbuff(CFILE *cfile, const char *format, ...); /** Dump a parsed expression tree, for debugging. */ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { + if (!expr) { + return dstrcat(&cfile->buffer, "(null)"); + } + if (dstrcat(&cfile->buffer, "(") != 0) { return -1; } diff --git a/src/expr.h b/src/expr.h index 2de8958..e541d8e 100644 --- a/src/expr.h +++ b/src/expr.h @@ -202,11 +202,6 @@ struct bfs_expr { }; }; -/** Singleton true expression instance. */ -extern struct bfs_expr bfs_true; -/** Singleton false expression instance. */ -extern struct bfs_expr bfs_false; - /** * Create a new expression. */ diff --git a/src/opt.c b/src/opt.c index f7f80d7..441c611 100644 --- a/src/opt.c +++ b/src/opt.c @@ -344,6 +344,13 @@ static void opt_warning(const struct opt_state *state, const struct bfs_expr *ex } } +/** Create a constant expression. */ +static struct bfs_expr *opt_const(bool value) { + static bfs_eval_fn *fns[] = {eval_false, eval_true}; + static char *fake_args[] = {"-false", "-true"}; + return bfs_expr_new(fns[value], 1, &fake_args[value]); +} + /** Extract a child expression, freeing the outer expression. */ static struct bfs_expr *extract_child_expr(struct bfs_expr *expr, struct bfs_expr **child) { struct bfs_expr *ret = *child; @@ -457,14 +464,11 @@ static struct bfs_expr *optimize_not_expr(const struct opt_state *state, struct int optlevel = state->ctx->optlevel; if (optlevel >= 1) { - if (rhs == &bfs_true) { - opt_debug(state, 1, "constant propagation: %pe <==> %pe\n", expr, &bfs_false); + if (rhs->eval_fn == eval_true || rhs->eval_fn == eval_false) { + struct bfs_expr *ret = opt_const(rhs->eval_fn == eval_false); + opt_debug(state, 1, "constant propagation: %pe <==> %pe\n", expr, ret); bfs_expr_free(expr); - return &bfs_false; - } else if (rhs == &bfs_false) { - opt_debug(state, 1, "constant propagation: %pe <==> %pe\n", expr, &bfs_true); - bfs_expr_free(expr); - return &bfs_true; + return ret; } else if (rhs->eval_fn == eval_not) { opt_debug(state, 1, "double negation: %pe <==> %pe\n", expr, rhs->rhs); return extract_child_expr(expr, &rhs->rhs); @@ -514,17 +518,17 @@ static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct const struct bfs_ctx *ctx = state->ctx; int optlevel = ctx->optlevel; if (optlevel >= 1) { - if (lhs == &bfs_true) { + if (lhs->eval_fn == eval_true) { opt_debug(state, 1, "conjunction elimination: %pe <==> %pe\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); - } else if (rhs == &bfs_true) { + } else if (rhs->eval_fn == eval_true) { opt_debug(state, 1, "conjunction elimination: %pe <==> %pe\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_false) { opt_debug(state, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); opt_warning(state, expr->rhs, "This expression is unreachable.\n\n"); return extract_child_expr(expr, &expr->lhs); - } else if (lhs->always_true && rhs == &bfs_false) { + } else if (lhs->always_true && rhs->eval_fn == eval_false) { bool debug = opt_debug(state, 1, "strength reduction: %pe <==> ", expr); struct bfs_expr *ret = extract_child_expr(expr, &expr->lhs); ret = negate_expr(ret, &fake_not_arg); @@ -532,7 +536,7 @@ static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct cfprintf(ctx->cerr, "%pe\n", ret); } return ret; - } else if (optlevel >= 2 && lhs->pure && rhs == &bfs_false) { + } else if (optlevel >= 2 && lhs->pure && rhs->eval_fn == eval_false) { opt_debug(state, 2, "purity: %pe <==> %pe\n", expr, rhs); opt_warning(state, expr->lhs, "The result of this expression is ignored.\n\n"); return extract_child_expr(expr, &expr->rhs); @@ -589,13 +593,13 @@ static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct b opt_debug(state, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); opt_warning(state, expr->rhs, "This expression is unreachable.\n\n"); return extract_child_expr(expr, &expr->lhs); - } else if (lhs == &bfs_false) { + } else if (lhs->eval_fn == eval_false) { opt_debug(state, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); - } else if (rhs == &bfs_false) { + } else if (rhs->eval_fn == eval_false) { opt_debug(state, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); - } else if (lhs->always_false && rhs == &bfs_true) { + } else if (lhs->always_false && rhs->eval_fn == eval_true) { bool debug = opt_debug(state, 1, "strength reduction: %pe <==> ", expr); struct bfs_expr *ret = extract_child_expr(expr, &expr->lhs); ret = negate_expr(ret, &fake_not_arg); @@ -603,7 +607,7 @@ static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct b cfprintf(ctx->cerr, "%pe\n", ret); } return ret; - } else if (optlevel >= 2 && lhs->pure && rhs == &bfs_true) { + } else if (optlevel >= 2 && lhs->pure && rhs->eval_fn == eval_true) { opt_debug(state, 2, "purity: %pe <==> %pe\n", expr, rhs); opt_warning(state, expr->lhs, "The result of this expression is ignored.\n\n"); return extract_child_expr(expr, &expr->rhs); @@ -667,11 +671,12 @@ static struct bfs_expr *ignore_result(const struct opt_state *state, struct bfs_ } } - if (optlevel >= 2 && expr->pure && expr != &bfs_false) { - opt_debug(state, 2, "ignored result: %pe --> %pe\n", expr, &bfs_false); + if (optlevel >= 2 && expr->pure && expr->eval_fn != eval_false) { + struct bfs_expr *ret = opt_const(false); + opt_debug(state, 2, "ignored result: %pe --> %pe\n", expr, ret); opt_warning(state, expr, "The result of this expression is ignored.\n\n"); bfs_expr_free(expr); - expr = &bfs_false; + return ret; } } @@ -693,8 +698,8 @@ static struct bfs_expr *optimize_comma_expr(const struct opt_state *state, struc opt_debug(state, 1, "reachability: %pe <==> %pe\n", expr, lhs); opt_warning(state, expr->rhs, "This expression is unreachable.\n\n"); return extract_child_expr(expr, &expr->lhs); - } else if ((lhs->always_true && rhs == &bfs_true) - || (lhs->always_false && rhs == &bfs_false)) { + } else if ((lhs->always_true && rhs->eval_fn == eval_true) + || (lhs->always_false && rhs->eval_fn == eval_false)) { opt_debug(state, 1, "redundancy elimination: %pe <==> %pe\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (optlevel >= 2 && lhs->pure) { @@ -835,11 +840,11 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct state->facts_when_false = state->facts; if (optlevel >= 2 && facts_are_impossible(&state->facts)) { - opt_debug(state, 2, "reachability: %pe --> %pe\n", expr, &bfs_false); + struct bfs_expr *ret = opt_const(false); + opt_debug(state, 2, "reachability: %pe --> %pe\n", expr, ret); opt_warning(state, expr, "This expression is unreachable.\n\n"); bfs_expr_free(expr); - expr = &bfs_false; - goto done; + return ret; } if (!bfs_expr_has_children(expr) && !expr->pure) { @@ -893,7 +898,7 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct } if (!expr) { - goto done; + return NULL; } if (bfs_expr_has_children(expr)) { @@ -918,33 +923,34 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct set_facts_impossible(&state->facts_when_true); } - if (optlevel < 2 || expr == &bfs_true || expr == &bfs_false) { - goto done; + if (optlevel < 2 || expr->eval_fn == eval_true || expr->eval_fn == eval_false) { + return expr; } if (facts_are_impossible(&state->facts_when_true)) { if (expr->pure) { - opt_debug(state, 2, "data flow: %pe --> %pe\n", expr, &bfs_false); + struct bfs_expr *ret = opt_const(false); opt_warning(state, expr, "This expression is always false.\n\n"); + opt_debug(state, 2, "data flow: %pe --> %pe\n", expr, ret); bfs_expr_free(expr); - expr = &bfs_false; + return ret; } else { expr->always_false = true; expr->probability = 0.0; } } else if (facts_are_impossible(&state->facts_when_false)) { if (expr->pure) { - opt_debug(state, 2, "data flow: %pe --> %pe\n", expr, &bfs_true); + struct bfs_expr *ret = opt_const(true); + opt_debug(state, 2, "data flow: %pe --> %pe\n", expr, ret); opt_warning(state, expr, "This expression is always true.\n\n"); bfs_expr_free(expr); - expr = &bfs_true; + return ret; } else { expr->always_true = true; expr->probability = 1.0; } } -done: return expr; } diff --git a/src/parse.c b/src/parse.c index 2136bb5..16132f3 100644 --- a/src/parse.c +++ b/src/parse.c @@ -73,45 +73,6 @@ static char *fake_true_arg = "-true"; #define STAT_COST 1000.0 #define PRINT_COST 20000.0 -struct bfs_expr bfs_true = { - .eval_fn = eval_true, - .argc = 1, - .argv = &fake_true_arg, - .pure = true, - .always_true = true, - .cost = FAST_COST, - .probability = 1.0, -}; - -struct bfs_expr bfs_false = { - .eval_fn = eval_false, - .argc = 1, - .argv = &fake_false_arg, - .pure = true, - .always_false = true, - .cost = FAST_COST, - .probability = 0.0, -}; - -void bfs_expr_free(struct bfs_expr *expr) { - if (!expr || expr == &bfs_true || expr == &bfs_false) { - return; - } - - if (bfs_expr_has_children(expr)) { - bfs_expr_free(expr->rhs); - bfs_expr_free(expr->lhs); - } else if (expr->eval_fn == eval_exec) { - bfs_exec_free(expr->exec); - } else if (expr->eval_fn == eval_fprintf) { - bfs_printf_free(expr->printf); - } else if (expr->eval_fn == eval_regex) { - bfs_regfree(expr->regex); - } - - free(expr); -} - struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { struct bfs_expr *expr = malloc(sizeof(*expr)); if (!expr) { @@ -124,16 +85,29 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { expr->argv = argv; expr->persistent_fds = 0; expr->ephemeral_fds = 0; - expr->pure = false; - expr->always_true = false; - expr->always_false = false; expr->cost = FAST_COST; - expr->probability = 0.5; expr->evaluations = 0; expr->successes = 0; expr->elapsed.tv_sec = 0; expr->elapsed.tv_nsec = 0; + if (eval_fn == eval_true) { + expr->pure = true; + expr->always_true = true; + expr->always_false = false; + expr->probability = 1.0; + } else if (eval_fn == eval_false) { + expr->pure = true; + expr->always_true = false; + expr->always_false = true; + expr->probability = 0.0; + } else { + expr->pure = false; + expr->always_true = false; + expr->always_false = false; + expr->probability = 0.5; + } + // Prevent bfs_expr_free() from freeing uninitialized pointers on error paths if (bfs_expr_has_children(expr)) { expr->lhs = NULL; @@ -149,6 +123,37 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { return expr; } +bool bfs_expr_has_children(const struct bfs_expr *expr) { + return expr->eval_fn == eval_and + || expr->eval_fn == eval_or + || expr->eval_fn == eval_not + || expr->eval_fn == eval_comma; +} + +bool bfs_expr_never_returns(const struct bfs_expr *expr) { + // Expressions that never return are vacuously both always true and always false + return expr->always_true && expr->always_false; +} + +void bfs_expr_free(struct bfs_expr *expr) { + if (!expr) { + return; + } + + if (bfs_expr_has_children(expr)) { + bfs_expr_free(expr->rhs); + bfs_expr_free(expr->lhs); + } else if (expr->eval_fn == eval_exec) { + bfs_exec_free(expr->exec); + } else if (expr->eval_fn == eval_fprintf) { + bfs_printf_free(expr->printf); + } else if (expr->eval_fn == eval_regex) { + bfs_regfree(expr->regex); + } + + free(expr); +} + /** * Create a new unary expression. */ @@ -193,18 +198,6 @@ static struct bfs_expr *new_binary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *l return expr; } -bool bfs_expr_has_children(const struct bfs_expr *expr) { - return expr->eval_fn == eval_and - || expr->eval_fn == eval_or - || expr->eval_fn == eval_not - || expr->eval_fn == eval_comma; -} - -bool bfs_expr_never_returns(const struct bfs_expr *expr) { - // Expressions that never return are vacuously both always true and always false - return expr->always_true && expr->always_false; -} - /** * Set an expression to always return true. */ @@ -762,8 +755,8 @@ static bool looks_like_icmp(const char *str) { * Parse a single flag. */ static struct bfs_expr *parse_flag(struct parser_state *state, size_t argc) { - parser_advance(state, T_FLAG, argc); - return &bfs_true; + char **argv = parser_advance(state, T_FLAG, argc); + return bfs_expr_new(eval_true, argc, argv); } /** @@ -773,12 +766,26 @@ static struct bfs_expr *parse_nullary_flag(struct parser_state *state) { return parse_flag(state, 1); } +/** + * Parse a flag that takes a value. + */ +static struct bfs_expr *parse_unary_flag(struct parser_state *state) { + const char *arg = state->argv[0]; + const char *value = state->argv[1]; + if (!value) { + parse_error(state, "${cyn}%s${rs} needs a value.\n", arg); + return NULL; + } + + return parse_flag(state, 2); +} + /** * Parse a single option. */ static struct bfs_expr *parse_option(struct parser_state *state, size_t argc) { - parser_advance(state, T_OPTION, argc); - return &bfs_true; + char **argv = parser_advance(state, T_OPTION, argc); + return bfs_expr_new(eval_true, argc, argv); } /** @@ -792,6 +799,13 @@ static struct bfs_expr *parse_nullary_option(struct parser_state *state) { * Parse an option that takes a value. */ static struct bfs_expr *parse_unary_option(struct parser_state *state) { + const char *arg = state->argv[0]; + const char *value = state->argv[1]; + if (!value) { + parse_error(state, "${blu}%s${rs} needs a value.\n", arg); + return NULL; + } + return parse_option(state, 2); } @@ -929,19 +943,16 @@ static bool parse_debug_flag(const char *flag, size_t len, const char *expected) static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int arg2) { struct bfs_ctx *ctx = state->ctx; - const char *arg = state->argv[0]; - const char *flags = state->argv[1]; - if (!flags) { - parse_error(state, "${cyn}%s${rs} needs a flag.\n\n", arg); + struct bfs_expr *expr = parse_unary_flag(state); + if (!expr) { + cfprintf(ctx->cerr, "\n"); debug_help(ctx->cerr); return NULL; } - parser_advance(state, T_FLAG, 1); - bool unrecognized = false; - for (const char *flag = flags, *next; flag; flag = next) { + for (const char *flag = expr->argv[1], *next; flag; flag = next) { size_t len = strcspn(flag, ","); if (flag[len]) { next = flag + len + 1; @@ -952,6 +963,7 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar if (parse_debug_flag(flag, len, "help")) { debug_help(ctx->cout); state->just_info = true; + bfs_expr_free(expr); return NULL; } else if (parse_debug_flag(flag, len, "all")) { ctx->debug = DEBUG_ALL; @@ -969,7 +981,7 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar if (DEBUG_ALL & i) { ctx->debug |= i; } else { - if (parse_warning(state, "Unrecognized debug flag ${bld}")) { + if (parse_expr_warning(state, expr, "Unrecognized debug flag ${bld}")) { fwrite(flag, 1, len, stderr); cfprintf(ctx->cerr, "${rs}.\n\n"); unrecognized = true; @@ -982,27 +994,32 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar cfprintf(ctx->cerr, "\n"); } - parser_advance(state, T_FLAG, 1); - return &bfs_true; + return expr; } /** * Parse -On. */ static struct bfs_expr *parse_optlevel(struct parser_state *state, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_flag(state); + if (!expr) { + return NULL; + } + int *optlevel = &state->ctx->optlevel; - if (strcmp(state->argv[0], "-Ofast") == 0) { + if (strcmp(expr->argv[0], "-Ofast") == 0) { *optlevel = 4; - } else if (!parse_int(state, state->argv, state->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { + } else if (!parse_int(state, expr->argv, expr->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { + bfs_expr_free(expr); return NULL; } if (*optlevel > 4) { - parse_warning(state, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", state->argv[0] + 2); + parse_expr_warning(state, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", state->argv[0] + 2); } - return parse_nullary_flag(state); + return expr; } /** @@ -1203,12 +1220,18 @@ static struct bfs_expr *parse_capable(struct parser_state *state, int flag, int * Parse -(no)?color. */ static struct bfs_expr *parse_color(struct parser_state *state, int color, int arg2) { + struct bfs_expr *expr = parse_nullary_option(state); + if (!expr) { + return NULL; + } + struct bfs_ctx *ctx = state->ctx; struct colors *colors = ctx->colors; if (color) { if (!colors) { - parse_error(state, "%s.\n", strerror(ctx->colors_error)); + parse_expr_error(state, expr, "%s.\n", strerror(ctx->colors_error)); + bfs_expr_free(expr); return NULL; } @@ -1221,15 +1244,14 @@ static struct bfs_expr *parse_color(struct parser_state *state, int color, int a ctx->cerr->colors = NULL; } - return parse_nullary_option(state); + return expr; } /** * Parse -{false,true}. */ static struct bfs_expr *parse_const(struct parser_state *state, int value, int arg2) { - parser_advance(state, T_TEST, 1); - return value ? &bfs_true : &bfs_false; + return parse_nullary_test(state, value ? eval_true : eval_false); } /** @@ -1295,20 +1317,20 @@ static struct bfs_expr *parse_depth_n(struct parser_state *state, int arg1, int * Parse -{min,max}depth N. */ static struct bfs_expr *parse_depth_limit(struct parser_state *state, int is_min, int arg2) { - struct bfs_ctx *ctx = state->ctx; - const char *arg = state->argv[0]; - const char *value = state->argv[1]; - if (!value) { - parse_error(state, "${blu}%s${rs} needs a value.\n", arg); + struct bfs_expr *expr = parse_unary_option(state); + if (!expr) { return NULL; } + struct bfs_ctx *ctx = state->ctx; int *depth = is_min ? &ctx->mindepth : &ctx->maxdepth; - if (!parse_int(state, &state->argv[1], value, depth, IF_INT | IF_UNSIGNED)) { + char **arg = &expr->argv[1]; + if (!parse_int(state, arg, *arg, depth, IF_INT | IF_UNSIGNED)) { + bfs_expr_free(expr); return NULL; } - return parse_unary_option(state); + return expr; } /** @@ -1398,33 +1420,30 @@ static struct bfs_expr *parse_exit(struct parser_state *state, int arg1, int arg * Parse -f PATH. */ static struct bfs_expr *parse_f(struct parser_state *state, int arg1, int arg2) { - const char *path = state->argv[1]; - if (!path) { - parse_error(state, "${cyn}-f${rs} requires a path.\n"); + struct bfs_expr *expr = parse_unary_flag(state); + if (!expr) { return NULL; } - if (parse_root(state, path) != 0) { + if (parse_root(state, expr->argv[1]) != 0) { + bfs_expr_free(expr); return NULL; } - parser_advance(state, T_FLAG, 1); - parser_advance(state, T_PATH, 1); - return &bfs_true; + return expr; } /** * Parse -files0-from PATH. */ static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, int arg2) { - const char *arg = state->argv[0]; - const char *from = state->argv[1]; - if (!from) { - parse_error(state, "${blu}%s${rs} requires a path.\n", arg); + struct bfs_expr *expr = parse_unary_option(state); + if (!expr) { return NULL; } - state->files0_arg = parser_advance(state, T_OPTION, 1); + state->files0_arg = expr->argv; + const char *from = expr->argv[1]; FILE *file; if (strcmp(from, "-") == 0) { @@ -1433,27 +1452,24 @@ static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, file = xfopen(from, O_RDONLY | O_CLOEXEC); } if (!file) { - parse_error(state, "%m.\n"); - return NULL; + parse_expr_error(state, expr, "%m.\n"); + goto fail; } - struct bfs_expr *expr = &bfs_true; - while (true) { char *path = xgetdelim(file, '\0'); if (!path) { if (errno) { - parse_error(state, "%m.\n"); - expr = NULL; + goto fail; + } else { + break; } - break; } int ret = parse_root(state, path); free(path); if (ret != 0) { - expr = NULL; - break; + goto fail; } } @@ -1464,8 +1480,14 @@ static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, } state->implicit_root = false; - parser_advance(state, T_OPTION, 1); return expr; + +fail: + if (file && file != stdin) { + fclose(file); + } + bfs_expr_free(expr); + return NULL; } /** @@ -1771,12 +1793,17 @@ static struct bfs_expr *parse_ls(struct parser_state *state, int arg1, int arg2) * Parse -mount. */ static struct bfs_expr *parse_mount(struct parser_state *state, int arg1, int arg2) { - parse_warning(state, "In the future, ${blu}%s${rs} will skip mount points entirely, unlike\n", state->argv[0]); + struct bfs_expr *expr = parse_nullary_option(state); + if (!expr) { + return NULL; + } + + parse_expr_warning(state, expr, "In the future, ${blu}%s${rs} will skip mount points entirely, unlike\n", expr->argv[0]); bfs_warning(state->ctx, "${blu}-xdev${rs}, due to http://austingroupbugs.net/view.php?id=1133.\n\n"); state->ctx->flags |= BFTW_PRUNE_MOUNTS; state->mount_arg = state->argv; - return parse_nullary_option(state); + return expr; } /** @@ -1814,8 +1841,8 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b } if (i % 2 != 0) { parse_expr_warning(state, expr, "Unescaped trailing backslash.\n\n"); - bfs_expr_free(expr); - return &bfs_false; + expr->eval_fn = eval_false; + return expr; } expr->cost = 400.0; @@ -2009,8 +2036,7 @@ static struct bfs_expr *parse_nohidden(struct parser_state *state, int arg1, int return NULL; } - parser_advance(state, T_OPTION, 1); - return &bfs_true; + return parse_nullary_option(state); } /** @@ -2430,16 +2456,14 @@ static struct bfs_expr *parse_regextype(struct parser_state *state, int arg1, in struct bfs_ctx *ctx = state->ctx; CFILE *cfile = ctx->cerr; - const char *arg = state->argv[0]; - const char *type = state->argv[1]; - if (!type) { - parse_error(state, "${blu}%s${rs} needs a value.\n\n", arg); + struct bfs_expr *expr = parse_unary_option(state); + if (!expr) { + cfprintf(cfile, "\n"); goto list_types; } - parser_advance(state, T_OPTION, 1); - // 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, "ed") == 0 || strcmp(type, "sed") == 0) { @@ -2457,12 +2481,11 @@ static struct bfs_expr *parse_regextype(struct parser_state *state, int arg1, in cfile = ctx->cout; goto list_types; } else { - parse_error(state, "Unsupported regex type.\n\n"); + parse_expr_error(state, expr, "Unsupported regex type.\n\n"); goto list_types; } - parser_advance(state, T_OPTION, 1); - return &bfs_true; + return expr; list_types: cfprintf(cfile, "Supported types are:\n\n"); @@ -2474,6 +2497,8 @@ list_types: cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n"); #endif cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n"); + + bfs_expr_free(expr); return NULL; } @@ -2516,15 +2541,13 @@ static struct bfs_expr *parse_search_strategy(struct parser_state *state, int ar struct bfs_ctx *ctx = state->ctx; CFILE *cfile = ctx->cerr; - const char *flag = state->argv[0]; - const char *arg = state->argv[1]; - if (!arg) { - parse_error(state, "${cyn}%s${rs} needs an argument.\n\n", flag); + struct bfs_expr *expr = parse_unary_flag(state); + if (!expr) { + cfprintf(cfile, "\n"); goto list_strategies; } - parser_advance(state, T_FLAG, 1); - + const char *arg = expr->argv[1]; if (strcmp(arg, "bfs") == 0) { ctx->strategy = BFTW_BFS; } else if (strcmp(arg, "dfs") == 0) { @@ -2538,12 +2561,11 @@ static struct bfs_expr *parse_search_strategy(struct parser_state *state, int ar cfile = ctx->cout; goto list_strategies; } else { - parse_error(state, "Unrecognized search strategy.\n\n"); + parse_expr_error(state, expr, "Unrecognized search strategy.\n\n"); goto list_strategies; } - parser_advance(state, T_FLAG, 1); - return &bfs_true; + return expr; list_strategies: cfprintf(cfile, "Supported search strategies:\n\n"); @@ -2551,6 +2573,8 @@ list_strategies: cfprintf(cfile, " ${bld}dfs${rs}: depth-first search\n"); cfprintf(cfile, " ${bld}ids${rs}: iterative deepening search\n"); cfprintf(cfile, " ${bld}eds${rs}: exponential deepening search\n"); + + bfs_expr_free(expr); return NULL; } @@ -3456,7 +3480,7 @@ static struct bfs_expr *parse_factor(struct parser_state *state) { return NULL; } - parser_advance(state, T_OPERATOR, 1); + char **argv = parser_advance(state, T_OPERATOR, 1); state->excluding = true; struct bfs_expr *factor = parse_factor(state); @@ -3470,7 +3494,7 @@ static struct bfs_expr *parse_factor(struct parser_state *state) { return NULL; } - return &bfs_true; + return bfs_expr_new(eval_true, state->argv - argv, argv); } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { char **argv = parser_advance(state, T_OPERATOR, 1); @@ -3612,12 +3636,14 @@ static struct bfs_expr *parse_whole_expr(struct parser_state *state) { return NULL; } - struct bfs_expr *expr = &bfs_true; + struct bfs_expr *expr; if (state->argv[0]) { expr = parse_expr(state); - if (!expr) { - return NULL; - } + } else { + expr = bfs_expr_new(eval_true, 1, &fake_true_arg); + } + if (!expr) { + return NULL; } if (state->argv[0]) { @@ -3804,7 +3830,7 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { fputs("\n", stderr); - if (ctx->exclude != &bfs_false) { + if (ctx->exclude->eval_fn != eval_false) { bfs_debug(ctx, flag, "(${red}-exclude${rs}\n"); dump_expr_multiline(ctx, flag, ctx->exclude, 1, 1); } @@ -3910,7 +3936,11 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { ctx->strategy = BFTW_DFS; } - ctx->exclude = &bfs_false; + ctx->exclude = bfs_expr_new(eval_false, 1, &fake_false_arg); + if (!ctx->exclude) { + goto fail; + } + ctx->expr = parse_whole_expr(&state); if (!ctx->expr) { if (state.just_info) { -- cgit v1.2.3 From 33a6a3027f0b1ec931824a4b2206bf6659df53a4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 30 Nov 2022 13:57:12 -0500 Subject: parse: Pass the right argument when warning about -O9 --- src/parse.c | 2 +- tests/bfs/warn_O9.out | 19 +++++++++++++++++++ tests/bfs/warn_O9.sh | 3 +++ 3 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 tests/bfs/warn_O9.out create mode 100644 tests/bfs/warn_O9.sh diff --git a/src/parse.c b/src/parse.c index 16132f3..5ef71f8 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1016,7 +1016,7 @@ static struct bfs_expr *parse_optlevel(struct parser_state *state, int arg1, int } if (*optlevel > 4) { - parse_expr_warning(state, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", state->argv[0] + 2); + parse_expr_warning(state, expr, "${cyn}-O${bld}%s${rs} is the same as ${cyn}-O${bld}4${rs}.\n\n", expr->argv[0] + 2); } return expr; diff --git a/tests/bfs/warn_O9.out b/tests/bfs/warn_O9.out new file mode 100644 index 0000000..336a6e8 --- /dev/null +++ b/tests/bfs/warn_O9.out @@ -0,0 +1,19 @@ +. +./a +./b +./c +./c/d +./e +./e/f +./g +./g/h +./i +./j +./j/foo +./k +./k/foo +./k/foo/bar +./l +./l/foo +./l/foo/bar +./l/foo/bar/baz diff --git a/tests/bfs/warn_O9.sh b/tests/bfs/warn_O9.sh new file mode 100644 index 0000000..821789f --- /dev/null +++ b/tests/bfs/warn_O9.sh @@ -0,0 +1,3 @@ +# Regression test: don't crash when warning if -O9 is the last argument +cd basic +bfs_diff -warn -O9 -- cgit v1.2.3 From c55e85580df10c5afdc6fc0710e756a456aa8e93 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 30 Nov 2022 14:16:51 -0500 Subject: parse: Fix crash on -xdev -mount --- src/parse.c | 2 +- tests/bfs/warn_xdev_mount.out | 19 +++++++++++++++++++ tests/bfs/warn_xdev_mount.sh | 2 ++ 3 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 tests/bfs/warn_xdev_mount.out create mode 100644 tests/bfs/warn_xdev_mount.sh diff --git a/src/parse.c b/src/parse.c index 5ef71f8..a858a4c 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1802,7 +1802,7 @@ static struct bfs_expr *parse_mount(struct parser_state *state, int arg1, int ar bfs_warning(state->ctx, "${blu}-xdev${rs}, due to http://austingroupbugs.net/view.php?id=1133.\n\n"); state->ctx->flags |= BFTW_PRUNE_MOUNTS; - state->mount_arg = state->argv; + state->mount_arg = expr->argv; return expr; } diff --git a/tests/bfs/warn_xdev_mount.out b/tests/bfs/warn_xdev_mount.out new file mode 100644 index 0000000..a7ccfe4 --- /dev/null +++ b/tests/bfs/warn_xdev_mount.out @@ -0,0 +1,19 @@ +basic +basic/a +basic/b +basic/c +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/bfs/warn_xdev_mount.sh b/tests/bfs/warn_xdev_mount.sh new file mode 100644 index 0000000..5d395f6 --- /dev/null +++ b/tests/bfs/warn_xdev_mount.sh @@ -0,0 +1,2 @@ +# Regression test: don't crash if -mount is the last option +bfs_diff basic -warn -xdev -mount -- cgit v1.2.3 From d6a1b97b0eece42bb18a171ccdfcec7082d5f2d0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 2 Dec 2022 10:27:46 -0500 Subject: tests: Allow wildcard patterns like "posix/*" --- tests/tests.sh | 79 +++++++++++++++++++++++----------------------------------- 1 file changed, 31 insertions(+), 48 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 26c993f..639822a 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -138,16 +138,11 @@ Usage: ${GRN}$0${RST} [${BLU}--bfs${RST}=${MAG}path/to/bfs${RST}] [${BLU}--posix This message ${BLD}TEST${RST} - Select individual test cases to run (e.g. ${BLD}posix/basic${RST}) + Select individual test cases to run (e.g. ${BLD}posix/basic${RST}, ${BLD}"*exec*"${RST}, ...) EOF } -DEFAULT=yes -POSIX= -COMMON= -BSD= -GNU= -ALL= +PATTERNS=() SUDO= STOP= CLEAN=yes @@ -156,9 +151,6 @@ VERBOSE_COMMANDS= VERBOSE_ERRORS= VERBOSE_SKIPPED= VERBOSE_TESTS= -EXPLICIT= - -enabled_tests=() for arg; do case "$arg" in @@ -166,28 +158,16 @@ for arg; do BFS="${arg#*=}" ;; --posix) - DEFAULT= - POSIX=yes + PATTERNS+=("posix/*") ;; --bsd) - DEFAULT= - POSIX=yes - COMMON=yes - BSD=yes + PATTERNS+=("posix/*" "common/*" "bsd/*") ;; --gnu) - DEFAULT= - POSIX=yes - COMMON=yes - GNU=yes + PATTERNS+=("posix/*" "common/*" "gnu/*") ;; --all) - DEFAULT= - POSIX=yes - COMMON=yes - BSD=yes - GNU=yes - ALL=yes + PATTERNS+=("*") ;; --sudo) SUDO=yes @@ -224,27 +204,17 @@ for arg; do usage exit 0 ;; - */*) - EXPLICIT=yes - SUDO=yes - enabled_tests+=("$arg") - ;; - *) + -*) printf "${RED}error:${RST} Unrecognized option '%s'.\n\n" "$arg" >&2 usage >&2 exit 1 ;; + *) + PATTERNS+=("$arg") + ;; esac done -if [ "$DEFAULT" ]; then - POSIX=yes - COMMON=yes - BSD=yes - GNU=yes - ALL=yes -fi - function _realpath() { ( cd "$(dirname -- "$1")" @@ -273,14 +243,27 @@ chown "$(id -u):$(id -g)" "$TMP" cd "$TESTS" -if [ ! "$EXPLICIT" ]; then - [ "$POSIX" ] && enabled_tests+=(posix/*.sh) - [ "$COMMON" ] && enabled_tests+=(common/*.sh) - [ "$BSD" ] && enabled_tests+=(bsd/*.sh) - [ "$GNU" ] && enabled_tests+=(gnu/*.sh) - [ "$ALL" ] && enabled_tests+=(bfs/*.sh) +if (( ${#PATTERNS[@]} == 0 )); then + PATTERNS=("*") +fi - enabled_tests=("${enabled_tests[@]%.sh}") +TEST_CASES=() +for TEST in {posix,common,bsd,gnu,bfs}/*.sh; do + TEST="${TEST%.sh}" + for PATTERN in "${PATTERNS[@]}"; do + if [[ $TEST == $PATTERN ]]; then + TEST_CASES+=("$TEST") + break + fi + done +done + +if (( ${#TEST_CASES[@]} == 0 )); then + printf "${RED}error:${RST} No tests matched" >&2 + printf " ${BLD}%s${RST}" "${PATTERNS[@]}" >&2 + printf ".\n\n" >&2 + usage >&2 + exit 1 fi function clean_scratch() { @@ -671,7 +654,7 @@ passed=0 failed=0 skipped=0 -for TEST in "${enabled_tests[@]}"; do +for TEST in "${TEST_CASES[@]}"; do if [[ -t 1 || "$VERBOSE_TESTS" ]]; then printf "${BOL}${YLW}%s${RST}${EOL}" "$TEST" else -- cgit v1.2.3 From d1e532ed839c1b2be093c88006fcf4cd3d11805d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Dec 2022 14:18:53 -0500 Subject: expr: Rename bfs_expr_has_children() to _is_parent() --- src/color.c | 2 +- src/diag.c | 2 +- src/eval.c | 4 ++-- src/expr.h | 2 +- src/opt.c | 6 +++--- src/parse.c | 12 ++++++------ 6 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/color.c b/src/color.c index 94fcac8..8d32c6c 100644 --- a/src/color.c +++ b/src/color.c @@ -888,7 +888,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { const struct bfs_expr *lhs = NULL; const struct bfs_expr *rhs = NULL; - if (bfs_expr_has_children(expr)) { + if (bfs_expr_is_parent(expr)) { lhs = expr->lhs; rhs = expr->rhs; diff --git a/src/diag.c b/src/diag.c index c5e139e..a0c11f2 100644 --- a/src/diag.c +++ b/src/diag.c @@ -125,7 +125,7 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs } } - if (bfs_expr_has_children(expr)) { + if (bfs_expr_is_parent(expr)) { ret |= highlight_expr_recursive(ctx, expr->lhs, args); ret |= highlight_expr_recursive(ctx, expr->rhs, args); } diff --git a/src/eval.c b/src/eval.c index 4c9d807..e3257ce 100644 --- a/src/eval.c +++ b/src/eval.c @@ -387,7 +387,7 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c } ret = -1; } - } else if (bfs_expr_has_children(expr)) { + } else if (bfs_expr_is_parent(expr)) { if (expr->lhs && eval_exec_finish(expr->lhs, ctx) != 0) { ret = -1; } @@ -1557,7 +1557,7 @@ static bool eval_must_buffer(const struct bfs_expr *expr) { return true; } - if (bfs_expr_has_children(expr)) { + if (bfs_expr_is_parent(expr)) { if (expr->lhs && eval_must_buffer(expr->lhs)) { return true; } diff --git a/src/expr.h b/src/expr.h index e541d8e..a52007a 100644 --- a/src/expr.h +++ b/src/expr.h @@ -210,7 +210,7 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval, size_t argc, char **argv); /** * @return Whether the expression has child expressions. */ -bool bfs_expr_has_children(const struct bfs_expr *expr); +bool bfs_expr_is_parent(const struct bfs_expr *expr); /** * @return Whether expr is known to always quit. diff --git a/src/opt.c b/src/opt.c index 441c611..56d4102 100644 --- a/src/opt.c +++ b/src/opt.c @@ -847,7 +847,7 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct return ret; } - if (!bfs_expr_has_children(expr) && !expr->pure) { + if (!bfs_expr_is_parent(expr) && !expr->pure) { facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); } @@ -901,7 +901,7 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct return NULL; } - if (bfs_expr_has_children(expr)) { + if (bfs_expr_is_parent(expr)) { struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; if (rhs) { @@ -980,7 +980,7 @@ static bool reorder_expr(const struct opt_state *state, struct bfs_expr *expr, f * Whether any subexpression was reordered. */ static bool reorder_expr_recursive(const struct opt_state *state, struct bfs_expr *expr) { - if (!bfs_expr_has_children(expr)) { + if (!bfs_expr_is_parent(expr)) { return false; } diff --git a/src/parse.c b/src/parse.c index a858a4c..320e165 100644 --- a/src/parse.c +++ b/src/parse.c @@ -109,7 +109,7 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { } // Prevent bfs_expr_free() from freeing uninitialized pointers on error paths - if (bfs_expr_has_children(expr)) { + if (bfs_expr_is_parent(expr)) { expr->lhs = NULL; expr->rhs = NULL; } else if (eval_fn == eval_exec) { @@ -123,7 +123,7 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { return expr; } -bool bfs_expr_has_children(const struct bfs_expr *expr) { +bool bfs_expr_is_parent(const struct bfs_expr *expr) { return expr->eval_fn == eval_and || expr->eval_fn == eval_or || expr->eval_fn == eval_not @@ -140,7 +140,7 @@ void bfs_expr_free(struct bfs_expr *expr) { return; } - if (bfs_expr_has_children(expr)) { + if (bfs_expr_is_parent(expr)) { bfs_expr_free(expr->rhs); bfs_expr_free(expr->lhs); } else if (expr->eval_fn == eval_exec) { @@ -166,7 +166,7 @@ static struct bfs_expr *new_unary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *rh expr->lhs = NULL; expr->rhs = rhs; - assert(bfs_expr_has_children(expr)); + assert(bfs_expr_is_parent(expr)); expr->persistent_fds = rhs->persistent_fds; expr->ephemeral_fds = rhs->ephemeral_fds; @@ -186,7 +186,7 @@ static struct bfs_expr *new_binary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *l expr->lhs = lhs; expr->rhs = rhs; - assert(bfs_expr_has_children(expr)); + assert(bfs_expr_is_parent(expr)); expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; if (lhs->ephemeral_fds > rhs->ephemeral_fds) { @@ -3722,7 +3722,7 @@ static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag cfprintf(ctx->cerr, " "); } - if (bfs_expr_has_children(expr)) { + if (bfs_expr_is_parent(expr)) { cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]); if (expr->lhs) { dump_expr_multiline(ctx, flag, expr->lhs, indent + 1, 0); -- cgit v1.2.3 From 2898d6b79d15c984f95977f7385dd4273087c61b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 7 Dec 2022 10:21:19 -0500 Subject: config: New BFS_COUNTOF macro --- src/config.h | 5 +++++ src/fsade.c | 3 +-- tests/trie.c | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/config.h b/src/config.h index 0822e3c..4a49b75 100644 --- a/src/config.h +++ b/src/config.h @@ -155,6 +155,11 @@ # define BFS_FALLTHROUGH ((void)0) #endif +/** + * Get the length of an array. + */ +#define BFS_COUNTOF(array) (sizeof(array) / sizeof(0[array])) + // Lower bound on BFS_FLEX_SIZEOF() #define BFS_FLEX_LB(type, member, length) (offsetof(type, member) + sizeof(((type *)NULL)->member[0]) * (length)) diff --git a/src/fsade.c b/src/fsade.c index a609b97..45969d1 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -206,7 +206,6 @@ int bfs_check_acl(const struct BFTW *ftwbuf) { ACL_TYPE_NFS4, #endif }; - static const size_t n_acl_types = sizeof(acl_types)/sizeof(acl_types[0]); if (ftwbuf->type == BFS_LNK) { return 0; @@ -215,7 +214,7 @@ int bfs_check_acl(const struct BFTW *ftwbuf) { const char *path = fake_at(ftwbuf); int ret = -1, error = 0; - for (size_t i = 0; i < n_acl_types && ret <= 0; ++i) { + for (size_t i = 0; i < BFS_COUNTOF(acl_types) && ret <= 0; ++i) { acl_type_t type = acl_types[i]; if (type == ACL_TYPE_DEFAULT && ftwbuf->type != BFS_DIR) { diff --git a/tests/trie.c b/tests/trie.c index 6bc7549..88e92da 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -17,6 +17,7 @@ #undef NDEBUG #include "../src/trie.h" +#include "../src/config.h" #include #include #include @@ -50,7 +51,7 @@ const char *keys[] = { ">>>", }; -const size_t nkeys = sizeof(keys) / sizeof(keys[0]); +const size_t nkeys = BFS_COUNTOF(keys); int main(void) { struct trie trie; -- cgit v1.2.3 From 87d0f8988c9452428d5a250644ac4cb2acaf43bb Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 7 Dec 2022 16:49:03 -0500 Subject: trie: Use static_assert() instead of #error --- src/trie.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/trie.c b/src/trie.c index caa38a7..08a99b5 100644 --- a/src/trie.c +++ b/src/trie.c @@ -103,9 +103,7 @@ #include #include -#if CHAR_BIT != 8 -# error "This trie implementation assumes 8-bit bytes." -#endif +static_assert(CHAR_BIT == 8, "This trie implementation assumes 8-bit bytes."); #if BFS_TARGET_CLONES && (__i386__ || __x86_64__) # define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) -- cgit v1.2.3 From c4d2f79e74a684f40f0c3d2b670133494395c9ee Mon Sep 17 00:00:00 2001 From: LGTM Migrator Date: Thu, 8 Dec 2022 17:04:59 +0000 Subject: Add CodeQL workflow for GitHub code scanning --- .github/workflows/codeql.yml | 55 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 .github/workflows/codeql.yml diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 0000000..e1a99f2 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,55 @@ +name: CodeQL + +on: + push: + branches: + - main + pull_request: + branches: + - main + schedule: + - cron: "10 14 * * 2" + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Install dependencies + run: | + sudo apt-get update -y + sudo apt-get install -y \ + gcc \ + acl \ + libacl1-dev \ + attr \ + libattr1-dev \ + libcap2-bin \ + libcap-dev \ + libonig-dev + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: cpp + queries: +security-and-quality + + - name: Generate coverage + run: | + make -j$(nproc) all + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:cpp" -- cgit v1.2.3 From ededa3ed1cf0d02fcdb9a42d44d46a84fec1c58e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 8 Dec 2022 16:43:29 -0500 Subject: opt: Pass a va_list to bfs_vwarning(), not bfs_warning() Luckily none of the callers actually pass any arguments. --- src/opt.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/opt.c b/src/opt.c index 56d4102..3250331 100644 --- a/src/opt.c +++ b/src/opt.c @@ -339,7 +339,7 @@ static void opt_warning(const struct opt_state *state, const struct bfs_expr *ex if (bfs_expr_warning(state->ctx, expr)) { va_list args; va_start(args, format); - bfs_warning(state->ctx, format, args); + bfs_vwarning(state->ctx, format, args); va_end(args); } } -- cgit v1.2.3 From 0cc598f1628167599131756e909630dc36d33610 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 8 Dec 2022 16:45:06 -0500 Subject: Add BFS_FORMATTER() to vprintf()-type functions too --- src/color.c | 2 ++ src/color.h | 1 + src/diag.h | 3 +++ src/dstring.h | 2 ++ 4 files changed, 8 insertions(+) diff --git a/src/color.c b/src/color.c index 8d32c6c..cc37e96 100644 --- a/src/color.c +++ b/src/color.c @@ -17,6 +17,7 @@ #include "color.h" #include "bfstd.h" #include "bftw.h" +#include "config.h" #include "dir.h" #include "dstring.h" #include "expr.h" @@ -944,6 +945,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { return 0; } +BFS_FORMATTER(2, 0) static int cvbuff(CFILE *cfile, const char *format, va_list args) { const struct colors *colors = cfile->colors; int error = errno; diff --git a/src/color.h b/src/color.h index 1b0cadb..5b350cc 100644 --- a/src/color.h +++ b/src/color.h @@ -115,6 +115,7 @@ int cfprintf(CFILE *cfile, const char *format, ...); /** * cfprintf() variant that takes a va_list. */ +BFS_FORMATTER(2, 0) int cvfprintf(CFILE *cfile, const char *format, va_list args); #endif // BFS_COLOR_H diff --git a/src/diag.h b/src/diag.h index 56ad39b..8d0b19f 100644 --- a/src/diag.h +++ b/src/diag.h @@ -58,16 +58,19 @@ bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *for /** * bfs_error() variant that takes a va_list. */ +BFS_FORMATTER(2, 0) void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_warning() variant that takes a va_list. */ +BFS_FORMATTER(2, 0) bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_debug() variant that takes a va_list. */ +BFS_FORMATTER(3, 0) bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args); /** diff --git a/src/dstring.h b/src/dstring.h index 51f1b2f..df20a04 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -152,6 +152,7 @@ char *dstrprintf(const char *format, ...); * @return * The created string, or NULL on failure. */ +BFS_FORMATTER(1, 0) char *dstrvprintf(const char *format, va_list args); /** @@ -181,6 +182,7 @@ int dstrcatf(char **str, const char *format, ...); * @return * 0 on success, -1 on failure. */ +BFS_FORMATTER(2, 0) int dstrvcatf(char **str, const char *format, va_list args); /** -- cgit v1.2.3 From d0e8026d650bb4318ea8608865d5f5a011366dcc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 9 Dec 2022 11:59:26 -0500 Subject: Turn on more aggressive format string warnings --- Makefile | 5 +++-- src/config.h | 15 ++++++++++++++ src/eval.c | 8 +++++--- src/printf.c | 67 +++++++++++++++++++++++++++++++++++++----------------------- 4 files changed, 65 insertions(+), 30 deletions(-) diff --git a/Makefile b/Makefile index a1144c9..afd3c77 100644 --- a/Makefile +++ b/Makefile @@ -46,11 +46,12 @@ OBJ := $(BUILDDIR)/obj DEFAULT_CFLAGS := \ -g \ -Wall \ + -Wformat=2 \ + -Wimplicit-fallthrough \ -Wmissing-declarations \ -Wshadow \ -Wsign-compare \ - -Wstrict-prototypes \ - -Wimplicit-fallthrough + -Wstrict-prototypes CFLAGS ?= $(DEFAULT_CFLAGS) LDFLAGS ?= diff --git a/src/config.h b/src/config.h index 4a49b75..229f593 100644 --- a/src/config.h +++ b/src/config.h @@ -198,4 +198,19 @@ # define BFS_TARGET_CLONES true #endif +/** + * Ignore a particular GCC warning for a region of code. + */ +#if __GNUC__ +# define BFS_PRAGMA_STRINGIFY(...) _Pragma(#__VA_ARGS__) +# define BFS_SUPPRESS(warning) \ + _Pragma("GCC diagnostic push"); \ + BFS_PRAGMA_STRINGIFY(GCC diagnostic ignored warning) +# define BFS_UNSUPPRESS() \ + _Pragma("GCC diagnostic pop") +#else +# define BFS_SUPPRESS(warning) +# define BFS_UNSUPPRESS() +#endif + #endif // BFS_CONFIG_H diff --git a/src/eval.c b/src/eval.c index e3257ce..89591b2 100644 --- a/src/eval.c +++ b/src/eval.c @@ -716,11 +716,13 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { goto error; } char time_str[256]; - const char *time_format = "%b %e %H:%M"; + size_t time_ret; if (time <= six_months_ago || time >= tomorrow) { - time_format = "%b %e %Y"; + time_ret = strftime(time_str, sizeof(time_str), "%b %e %Y", &tm); + } else { + time_ret = strftime(time_str, sizeof(time_str), "%b %e %H:%M", &tm); } - if (!strftime(time_str, sizeof(time_str), time_format, &tm)) { + if (time_ret == 0) { errno = EOVERFLOW; goto error; } diff --git a/src/printf.c b/src/printf.c index 6a7b9a9..5af8362 100644 --- a/src/printf.c +++ b/src/printf.c @@ -91,6 +91,21 @@ static bool should_color(CFILE *cfile, const struct bfs_printf *directive) { assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ (void)ret +/** + * Common entry point for fprintf() with a dynamic format string. + */ +static int dyn_fprintf(FILE *file, const struct bfs_printf *directive, ...) { + va_list args; + va_start(args, directive); + + BFS_SUPPRESS("-Wformat-nonliteral"); + int ret = vfprintf(file, directive->str, args); + BFS_UNSUPPRESS(); + + va_end(args); + return ret; +} + /** %a, %c, %t: ctime() */ static int bfs_printf_ctime(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { // Not using ctime() itself because GNU find adds nanoseconds @@ -122,7 +137,7 @@ static int bfs_printf_ctime(CFILE *cfile, const struct bfs_printf *directive, co (long)ts->tv_nsec, 1900 + tm.tm_year); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %A, %B/%W, %C, %T: strftime() */ @@ -183,14 +198,16 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, // POSIX strftime() features default: format[1] = directive->c; + BFS_SUPPRESS("-Wformat-nonliteral"); ret = strftime(buf, sizeof(buf), format, &tm); + BFS_UNSUPPRESS(); break; } assert(ret >= 0 && (size_t)ret < sizeof(buf)); (void)ret; - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %b: blocks */ @@ -202,12 +219,12 @@ static int bfs_printf_b(CFILE *cfile, const struct bfs_printf *directive, const uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 511)/512; BFS_PRINTF_BUF(buf, "%ju", blocks); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %d: depth */ static int bfs_printf_d(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { - return fprintf(cfile->file, directive->str, (intmax_t)ftwbuf->depth); + return dyn_fprintf(cfile->file, directive, (intmax_t)ftwbuf->depth); } /** %D: device */ @@ -218,7 +235,7 @@ static int bfs_printf_D(CFILE *cfile, const struct bfs_printf *directive, const } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->dev); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %f: file name */ @@ -226,7 +243,7 @@ static int bfs_printf_f(CFILE *cfile, const struct bfs_printf *directive, const if (should_color(cfile, directive)) { return cfprintf(cfile, "%pF", ftwbuf); } else { - return fprintf(cfile->file, directive->str, ftwbuf->path + ftwbuf->nameoff); + return dyn_fprintf(cfile->file, directive, ftwbuf->path + ftwbuf->nameoff); } } @@ -238,7 +255,7 @@ static int bfs_printf_F(CFILE *cfile, const struct bfs_printf *directive, const } const char *type = bfs_fstype(directive->ptr, statbuf); - return fprintf(cfile->file, directive->str, type); + return dyn_fprintf(cfile->file, directive, type); } /** %G: gid */ @@ -249,7 +266,7 @@ static int bfs_printf_G(CFILE *cfile, const struct bfs_printf *directive, const } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->gid); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %g: group name */ @@ -265,7 +282,7 @@ static int bfs_printf_g(CFILE *cfile, const struct bfs_printf *directive, const return bfs_printf_G(cfile, directive, ftwbuf); } - return fprintf(cfile->file, directive->str, grp->gr_name); + return dyn_fprintf(cfile->file, directive, grp->gr_name); } /** %h: leading directories */ @@ -294,7 +311,7 @@ static int bfs_printf_h(CFILE *cfile, const struct bfs_printf *directive, const if (should_color(cfile, directive)) { ret = cfprintf(cfile, "${di}%s${rs}", buf); } else { - ret = fprintf(cfile->file, directive->str, buf); + ret = dyn_fprintf(cfile->file, directive, buf); } free(copy); @@ -310,7 +327,7 @@ static int bfs_printf_H(CFILE *cfile, const struct bfs_printf *directive, const return cfprintf(cfile, "${di}%s${rs}", ftwbuf->root); } } else { - return fprintf(cfile->file, directive->str, ftwbuf->root); + return dyn_fprintf(cfile->file, directive, ftwbuf->root); } } @@ -322,7 +339,7 @@ static int bfs_printf_i(CFILE *cfile, const struct bfs_printf *directive, const } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->ino); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %k: 1K blocks */ @@ -334,7 +351,7 @@ static int bfs_printf_k(CFILE *cfile, const struct bfs_printf *directive, const uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 1023)/1024; BFS_PRINTF_BUF(buf, "%ju", blocks); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %l: link target */ @@ -356,7 +373,7 @@ static int bfs_printf_l(CFILE *cfile, const struct bfs_printf *directive, const } } - int ret = fprintf(cfile->file, directive->str, target); + int ret = dyn_fprintf(cfile->file, directive, target); free(buf); return ret; } @@ -368,7 +385,7 @@ static int bfs_printf_m(CFILE *cfile, const struct bfs_printf *directive, const return -1; } - return fprintf(cfile->file, directive->str, (unsigned int)(statbuf->mode & 07777)); + return dyn_fprintf(cfile->file, directive, (unsigned int)(statbuf->mode & 07777)); } /** %M: symbolic mode */ @@ -380,7 +397,7 @@ static int bfs_printf_M(CFILE *cfile, const struct bfs_printf *directive, const char buf[11]; xstrmode(statbuf->mode, buf); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %n: link count */ @@ -391,7 +408,7 @@ static int bfs_printf_n(CFILE *cfile, const struct bfs_printf *directive, const } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->nlink); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %p: full path */ @@ -399,7 +416,7 @@ static int bfs_printf_p(CFILE *cfile, const struct bfs_printf *directive, const if (should_color(cfile, directive)) { return cfprintf(cfile, "%pP", ftwbuf); } else { - return fprintf(cfile->file, directive->str, ftwbuf->path); + return dyn_fprintf(cfile->file, directive, ftwbuf->path); } } @@ -420,7 +437,7 @@ static int bfs_printf_P(CFILE *cfile, const struct bfs_printf *directive, const copybuf.nameoff -= offset; return cfprintf(cfile, "%pP", ©buf); } else { - return fprintf(cfile->file, directive->str, ftwbuf->path + offset); + return dyn_fprintf(cfile->file, directive, ftwbuf->path + offset); } } @@ -432,7 +449,7 @@ static int bfs_printf_s(CFILE *cfile, const struct bfs_printf *directive, const } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->size); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %S: sparseness */ @@ -448,7 +465,7 @@ static int bfs_printf_S(CFILE *cfile, const struct bfs_printf *directive, const } else { sparsity = (double)BFS_STAT_BLKSIZE*statbuf->blocks/statbuf->size; } - return fprintf(cfile->file, directive->str, sparsity); + return dyn_fprintf(cfile->file, directive, sparsity); } /** %U: uid */ @@ -459,7 +476,7 @@ static int bfs_printf_U(CFILE *cfile, const struct bfs_printf *directive, const } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->uid); - return fprintf(cfile->file, directive->str, buf); + return dyn_fprintf(cfile->file, directive, buf); } /** %u: user name */ @@ -475,7 +492,7 @@ static int bfs_printf_u(CFILE *cfile, const struct bfs_printf *directive, const return bfs_printf_U(cfile, directive, ftwbuf); } - return fprintf(cfile->file, directive->str, pwd->pw_name); + return dyn_fprintf(cfile->file, directive, pwd->pw_name); } static const char *bfs_printf_type(enum bfs_type type) { @@ -504,7 +521,7 @@ static const char *bfs_printf_type(enum bfs_type type) { /** %y: type */ static int bfs_printf_y(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { const char *type = bfs_printf_type(ftwbuf->type); - return fprintf(cfile->file, directive->str, type); + return dyn_fprintf(cfile->file, directive, type); } /** %Y: target type */ @@ -536,7 +553,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_printf *directive, const } } - int ret = fprintf(cfile->file, directive->str, type); + int ret = dyn_fprintf(cfile->file, directive, type); if (error != 0) { ret = -1; errno = error; -- cgit v1.2.3 From ee5d250d27bb1def69077a784bfeb2b444735448 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 9 Dec 2022 12:04:50 -0500 Subject: ci/codeql: Rename the build step --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index e1a99f2..88e9f3f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -45,7 +45,7 @@ jobs: languages: cpp queries: +security-and-quality - - name: Generate coverage + - name: Build run: | make -j$(nproc) all -- cgit v1.2.3 From e477e12ade9544c31779f007faee169aae22e145 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 9 Dec 2022 12:51:00 -0500 Subject: config: New BFS_UNINIT() macro for intentionally uninitialized variables --- src/config.h | 9 +++++++++ src/parse.c | 6 +++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/config.h b/src/config.h index 229f593..810f913 100644 --- a/src/config.h +++ b/src/config.h @@ -213,4 +213,13 @@ # define BFS_UNSUPPRESS() #endif +/** + * Initialize a variable, unless sanitizers would detect uninitialized uses. + */ +#if __has_feature(memory_sanitizer) +# define BFS_UNINIT(var, value) var = var +#else +# define BFS_UNINIT(var, value) var = value +#endif + #endif // BFS_CONFIG_H diff --git a/src/parse.c b/src/parse.c index 320e165..90e7e3b 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2121,9 +2121,9 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct MODE_EQUALS, } op; - mode_t who; - mode_t file_change; - mode_t dir_change; + mode_t BFS_UNINIT(who, 0); + mode_t BFS_UNINIT(file_change, 0); + mode_t BFS_UNINIT(dir_change, 0); const char *i = mode; while (true) { -- cgit v1.2.3 From 7f303bec64c7a300f0f38e9938a8327cac9591e0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 9 Dec 2022 12:58:08 -0500 Subject: parse: Mark another variable with BFS_UNINIT() --- src/parse.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/parse.c b/src/parse.c index 90e7e3b..ad32714 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2119,7 +2119,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct MODE_PLUS, MODE_MINUS, MODE_EQUALS, - } op; + } BFS_UNINIT(op, MODE_EQUALS); mode_t BFS_UNINIT(who, 0); mode_t BFS_UNINIT(file_change, 0); -- cgit v1.2.3 From f3ba6a3e40d72e00d13e943d9338b60b4bee2e40 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 13 Dec 2022 15:44:17 -0500 Subject: tests: Add a test for readdir() errors --- tests/posix/readdir_error.sh | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 tests/posix/readdir_error.sh diff --git a/tests/posix/readdir_error.sh b/tests/posix/readdir_error.sh new file mode 100644 index 0000000..cca71f0 --- /dev/null +++ b/tests/posix/readdir_error.sh @@ -0,0 +1,21 @@ +clean_scratch +mkfifo scratch/{pid,hup} + +( + # Create a zombie process + echo >/dev/null & + # Write the PID to scratch/pid + echo $! >scratch/pid + # Don't wait on the processes + exec cat scratch/hup >/dev/null +) & + +# Kill cat on exit +trap "echo >scratch/hup" EXIT + +# Read the zombie PID +read -r pid /dev/null -- cgit v1.2.3 From f21a9340cd0c615ead1954eb6b93845a4f8c0897 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Dec 2022 12:19:31 -0500 Subject: tests/posix/readdir_error: Fix flakiness --- tests/posix/readdir_error.sh | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/tests/posix/readdir_error.sh b/tests/posix/readdir_error.sh index cca71f0..ce06723 100644 --- a/tests/posix/readdir_error.sh +++ b/tests/posix/readdir_error.sh @@ -1,21 +1,37 @@ +skip_unless test "$UNAME" = "Linux" + clean_scratch -mkfifo scratch/{pid,hup} +mkfifo scratch/{fever,pid,wait,running} ( # Create a zombie process - echo >/dev/null & + cat scratch/fever >/dev/null & # Write the PID to scratch/pid echo $! >scratch/pid - # Don't wait on the processes - exec cat scratch/hup >/dev/null + # Don't wait on the zombie process + exec cat scratch/wait scratch/fever >scratch/running ) & -# Kill cat on exit -trap "echo >scratch/hup" EXIT +# Kill the parent cat on exit +trap "kill -9 %1" EXIT -# Read the zombie PID +# Read the child PID read -r pid scratch/wait & +read -r _ /dev/null -- cgit v1.2.3 From 0074d5b0c064dbf6cfc7231fc5b74659d81b120e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Dec 2022 12:20:18 -0500 Subject: tests: Fix crash when stderr is redirected bash uses fileno(stderr) to keep track of $COLUMNS. With stderr redirected, $COLUMNS will be unset, leading to $ ./tests/tests.sh 2> >(cat) ./tests/tests.sh: line 635: COLUMNS: unbound variable Fix it by using $(tput cols) if $COLUMNS is unset, which is almost POSIX. Link: https://www.austingroupbugs.net/view.php?id=1053 --- tests/tests.sh | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/tests.sh b/tests/tests.sh index 639822a..3fdf49b 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -630,9 +630,15 @@ BOL='\n' EOL='\n' function update_eol() { + # Bash gets $COLUMNS from stderr, so if it's redirected use tput instead + local cols="${COLUMNS-}" + if [ -z "$cols" ]; then + cols=$(tput cols) + fi + # Put the cursor at the last column, then write a space so the next # character will wrap - EOL="\\033[${COLUMNS}G " + EOL="\\033[${cols}G " } if [ "$VERBOSE_TESTS" ]; then -- cgit v1.2.3 From 681f14f7cdff2d8fae235b8fd09745cfcb93e666 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 15 Dec 2022 15:35:07 -0500 Subject: README: Fix the CI badge Link: https://github.com/badges/shields/issues/8671 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 513b8a2..b7707bb 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ Version License -CI Status +CI Status Code coverage ***Breadth-first search for your files.*** -- cgit v1.2.3 From e01042b84abdfa224d47e6d11eb9798ce4c7d2f8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Dec 2022 14:17:07 -0500 Subject: tests: Replace skip_unless test with test || skip --- tests/bfs/L_capable.sh | 6 +++--- tests/bfs/capable.sh | 6 +++--- tests/bfs/exec_flush_fprint_fail.sh | 2 +- tests/bfs/fprint_error_stderr.sh | 2 +- tests/bfs/fprint_error_stdout.sh | 2 +- tests/bfs/stderr_fails_loudly.sh | 2 +- tests/bfs/stderr_fails_silently.sh | 2 +- tests/bsd/L_acl.sh | 4 ++-- tests/bsd/L_xattr.sh | 4 ++-- tests/bsd/L_xattrname.sh | 4 ++-- tests/bsd/acl.sh | 4 ++-- tests/bsd/flags.sh | 4 ++-- tests/bsd/xattr.sh | 4 ++-- tests/bsd/xattrname.sh | 4 ++-- tests/common/L_ilname.sh | 2 +- tests/common/L_mount.sh | 4 ++-- tests/common/ilname.sh | 2 +- tests/common/iname.sh | 2 +- tests/common/inum_bind_mount.sh | 4 ++-- tests/common/inum_mount.sh | 4 ++-- tests/common/ipath.sh | 2 +- tests/common/mount.sh | 4 ++-- tests/gnu/exec_flush_fail.sh | 2 +- tests/gnu/exec_plus_flush_fail.sh | 2 +- tests/gnu/fprint_error.sh | 2 +- tests/gnu/fprint_noerror.sh | 2 +- tests/gnu/fstype.sh | 3 +-- tests/gnu/gid_plus.sh | 2 +- tests/gnu/gid_plus_plus.sh | 2 +- tests/gnu/inum_automount.sh | 6 +++--- tests/gnu/iwholename.sh | 2 +- tests/gnu/print_error.sh | 2 +- tests/gnu/regex_invalid_utf8.sh | 6 +++--- tests/gnu/regextype_emacs.sh | 2 +- tests/gnu/regextype_grep.sh | 2 +- tests/gnu/uid_plus.sh | 2 +- tests/gnu/uid_plus_plus.sh | 2 +- tests/gnu/xtype_bind_mount.sh | 4 ++-- tests/posix/L_xdev.sh | 4 ++-- tests/posix/name_bracket.sh | 2 +- tests/posix/readdir_error.sh | 4 ++-- tests/posix/type_bind_mount.sh | 4 ++-- tests/posix/xdev.sh | 4 ++-- tests/tests.sh | 10 ---------- 44 files changed, 67 insertions(+), 78 deletions(-) diff --git a/tests/bfs/L_capable.sh b/tests/bfs/L_capable.sh index 533ac2f..a349677 100644 --- a/tests/bfs/L_capable.sh +++ b/tests/bfs/L_capable.sh @@ -1,9 +1,9 @@ -skip_unless test "$SUDO" -skip_unless test "$UNAME" = "Linux" +test "$SUDO" || skip +test "$UNAME" = "Linux" || skip clean_scratch -skip_unless invoke_bfs scratch -quit -capable +invoke_bfs scratch -quit -capable || skip "$XTOUCH" scratch/{normal,capable} sudo setcap all+ep scratch/capable diff --git a/tests/bfs/capable.sh b/tests/bfs/capable.sh index 256b9bc..8b60ea6 100644 --- a/tests/bfs/capable.sh +++ b/tests/bfs/capable.sh @@ -1,9 +1,9 @@ -skip_unless test "$SUDO" -skip_unless test "$UNAME" = "Linux" +test "$SUDO" || skip +test "$UNAME" = "Linux" || skip clean_scratch -skip_unless invoke_bfs scratch -quit -capable +invoke_bfs scratch -quit -capable || skip "$XTOUCH" scratch/{normal,capable} sudo setcap all+ep scratch/capable diff --git a/tests/bfs/exec_flush_fprint_fail.sh b/tests/bfs/exec_flush_fprint_fail.sh index 5da944a..b58624a 100644 --- a/tests/bfs/exec_flush_fprint_fail.sh +++ b/tests/bfs/exec_flush_fprint_fail.sh @@ -1,2 +1,2 @@ -skip_unless test -e /dev/full +test -e /dev/full || skip fail invoke_bfs basic/a -fprint /dev/full -exec true \; diff --git a/tests/bfs/fprint_error_stderr.sh b/tests/bfs/fprint_error_stderr.sh index 427808f..e51d026 100644 --- a/tests/bfs/fprint_error_stderr.sh +++ b/tests/bfs/fprint_error_stderr.sh @@ -1,2 +1,2 @@ -skip_unless test -e /dev/full +test -e /dev/full || skip fail invoke_bfs basic -maxdepth 0 -fprint /dev/full 2>/dev/full diff --git a/tests/bfs/fprint_error_stdout.sh b/tests/bfs/fprint_error_stdout.sh index fbdc1d0..6aa4b11 100644 --- a/tests/bfs/fprint_error_stdout.sh +++ b/tests/bfs/fprint_error_stdout.sh @@ -1,2 +1,2 @@ -skip_unless test -e /dev/full +test -e /dev/full || skip fail invoke_bfs basic -maxdepth 0 -fprint /dev/full >/dev/full diff --git a/tests/bfs/stderr_fails_loudly.sh b/tests/bfs/stderr_fails_loudly.sh index d8b3861..c423d9e 100644 --- a/tests/bfs/stderr_fails_loudly.sh +++ b/tests/bfs/stderr_fails_loudly.sh @@ -1,2 +1,2 @@ -skip_unless test -e /dev/full +test -e /dev/full || skip fail invoke_bfs -D all basic -false -fprint /dev/full 2>/dev/full diff --git a/tests/bfs/stderr_fails_silently.sh b/tests/bfs/stderr_fails_silently.sh index 731cb02..a37393d 100644 --- a/tests/bfs/stderr_fails_silently.sh +++ b/tests/bfs/stderr_fails_silently.sh @@ -1,2 +1,2 @@ -skip_unless test -e /dev/full +test -e /dev/full || skip bfs_diff -D all basic 2>/dev/full diff --git a/tests/bsd/L_acl.sh b/tests/bsd/L_acl.sh index cf573e4..db97013 100644 --- a/tests/bsd/L_acl.sh +++ b/tests/bsd/L_acl.sh @@ -1,9 +1,9 @@ clean_scratch -skip_unless invoke_bfs scratch -quit -acl +invoke_bfs scratch -quit -acl || skip "$XTOUCH" scratch/{normal,acl} -skip_unless set_acl scratch/acl +set_acl scratch/acl || skip ln -s acl scratch/link bfs_diff -L scratch -acl diff --git a/tests/bsd/L_xattr.sh b/tests/bsd/L_xattr.sh index 7c27e0d..1f61c78 100644 --- a/tests/bsd/L_xattr.sh +++ b/tests/bsd/L_xattr.sh @@ -1,3 +1,3 @@ -skip_unless invoke_bfs scratch -quit -xattr -skip_unless make_xattrs +invoke_bfs scratch -quit -xattr || skip +make_xattrs || skip bfs_diff -L scratch -xattr diff --git a/tests/bsd/L_xattrname.sh b/tests/bsd/L_xattrname.sh index 39d6a77..3b2006b 100644 --- a/tests/bsd/L_xattrname.sh +++ b/tests/bsd/L_xattrname.sh @@ -1,5 +1,5 @@ -skip_unless invoke_bfs scratch -quit -xattr -skip_unless make_xattrs +invoke_bfs scratch -quit -xattr || skip +make_xattrs || skip case "$UNAME" in Darwin|FreeBSD) diff --git a/tests/bsd/acl.sh b/tests/bsd/acl.sh index 1665684..c044398 100644 --- a/tests/bsd/acl.sh +++ b/tests/bsd/acl.sh @@ -1,9 +1,9 @@ clean_scratch -skip_unless invoke_bfs scratch -quit -acl +invoke_bfs scratch -quit -acl || skip "$XTOUCH" scratch/{normal,acl} -skip_unless set_acl scratch/acl +set_acl scratch/acl || skip ln -s acl scratch/link bfs_diff scratch -acl diff --git a/tests/bsd/flags.sh b/tests/bsd/flags.sh index ffb1cc2..949a7d3 100644 --- a/tests/bsd/flags.sh +++ b/tests/bsd/flags.sh @@ -1,8 +1,8 @@ -skip_unless invoke_bfs scratch -quit -flags offline +invoke_bfs scratch -quit -flags offline || skip clean_scratch "$XTOUCH" scratch/{foo,bar} -skip_unless chflags offline scratch/bar +chflags offline scratch/bar || skip bfs_diff scratch -flags -offline,nohidden diff --git a/tests/bsd/xattr.sh b/tests/bsd/xattr.sh index 727c220..4a4658c 100644 --- a/tests/bsd/xattr.sh +++ b/tests/bsd/xattr.sh @@ -1,3 +1,3 @@ -skip_unless invoke_bfs scratch -quit -xattr -skip_unless make_xattrs +invoke_bfs scratch -quit -xattr || skip +make_xattrs || skip bfs_diff scratch -xattr diff --git a/tests/bsd/xattrname.sh b/tests/bsd/xattrname.sh index 6a0fe7e..655bd74 100644 --- a/tests/bsd/xattrname.sh +++ b/tests/bsd/xattrname.sh @@ -1,5 +1,5 @@ -skip_unless invoke_bfs scratch -quit -xattr -skip_unless make_xattrs +invoke_bfs scratch -quit -xattr || skip +make_xattrs || skip case "$UNAME" in Darwin|FreeBSD) diff --git a/tests/common/L_ilname.sh b/tests/common/L_ilname.sh index cfb15a8..e0495ed 100644 --- a/tests/common/L_ilname.sh +++ b/tests/common/L_ilname.sh @@ -1,2 +1,2 @@ -skip_unless invoke_bfs -quit -ilname PATTERN +invoke_bfs -quit -ilname PATTERN || skip bfs_diff -L links -ilname '[AQ]' diff --git a/tests/common/L_mount.sh b/tests/common/L_mount.sh index dad7e00..5b56762 100644 --- a/tests/common/L_mount.sh +++ b/tests/common/L_mount.sh @@ -1,5 +1,5 @@ -skip_unless test "$SUDO" -skip_if test "$UNAME" = "Darwin" +test "$SUDO" || skip +test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} diff --git a/tests/common/ilname.sh b/tests/common/ilname.sh index 7ab0793..fc7e9e4 100644 --- a/tests/common/ilname.sh +++ b/tests/common/ilname.sh @@ -1,2 +1,2 @@ -skip_unless invoke_bfs -quit -ilname PATTERN +invoke_bfs -quit -ilname PATTERN || skip bfs_diff links -ilname '[AQ]' diff --git a/tests/common/iname.sh b/tests/common/iname.sh index 8fcc443..c25a646 100644 --- a/tests/common/iname.sh +++ b/tests/common/iname.sh @@ -1,2 +1,2 @@ -skip_unless invoke_bfs -quit -iname PATTERN +invoke_bfs -quit -iname PATTERN || skip bfs_diff basic -iname '*F*' diff --git a/tests/common/inum_bind_mount.sh b/tests/common/inum_bind_mount.sh index e35ed4e..a9e01bf 100644 --- a/tests/common/inum_bind_mount.sh +++ b/tests/common/inum_bind_mount.sh @@ -1,5 +1,5 @@ -skip_unless test "$SUDO" -skip_unless test "$UNAME" = "Linux" +test "$SUDO" || skip +test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{foo,bar} diff --git a/tests/common/inum_mount.sh b/tests/common/inum_mount.sh index f9f4e2b..7cc5e40 100644 --- a/tests/common/inum_mount.sh +++ b/tests/common/inum_mount.sh @@ -1,5 +1,5 @@ -skip_unless test "$SUDO" -skip_if test "$UNAME" = "Darwin" +test "$SUDO" || skip +test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} diff --git a/tests/common/ipath.sh b/tests/common/ipath.sh index 9e6f8c5..7d05f31 100644 --- a/tests/common/ipath.sh +++ b/tests/common/ipath.sh @@ -1,2 +1,2 @@ -skip_unless invoke_bfs -quit -ipath PATTERN +invoke_bfs -quit -ipath PATTERN || skip bfs_diff basic -ipath 'basic/*F*' diff --git a/tests/common/mount.sh b/tests/common/mount.sh index 2732a68..f077ea2 100644 --- a/tests/common/mount.sh +++ b/tests/common/mount.sh @@ -1,5 +1,5 @@ -skip_unless test "$SUDO" -skip_if test "$UNAME" = "Darwin" +test "$SUDO" || skip +test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} diff --git a/tests/gnu/exec_flush_fail.sh b/tests/gnu/exec_flush_fail.sh index 4772a14..ce796b2 100644 --- a/tests/gnu/exec_flush_fail.sh +++ b/tests/gnu/exec_flush_fail.sh @@ -1,3 +1,3 @@ # Failure to flush streams before exec should be caught -skip_unless test -e /dev/full +test -e /dev/full || skip fail invoke_bfs basic -print0 -exec true \; >/dev/full diff --git a/tests/gnu/exec_plus_flush_fail.sh b/tests/gnu/exec_plus_flush_fail.sh index 5c74fd8..8beee09 100644 --- a/tests/gnu/exec_plus_flush_fail.sh +++ b/tests/gnu/exec_plus_flush_fail.sh @@ -1,2 +1,2 @@ -skip_unless test -e /dev/full +test -e /dev/full || skip fail invoke_bfs basic/a -print0 -exec echo found {} + >/dev/full diff --git a/tests/gnu/fprint_error.sh b/tests/gnu/fprint_error.sh index e7f2394..0e75b0e 100644 --- a/tests/gnu/fprint_error.sh +++ b/tests/gnu/fprint_error.sh @@ -1,2 +1,2 @@ -skip_unless test -e /dev/full +test -e /dev/full || skip fail invoke_bfs basic -maxdepth 0 -fprint /dev/full diff --git a/tests/gnu/fprint_noerror.sh b/tests/gnu/fprint_noerror.sh index 142e935..f13a62b 100644 --- a/tests/gnu/fprint_noerror.sh +++ b/tests/gnu/fprint_noerror.sh @@ -1,3 +1,3 @@ # Regression test: /dev/full should not fail until actually written to -skip_unless test -e /dev/full +test -e /dev/full || skip invoke_bfs basic -false -fprint /dev/full diff --git a/tests/gnu/fstype.sh b/tests/gnu/fstype.sh index 939438e..05645c3 100644 --- a/tests/gnu/fstype.sh +++ b/tests/gnu/fstype.sh @@ -1,3 +1,2 @@ -fstype=$(invoke_bfs basic -maxdepth 0 -printf '%F\n') -skip_if test $? -ne 0 +fstype=$(invoke_bfs basic -maxdepth 0 -printf '%F\n') || skip bfs_diff basic -fstype "$fstype" diff --git a/tests/gnu/gid_plus.sh b/tests/gnu/gid_plus.sh index 8ad493b..ccba0e6 100644 --- a/tests/gnu/gid_plus.sh +++ b/tests/gnu/gid_plus.sh @@ -1,2 +1,2 @@ -skip_if test "$(id -g)" -eq 0 +test "$(id -g)" -eq 0 && skip bfs_diff basic -gid +0 diff --git a/tests/gnu/gid_plus_plus.sh b/tests/gnu/gid_plus_plus.sh index 7982633..ec7ae86 100644 --- a/tests/gnu/gid_plus_plus.sh +++ b/tests/gnu/gid_plus_plus.sh @@ -1,2 +1,2 @@ -skip_if test "$(id -g)" -eq 0 +test "$(id -g)" -eq 0 && skip bfs_diff basic -gid ++0 diff --git a/tests/gnu/inum_automount.sh b/tests/gnu/inum_automount.sh index 648ea05..6bf2977 100644 --- a/tests/gnu/inum_automount.sh +++ b/tests/gnu/inum_automount.sh @@ -1,11 +1,11 @@ # bfs shouldn't trigger automounts unless it descends into them -skip_unless test "$SUDO" -skip_unless command -v systemd-mount &>/dev/null +test "$SUDO" || skip +command -v systemd-mount &>/dev/null || skip clean_scratch mkdir scratch/{foo,automnt} -skip_unless sudo systemd-mount -A -o bind basic scratch/automnt +sudo systemd-mount -A -o bind basic scratch/automnt || skip before=$(inum scratch/automnt) bfs_diff scratch -inum "$before" -prune diff --git a/tests/gnu/iwholename.sh b/tests/gnu/iwholename.sh index 67e9630..0b2d038 100644 --- a/tests/gnu/iwholename.sh +++ b/tests/gnu/iwholename.sh @@ -1,2 +1,2 @@ -skip_unless invoke_bfs -quit -iwholename PATTERN +invoke_bfs -quit -iwholename PATTERN || skip bfs_diff basic -iwholename 'basic/*F*' diff --git a/tests/gnu/print_error.sh b/tests/gnu/print_error.sh index 9fd5af5..62a32b4 100644 --- a/tests/gnu/print_error.sh +++ b/tests/gnu/print_error.sh @@ -1,2 +1,2 @@ -skip_unless test -e /dev/full +test -e /dev/full || skip fail invoke_bfs basic -maxdepth 0 >/dev/full diff --git a/tests/gnu/regex_invalid_utf8.sh b/tests/gnu/regex_invalid_utf8.sh index edb4b1e..603d688 100644 --- a/tests/gnu/regex_invalid_utf8.sh +++ b/tests/gnu/regex_invalid_utf8.sh @@ -1,8 +1,8 @@ clean_scratch # Incomplete UTF-8 sequences -skip_unless touch scratch/$'\xC3' -skip_unless touch scratch/$'\xE2\x84' -skip_unless touch scratch/$'\xF0\x9F\x92' +touch scratch/$'\xC3' || skip +touch scratch/$'\xE2\x84' || skip +touch scratch/$'\xF0\x9F\x92' || skip bfs_diff scratch -regex 'scratch/..' diff --git a/tests/gnu/regextype_emacs.sh b/tests/gnu/regextype_emacs.sh index d0f68cc..3cc388c 100644 --- a/tests/gnu/regextype_emacs.sh +++ b/tests/gnu/regextype_emacs.sh @@ -1,3 +1,3 @@ -skip_unless invoke_bfs -regextype emacs -quit +invoke_bfs -regextype emacs -quit || skip bfs_diff basic -regextype emacs -regex '.*/\(f+o?o?\|bar\)' diff --git a/tests/gnu/regextype_grep.sh b/tests/gnu/regextype_grep.sh index 0136700..0830667 100644 --- a/tests/gnu/regextype_grep.sh +++ b/tests/gnu/regextype_grep.sh @@ -1,3 +1,3 @@ -skip_unless invoke_bfs -regextype grep -quit +invoke_bfs -regextype grep -quit || skip bfs_diff basic -regextype grep -regex '.*/f\+o\?o\?' diff --git a/tests/gnu/uid_plus.sh b/tests/gnu/uid_plus.sh index fc4bce3..22b2c8e 100644 --- a/tests/gnu/uid_plus.sh +++ b/tests/gnu/uid_plus.sh @@ -1,2 +1,2 @@ -skip_if test "$(id -u)" -eq 0 +test "$(id -u)" -eq 0 && skip bfs_diff basic -uid +0 diff --git a/tests/gnu/uid_plus_plus.sh b/tests/gnu/uid_plus_plus.sh index 5d5e086..e021888 100644 --- a/tests/gnu/uid_plus_plus.sh +++ b/tests/gnu/uid_plus_plus.sh @@ -1,2 +1,2 @@ -skip_if test "$(id -u)" -eq 0 +test "$(id -u)" -eq 0 && skip bfs_diff basic -uid ++0 diff --git a/tests/gnu/xtype_bind_mount.sh b/tests/gnu/xtype_bind_mount.sh index 264b6f8..a6dbed3 100644 --- a/tests/gnu/xtype_bind_mount.sh +++ b/tests/gnu/xtype_bind_mount.sh @@ -1,5 +1,5 @@ -skip_unless test "$SUDO" -skip_unless test "$UNAME" = "Linux" +test "$SUDO" || skip +test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{file,null} diff --git a/tests/posix/L_xdev.sh b/tests/posix/L_xdev.sh index ddbadd8..2fc99dd 100644 --- a/tests/posix/L_xdev.sh +++ b/tests/posix/L_xdev.sh @@ -1,5 +1,5 @@ -skip_unless test "$SUDO" -skip_if test "$UNAME" = "Darwin" +test "$SUDO" || skip +test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} diff --git a/tests/posix/name_bracket.sh b/tests/posix/name_bracket.sh index 84a417f..80ca186 100644 --- a/tests/posix/name_bracket.sh +++ b/tests/posix/name_bracket.sh @@ -1,5 +1,5 @@ # fnmatch() is broken on macOS -skip_if test "$UNAME" = "Darwin" +test "$UNAME" = "Darwin" && skip # An unclosed [ should be matched literally bfs_diff weirdnames -name '[' diff --git a/tests/posix/readdir_error.sh b/tests/posix/readdir_error.sh index ce06723..483f543 100644 --- a/tests/posix/readdir_error.sh +++ b/tests/posix/readdir_error.sh @@ -1,4 +1,4 @@ -skip_unless test "$UNAME" = "Linux" +test "$UNAME" = "Linux" || skip clean_scratch mkfifo scratch/{fever,pid,wait,running} @@ -33,5 +33,5 @@ while [ "$state" != "Z" ]; do done # On Linux, open(/proc/$pid/net) will succeed but readdir() will fail -skip_unless test -r "/proc/$pid/net" +test -r "/proc/$pid/net" || skip fail invoke_bfs "/proc/$pid/net" >/dev/null diff --git a/tests/posix/type_bind_mount.sh b/tests/posix/type_bind_mount.sh index 445f6ef..fe32875 100644 --- a/tests/posix/type_bind_mount.sh +++ b/tests/posix/type_bind_mount.sh @@ -1,5 +1,5 @@ -skip_unless test "$SUDO" -skip_unless test "$UNAME" = "Linux" +test "$SUDO" || skip +test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{file,null} diff --git a/tests/posix/xdev.sh b/tests/posix/xdev.sh index 4591940..44e04dd 100644 --- a/tests/posix/xdev.sh +++ b/tests/posix/xdev.sh @@ -1,5 +1,5 @@ -skip_unless test "$SUDO" -skip_if test "$UNAME" = "Darwin" +test "$SUDO" || skip +test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} diff --git a/tests/tests.sh b/tests/tests.sh index 3fdf49b..3047bf4 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -543,16 +543,6 @@ function skip() { exit $EX_SKIP } -function skip_if() { - if "$@"; then - skip - fi -} - -function skip_unless() { - skip_if fail "$@" -} - function closefrom() { if [ -d /proc/self/fd ]; then local fds=/proc/self/fd -- cgit v1.2.3 From 7d87b96b421b76e387cee903b7b7c1bc16c54310 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Dec 2022 14:39:18 -0500 Subject: tests: Move crash detection into invoke_bfs, use ! instead of fail --- tests/bfs/D_incomplete.sh | 2 +- tests/bfs/and_incomplete.sh | 2 +- tests/bfs/comma_incomplete.sh | 2 +- tests/bfs/exclude_exclude.sh | 2 +- tests/bfs/exclude_print.sh | 2 +- tests/bfs/exec_flush_fprint_fail.sh | 2 +- tests/bfs/fprint_error_stderr.sh | 2 +- tests/bfs/fprint_error_stdout.sh | 2 +- tests/bfs/high_byte.sh | 2 +- tests/bfs/links_empty.sh | 2 +- tests/bfs/links_invalid.sh | 2 +- tests/bfs/links_negative.sh | 2 +- tests/bfs/links_noarg.sh | 2 +- tests/bfs/newerma_nonexistent.sh | 2 +- tests/bfs/newermq.sh | 2 +- tests/bfs/newermt_invalid.sh | 2 +- tests/bfs/newerqm.sh | 2 +- tests/bfs/or_incomplete.sh | 2 +- tests/bfs/perm_symbolic_double_comma.sh | 2 +- tests/bfs/perm_symbolic_missing_action.sh | 2 +- tests/bfs/perm_symbolic_trailing_comma.sh | 2 +- tests/bfs/printf_duplicate_flag.sh | 2 +- tests/bfs/printf_everything.sh | 4 ++-- tests/bfs/printf_incomplete_escape.sh | 2 +- tests/bfs/printf_incomplete_format.sh | 2 +- tests/bfs/printf_invalid_escape.sh | 2 +- tests/bfs/printf_invalid_format.sh | 2 +- tests/bfs/printf_must_be_numeric.sh | 2 +- tests/bfs/stderr_fails_loudly.sh | 2 +- tests/bfs/unexpected_operator.sh | 2 +- tests/bfs/xtype_depth.sh | 2 +- tests/bsd/f_incomplete.sh | 2 +- tests/bsd/mtime_bad_unit.sh | 2 +- tests/bsd/mtime_missing_unit.sh | 2 +- tests/common/maxdepth_incomplete.sh | 2 +- tests/common/mindepth_incomplete.sh | 2 +- tests/gnu/exec_flush_fail.sh | 2 +- tests/gnu/exec_nothing.sh | 2 +- tests/gnu/exec_plus_flush_fail.sh | 2 +- tests/gnu/files0_from_empty.sh | 2 +- tests/gnu/files0_from_error.sh | 2 +- tests/gnu/files0_from_none.sh | 2 +- tests/gnu/files0_from_nothing.sh | 2 +- tests/gnu/files0_from_nowhere.sh | 2 +- tests/gnu/files0_from_ok.sh | 2 +- tests/gnu/fls_nonexistent.sh | 2 +- tests/gnu/fprint0_nonexistent.sh | 2 +- tests/gnu/fprint_error.sh | 2 +- tests/gnu/fprint_noarg.sh | 2 +- tests/gnu/fprint_nonexistent.sh | 2 +- tests/gnu/fprintf_nofile.sh | 2 +- tests/gnu/fprintf_noformat.sh | 2 +- tests/gnu/fprintf_nonexistent.sh | 2 +- tests/gnu/ignore_readdir_race_root.sh | 2 +- tests/gnu/ok_nothing.sh | 2 +- tests/gnu/print_error.sh | 2 +- tests/gnu/regex_error.sh | 2 +- tests/posix/closed_stderr.sh | 2 +- tests/posix/closed_stdout.sh | 2 +- tests/posix/exec_plus_nothing.sh | 2 +- tests/posix/extra_paren.sh | 2 +- tests/posix/incomplete.sh | 2 +- tests/posix/missing_paren.sh | 2 +- tests/posix/newer_nonexistent.sh | 2 +- tests/posix/ok_plus_nothing.sh | 2 +- tests/posix/readdir_error.sh | 2 +- tests/tests.sh | 16 +++++----------- 67 files changed, 72 insertions(+), 78 deletions(-) diff --git a/tests/bfs/D_incomplete.sh b/tests/bfs/D_incomplete.sh index 396d365..30c522a 100644 --- a/tests/bfs/D_incomplete.sh +++ b/tests/bfs/D_incomplete.sh @@ -1 +1 @@ -fail invoke_bfs -D +! invoke_bfs -D diff --git a/tests/bfs/and_incomplete.sh b/tests/bfs/and_incomplete.sh index f7bc2c3..05abc2d 100644 --- a/tests/bfs/and_incomplete.sh +++ b/tests/bfs/and_incomplete.sh @@ -1 +1 @@ -fail invoke_bfs -print -a +! invoke_bfs -print -a diff --git a/tests/bfs/comma_incomplete.sh b/tests/bfs/comma_incomplete.sh index 07cf505..bd60168 100644 --- a/tests/bfs/comma_incomplete.sh +++ b/tests/bfs/comma_incomplete.sh @@ -1 +1 @@ -fail invoke_bfs -print , +! invoke_bfs -print , diff --git a/tests/bfs/exclude_exclude.sh b/tests/bfs/exclude_exclude.sh index c687623..739342f 100644 --- a/tests/bfs/exclude_exclude.sh +++ b/tests/bfs/exclude_exclude.sh @@ -1 +1 @@ -fail invoke_bfs basic -exclude -exclude -name foo +! invoke_bfs basic -exclude -exclude -name foo diff --git a/tests/bfs/exclude_print.sh b/tests/bfs/exclude_print.sh index 52ff0fd..dc89e1d 100644 --- a/tests/bfs/exclude_print.sh +++ b/tests/bfs/exclude_print.sh @@ -1 +1 @@ -fail invoke_bfs basic -exclude -print +! invoke_bfs basic -exclude -print diff --git a/tests/bfs/exec_flush_fprint_fail.sh b/tests/bfs/exec_flush_fprint_fail.sh index b58624a..cd38e41 100644 --- a/tests/bfs/exec_flush_fprint_fail.sh +++ b/tests/bfs/exec_flush_fprint_fail.sh @@ -1,2 +1,2 @@ test -e /dev/full || skip -fail invoke_bfs basic/a -fprint /dev/full -exec true \; +! invoke_bfs basic/a -fprint /dev/full -exec true \; diff --git a/tests/bfs/fprint_error_stderr.sh b/tests/bfs/fprint_error_stderr.sh index e51d026..2cc4037 100644 --- a/tests/bfs/fprint_error_stderr.sh +++ b/tests/bfs/fprint_error_stderr.sh @@ -1,2 +1,2 @@ test -e /dev/full || skip -fail invoke_bfs basic -maxdepth 0 -fprint /dev/full 2>/dev/full +! invoke_bfs basic -maxdepth 0 -fprint /dev/full 2>/dev/full diff --git a/tests/bfs/fprint_error_stdout.sh b/tests/bfs/fprint_error_stdout.sh index 6aa4b11..42a7b36 100644 --- a/tests/bfs/fprint_error_stdout.sh +++ b/tests/bfs/fprint_error_stdout.sh @@ -1,2 +1,2 @@ test -e /dev/full || skip -fail invoke_bfs basic -maxdepth 0 -fprint /dev/full >/dev/full +! invoke_bfs basic -maxdepth 0 -fprint /dev/full >/dev/full diff --git a/tests/bfs/high_byte.sh b/tests/bfs/high_byte.sh index 222f24b..c76199f 100644 --- a/tests/bfs/high_byte.sh +++ b/tests/bfs/high_byte.sh @@ -1 +1 @@ -fail invoke_bfs -$'\xFF' +! invoke_bfs -$'\xFF' diff --git a/tests/bfs/links_empty.sh b/tests/bfs/links_empty.sh index 34c7c25..42cf6e5 100644 --- a/tests/bfs/links_empty.sh +++ b/tests/bfs/links_empty.sh @@ -1 +1 @@ -fail invoke_bfs links -links '' +! invoke_bfs links -links '' diff --git a/tests/bfs/links_invalid.sh b/tests/bfs/links_invalid.sh index ff69fa6..4d139c9 100644 --- a/tests/bfs/links_invalid.sh +++ b/tests/bfs/links_invalid.sh @@ -1 +1 @@ -fail invoke_bfs links -links ASDF +! invoke_bfs links -links ASDF diff --git a/tests/bfs/links_negative.sh b/tests/bfs/links_negative.sh index b5d9c58..e664b99 100644 --- a/tests/bfs/links_negative.sh +++ b/tests/bfs/links_negative.sh @@ -1 +1 @@ -fail invoke_bfs links -links +-1 +! invoke_bfs links -links +-1 diff --git a/tests/bfs/links_noarg.sh b/tests/bfs/links_noarg.sh index 5dede5f..5c948dc 100644 --- a/tests/bfs/links_noarg.sh +++ b/tests/bfs/links_noarg.sh @@ -1 +1 @@ -fail invoke_bfs links -links +! invoke_bfs links -links diff --git a/tests/bfs/newerma_nonexistent.sh b/tests/bfs/newerma_nonexistent.sh index 7f3695f..cdedb4a 100644 --- a/tests/bfs/newerma_nonexistent.sh +++ b/tests/bfs/newerma_nonexistent.sh @@ -1 +1 @@ -fail invoke_bfs times -newerma basic/nonexistent +! invoke_bfs times -newerma basic/nonexistent diff --git a/tests/bfs/newermq.sh b/tests/bfs/newermq.sh index 2a22586..2f705dc 100644 --- a/tests/bfs/newermq.sh +++ b/tests/bfs/newermq.sh @@ -1 +1 @@ -fail invoke_bfs times -newermq times/a +! invoke_bfs times -newermq times/a diff --git a/tests/bfs/newermt_invalid.sh b/tests/bfs/newermt_invalid.sh index 61d2485..98efece 100644 --- a/tests/bfs/newermt_invalid.sh +++ b/tests/bfs/newermt_invalid.sh @@ -1 +1 @@ -fail invoke_bfs times -newermt not_a_date_time +! invoke_bfs times -newermt not_a_date_time diff --git a/tests/bfs/newerqm.sh b/tests/bfs/newerqm.sh index da84350..c0cff98 100644 --- a/tests/bfs/newerqm.sh +++ b/tests/bfs/newerqm.sh @@ -1 +1 @@ -fail invoke_bfs times -newerqm times/a +! invoke_bfs times -newerqm times/a diff --git a/tests/bfs/or_incomplete.sh b/tests/bfs/or_incomplete.sh index c941b95..4af31b6 100644 --- a/tests/bfs/or_incomplete.sh +++ b/tests/bfs/or_incomplete.sh @@ -1 +1 @@ -fail invoke_bfs -print -o +! invoke_bfs -print -o diff --git a/tests/bfs/perm_symbolic_double_comma.sh b/tests/bfs/perm_symbolic_double_comma.sh index 66db0ac..48f9d4b 100644 --- a/tests/bfs/perm_symbolic_double_comma.sh +++ b/tests/bfs/perm_symbolic_double_comma.sh @@ -1 +1 @@ -fail invoke_bfs perms -perm a+r,,u+w +! invoke_bfs perms -perm a+r,,u+w diff --git a/tests/bfs/perm_symbolic_missing_action.sh b/tests/bfs/perm_symbolic_missing_action.sh index 3b18721..28446ab 100644 --- a/tests/bfs/perm_symbolic_missing_action.sh +++ b/tests/bfs/perm_symbolic_missing_action.sh @@ -1 +1 @@ -fail invoke_bfs perms -perm a +! invoke_bfs perms -perm a diff --git a/tests/bfs/perm_symbolic_trailing_comma.sh b/tests/bfs/perm_symbolic_trailing_comma.sh index c52ebe6..01bbc16 100644 --- a/tests/bfs/perm_symbolic_trailing_comma.sh +++ b/tests/bfs/perm_symbolic_trailing_comma.sh @@ -1 +1 @@ -fail invoke_bfs perms -perm a+r, +! invoke_bfs perms -perm a+r, diff --git a/tests/bfs/printf_duplicate_flag.sh b/tests/bfs/printf_duplicate_flag.sh index 77650d0..5ff29f1 100644 --- a/tests/bfs/printf_duplicate_flag.sh +++ b/tests/bfs/printf_duplicate_flag.sh @@ -1 +1 @@ -fail invoke_bfs basic -printf '%--p' +! invoke_bfs basic -printf '%--p' diff --git a/tests/bfs/printf_everything.sh b/tests/bfs/printf_everything.sh index 5f20718..5e95830 100644 --- a/tests/bfs/printf_everything.sh +++ b/tests/bfs/printf_everything.sh @@ -1,14 +1,14 @@ everything=(%{a,b,c,d,D,f,g,G,h,H,i,k,l,m,M,n,p,P,s,S,t,u,U,y,Y}) # Check if we have fstypes -if ! fail invoke_bfs basic -printf '%F' -quit >/dev/null; then +if ! ! invoke_bfs basic -printf '%F' -quit >/dev/null; then everything+=(%F) fi everything+=(%{A,C,T}{%,+,@,a,A,b,B,c,C,d,D,e,F,g,G,h,H,I,j,k,l,m,M,n,p,r,R,s,S,t,T,u,U,V,w,W,x,X,y,Y,z,Z}) # Check if we have birth times -if ! fail invoke_bfs basic -printf '%w' -quit >/dev/null; then +if ! ! invoke_bfs basic -printf '%w' -quit >/dev/null; then everything+=(%w %{B,W}{%,+,@,a,A,b,B,c,C,d,D,e,F,g,G,h,H,I,j,k,l,m,M,n,p,r,R,s,S,t,T,u,U,V,w,W,x,X,y,Y,z,Z}) fi diff --git a/tests/bfs/printf_incomplete_escape.sh b/tests/bfs/printf_incomplete_escape.sh index 144add5..f560d28 100644 --- a/tests/bfs/printf_incomplete_escape.sh +++ b/tests/bfs/printf_incomplete_escape.sh @@ -1 +1 @@ -fail invoke_bfs basic -printf '\' +! invoke_bfs basic -printf '\' diff --git a/tests/bfs/printf_incomplete_format.sh b/tests/bfs/printf_incomplete_format.sh index 347a0f4..92c6afc 100644 --- a/tests/bfs/printf_incomplete_format.sh +++ b/tests/bfs/printf_incomplete_format.sh @@ -1 +1 @@ -fail invoke_bfs basic -printf '%' +! invoke_bfs basic -printf '%' diff --git a/tests/bfs/printf_invalid_escape.sh b/tests/bfs/printf_invalid_escape.sh index ce12233..4338f9b 100644 --- a/tests/bfs/printf_invalid_escape.sh +++ b/tests/bfs/printf_invalid_escape.sh @@ -1 +1 @@ -fail invoke_bfs basic -printf '\!' +! invoke_bfs basic -printf '\!' diff --git a/tests/bfs/printf_invalid_format.sh b/tests/bfs/printf_invalid_format.sh index 1717615..59d63a7 100644 --- a/tests/bfs/printf_invalid_format.sh +++ b/tests/bfs/printf_invalid_format.sh @@ -1 +1 @@ -fail invoke_bfs basic -printf '%!' +! invoke_bfs basic -printf '%!' diff --git a/tests/bfs/printf_must_be_numeric.sh b/tests/bfs/printf_must_be_numeric.sh index eabb3d6..7c7c3fa 100644 --- a/tests/bfs/printf_must_be_numeric.sh +++ b/tests/bfs/printf_must_be_numeric.sh @@ -1 +1 @@ -fail invoke_bfs basic -printf '%+p' +! invoke_bfs basic -printf '%+p' diff --git a/tests/bfs/stderr_fails_loudly.sh b/tests/bfs/stderr_fails_loudly.sh index c423d9e..8572d5a 100644 --- a/tests/bfs/stderr_fails_loudly.sh +++ b/tests/bfs/stderr_fails_loudly.sh @@ -1,2 +1,2 @@ test -e /dev/full || skip -fail invoke_bfs -D all basic -false -fprint /dev/full 2>/dev/full +! invoke_bfs -D all basic -false -fprint /dev/full 2>/dev/full diff --git a/tests/bfs/unexpected_operator.sh b/tests/bfs/unexpected_operator.sh index b3658f6..2eb0e71 100644 --- a/tests/bfs/unexpected_operator.sh +++ b/tests/bfs/unexpected_operator.sh @@ -1 +1 @@ -fail invoke_bfs \! -o -print +! invoke_bfs \! -o -print diff --git a/tests/bfs/xtype_depth.sh b/tests/bfs/xtype_depth.sh index cd478af..02c8173 100644 --- a/tests/bfs/xtype_depth.sh +++ b/tests/bfs/xtype_depth.sh @@ -1,2 +1,2 @@ # Make sure -xtype is considered side-effecting for facts_when_impure -fail invoke_bfs loops -xtype l -depth 100 +! invoke_bfs loops -xtype l -depth 100 diff --git a/tests/bsd/f_incomplete.sh b/tests/bsd/f_incomplete.sh index acb63af..50afe42 100644 --- a/tests/bsd/f_incomplete.sh +++ b/tests/bsd/f_incomplete.sh @@ -1,2 +1,2 @@ -fail invoke_bfs -f +! invoke_bfs -f diff --git a/tests/bsd/mtime_bad_unit.sh b/tests/bsd/mtime_bad_unit.sh index 3921f80..6e2caf1 100644 --- a/tests/bsd/mtime_bad_unit.sh +++ b/tests/bsd/mtime_bad_unit.sh @@ -1 +1 @@ -fail invoke_bfs times -mtime +1q +! invoke_bfs times -mtime +1q diff --git a/tests/bsd/mtime_missing_unit.sh b/tests/bsd/mtime_missing_unit.sh index 3ac4c97..f6b1f93 100644 --- a/tests/bsd/mtime_missing_unit.sh +++ b/tests/bsd/mtime_missing_unit.sh @@ -1 +1 @@ -fail invoke_bfs times -mtime +1w2 +! invoke_bfs times -mtime +1w2 diff --git a/tests/common/maxdepth_incomplete.sh b/tests/common/maxdepth_incomplete.sh index 536dcf5..0bcb461 100644 --- a/tests/common/maxdepth_incomplete.sh +++ b/tests/common/maxdepth_incomplete.sh @@ -1 +1 @@ -fail invoke_bfs basic -maxdepth +! invoke_bfs basic -maxdepth diff --git a/tests/common/mindepth_incomplete.sh b/tests/common/mindepth_incomplete.sh index 19a3b21..6f55a42 100644 --- a/tests/common/mindepth_incomplete.sh +++ b/tests/common/mindepth_incomplete.sh @@ -1 +1 @@ -fail invoke_bfs basic -mindepth +! invoke_bfs basic -mindepth diff --git a/tests/gnu/exec_flush_fail.sh b/tests/gnu/exec_flush_fail.sh index ce796b2..5505f7a 100644 --- a/tests/gnu/exec_flush_fail.sh +++ b/tests/gnu/exec_flush_fail.sh @@ -1,3 +1,3 @@ # Failure to flush streams before exec should be caught test -e /dev/full || skip -fail invoke_bfs basic -print0 -exec true \; >/dev/full +! invoke_bfs basic -print0 -exec true \; >/dev/full diff --git a/tests/gnu/exec_nothing.sh b/tests/gnu/exec_nothing.sh index 9d613e8..443aa0d 100644 --- a/tests/gnu/exec_nothing.sh +++ b/tests/gnu/exec_nothing.sh @@ -1,2 +1,2 @@ # Regression test: don't segfault on missing command -fail invoke_bfs basic -exec \; +! invoke_bfs basic -exec \; diff --git a/tests/gnu/exec_plus_flush_fail.sh b/tests/gnu/exec_plus_flush_fail.sh index 8beee09..53a50e5 100644 --- a/tests/gnu/exec_plus_flush_fail.sh +++ b/tests/gnu/exec_plus_flush_fail.sh @@ -1,2 +1,2 @@ test -e /dev/full || skip -fail invoke_bfs basic/a -print0 -exec echo found {} + >/dev/full +! invoke_bfs basic/a -print0 -exec echo found {} + >/dev/full diff --git a/tests/gnu/files0_from_empty.sh b/tests/gnu/files0_from_empty.sh index bd4fbf4..85eee8f 100644 --- a/tests/gnu/files0_from_empty.sh +++ b/tests/gnu/files0_from_empty.sh @@ -1 +1 @@ -printf "\0" | fail invoke_bfs -files0-from - +! printf "\0" | invoke_bfs -files0-from - diff --git a/tests/gnu/files0_from_error.sh b/tests/gnu/files0_from_error.sh index ab27ea2..1515d0b 100644 --- a/tests/gnu/files0_from_error.sh +++ b/tests/gnu/files0_from_error.sh @@ -1 +1 @@ -fail invoke_bfs -files0-from basic +! invoke_bfs -files0-from basic diff --git a/tests/gnu/files0_from_none.sh b/tests/gnu/files0_from_none.sh index c6e5b97..090fce0 100644 --- a/tests/gnu/files0_from_none.sh +++ b/tests/gnu/files0_from_none.sh @@ -1 +1 @@ -printf "" | fail invoke_bfs -files0-from - +! printf "" | invoke_bfs -files0-from - diff --git a/tests/gnu/files0_from_nothing.sh b/tests/gnu/files0_from_nothing.sh index 5fdae60..fee50a8 100644 --- a/tests/gnu/files0_from_nothing.sh +++ b/tests/gnu/files0_from_nothing.sh @@ -1 +1 @@ -fail invoke_bfs -files0-from basic/nonexistent +! invoke_bfs -files0-from basic/nonexistent diff --git a/tests/gnu/files0_from_nowhere.sh b/tests/gnu/files0_from_nowhere.sh index 2337613..68eea4b 100644 --- a/tests/gnu/files0_from_nowhere.sh +++ b/tests/gnu/files0_from_nowhere.sh @@ -1 +1 @@ -fail invoke_bfs -files0-from +! invoke_bfs -files0-from diff --git a/tests/gnu/files0_from_ok.sh b/tests/gnu/files0_from_ok.sh index 5387e5c..8e145ce 100644 --- a/tests/gnu/files0_from_ok.sh +++ b/tests/gnu/files0_from_ok.sh @@ -1 +1 @@ -printf "basic\0" | fail invoke_bfs -files0-from - -ok echo {} \; +! printf "basic\0" | invoke_bfs -files0-from - -ok echo {} \; diff --git a/tests/gnu/fls_nonexistent.sh b/tests/gnu/fls_nonexistent.sh index 4756834..ff86763 100644 --- a/tests/gnu/fls_nonexistent.sh +++ b/tests/gnu/fls_nonexistent.sh @@ -1 +1 @@ -fail invoke_bfs rainbow -fls scratch/nonexistent/path +! invoke_bfs rainbow -fls scratch/nonexistent/path diff --git a/tests/gnu/fprint0_nonexistent.sh b/tests/gnu/fprint0_nonexistent.sh index d8e0f30..ec14c2d 100644 --- a/tests/gnu/fprint0_nonexistent.sh +++ b/tests/gnu/fprint0_nonexistent.sh @@ -1 +1 @@ -fail invoke_bfs basic -fprint0 scratch/nonexistent/path +! invoke_bfs basic -fprint0 scratch/nonexistent/path diff --git a/tests/gnu/fprint_error.sh b/tests/gnu/fprint_error.sh index 0e75b0e..7617034 100644 --- a/tests/gnu/fprint_error.sh +++ b/tests/gnu/fprint_error.sh @@ -1,2 +1,2 @@ test -e /dev/full || skip -fail invoke_bfs basic -maxdepth 0 -fprint /dev/full +! invoke_bfs basic -maxdepth 0 -fprint /dev/full diff --git a/tests/gnu/fprint_noarg.sh b/tests/gnu/fprint_noarg.sh index bf772f3..8511649 100644 --- a/tests/gnu/fprint_noarg.sh +++ b/tests/gnu/fprint_noarg.sh @@ -1 +1 @@ -fail invoke_bfs basic -fprint +! invoke_bfs basic -fprint diff --git a/tests/gnu/fprint_nonexistent.sh b/tests/gnu/fprint_nonexistent.sh index b6dac8a..4409162 100644 --- a/tests/gnu/fprint_nonexistent.sh +++ b/tests/gnu/fprint_nonexistent.sh @@ -1 +1 @@ -fail invoke_bfs basic -fprint scratch/nonexistent/path +! invoke_bfs basic -fprint scratch/nonexistent/path diff --git a/tests/gnu/fprintf_nofile.sh b/tests/gnu/fprintf_nofile.sh index c2c48a5..4e79002 100644 --- a/tests/gnu/fprintf_nofile.sh +++ b/tests/gnu/fprintf_nofile.sh @@ -1 +1 @@ -fail invoke_bfs basic -fprintf +! invoke_bfs basic -fprintf diff --git a/tests/gnu/fprintf_noformat.sh b/tests/gnu/fprintf_noformat.sh index 0d285c3..fd97f4c 100644 --- a/tests/gnu/fprintf_noformat.sh +++ b/tests/gnu/fprintf_noformat.sh @@ -1 +1 @@ -fail invoke_bfs basic -fprintf /dev/null +! invoke_bfs basic -fprintf /dev/null diff --git a/tests/gnu/fprintf_nonexistent.sh b/tests/gnu/fprintf_nonexistent.sh index 6ed141a..160e739 100644 --- a/tests/gnu/fprintf_nonexistent.sh +++ b/tests/gnu/fprintf_nonexistent.sh @@ -1 +1 @@ -fail invoke_bfs basic -fprintf scratch/nonexistent/path '%p\n' +! invoke_bfs basic -fprintf scratch/nonexistent/path '%p\n' diff --git a/tests/gnu/ignore_readdir_race_root.sh b/tests/gnu/ignore_readdir_race_root.sh index bad7391..dc41e7f 100644 --- a/tests/gnu/ignore_readdir_race_root.sh +++ b/tests/gnu/ignore_readdir_race_root.sh @@ -1,2 +1,2 @@ # Make sure -ignore_readdir_race doesn't suppress ENOENT at the root -fail invoke_bfs basic/nonexistent -ignore_readdir_race +! invoke_bfs basic/nonexistent -ignore_readdir_race diff --git a/tests/gnu/ok_nothing.sh b/tests/gnu/ok_nothing.sh index 439687b..52c3547 100644 --- a/tests/gnu/ok_nothing.sh +++ b/tests/gnu/ok_nothing.sh @@ -1,2 +1,2 @@ # Regression test: don't segfault on missing command -fail invoke_bfs basic -ok \; +! invoke_bfs basic -ok \; diff --git a/tests/gnu/print_error.sh b/tests/gnu/print_error.sh index 62a32b4..bc79637 100644 --- a/tests/gnu/print_error.sh +++ b/tests/gnu/print_error.sh @@ -1,2 +1,2 @@ test -e /dev/full || skip -fail invoke_bfs basic -maxdepth 0 >/dev/full +! invoke_bfs basic -maxdepth 0 >/dev/full diff --git a/tests/gnu/regex_error.sh b/tests/gnu/regex_error.sh index 9bd4c8d..4af933f 100644 --- a/tests/gnu/regex_error.sh +++ b/tests/gnu/regex_error.sh @@ -1 +1 @@ -fail invoke_bfs basic -regex '[' +! invoke_bfs basic -regex '[' diff --git a/tests/posix/closed_stderr.sh b/tests/posix/closed_stderr.sh index cc746ef..570d5bb 100644 --- a/tests/posix/closed_stderr.sh +++ b/tests/posix/closed_stderr.sh @@ -1 +1 @@ -fail invoke_bfs basic >&- 2>&- +! invoke_bfs basic >&- 2>&- diff --git a/tests/posix/closed_stdout.sh b/tests/posix/closed_stdout.sh index 446bf56..25c060d 100644 --- a/tests/posix/closed_stdout.sh +++ b/tests/posix/closed_stdout.sh @@ -1 +1 @@ -fail invoke_bfs basic >&- +! invoke_bfs basic >&- diff --git a/tests/posix/exec_plus_nothing.sh b/tests/posix/exec_plus_nothing.sh index ef01968..347722d 100644 --- a/tests/posix/exec_plus_nothing.sh +++ b/tests/posix/exec_plus_nothing.sh @@ -1,2 +1,2 @@ # Regression test: don't look OOB for {} + -fail invoke_bfs basic -exec + +! invoke_bfs basic -exec + diff --git a/tests/posix/extra_paren.sh b/tests/posix/extra_paren.sh index cd8e8f8..d15022f 100644 --- a/tests/posix/extra_paren.sh +++ b/tests/posix/extra_paren.sh @@ -1 +1 @@ -fail invoke_bfs basic -print \) +! invoke_bfs basic -print \) diff --git a/tests/posix/incomplete.sh b/tests/posix/incomplete.sh index 07b1b61..bca5a13 100644 --- a/tests/posix/incomplete.sh +++ b/tests/posix/incomplete.sh @@ -1 +1 @@ -fail invoke_bfs basic \( +! invoke_bfs basic \( diff --git a/tests/posix/missing_paren.sh b/tests/posix/missing_paren.sh index ac8dd60..d906fbe 100644 --- a/tests/posix/missing_paren.sh +++ b/tests/posix/missing_paren.sh @@ -1 +1 @@ -fail invoke_bfs basic \( -print +! invoke_bfs basic \( -print diff --git a/tests/posix/newer_nonexistent.sh b/tests/posix/newer_nonexistent.sh index 789cadf..5f2da4b 100644 --- a/tests/posix/newer_nonexistent.sh +++ b/tests/posix/newer_nonexistent.sh @@ -1 +1 @@ -fail invoke_bfs times -newer times/nonexistent +! invoke_bfs times -newer times/nonexistent diff --git a/tests/posix/ok_plus_nothing.sh b/tests/posix/ok_plus_nothing.sh index 7cb7de5..77c7644 100644 --- a/tests/posix/ok_plus_nothing.sh +++ b/tests/posix/ok_plus_nothing.sh @@ -1,2 +1,2 @@ # Regression test: don't look OOB for {} + -fail invoke_bfs basic -ok + +! invoke_bfs basic -ok + diff --git a/tests/posix/readdir_error.sh b/tests/posix/readdir_error.sh index 483f543..e45ec5c 100644 --- a/tests/posix/readdir_error.sh +++ b/tests/posix/readdir_error.sh @@ -34,4 +34,4 @@ done # On Linux, open(/proc/$pid/net) will succeed but readdir() will fail test -r "/proc/$pid/net" || skip -fail invoke_bfs "/proc/$pid/net" >/dev/null +! invoke_bfs "/proc/$pid/net" >/dev/null diff --git a/tests/tests.sh b/tests/tests.sh index 3047bf4..844de5c 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -476,19 +476,13 @@ function bfs_verbose() { function invoke_bfs() { bfs_verbose "$@" "${BFS[@]}" "$@" -} - -# Expect a command to fail, but not crash -function fail() { - "$@" - local STATUS="$?" + local status=$? - if ((STATUS > 125)); then - exit "$STATUS" - elif ((STATUS > 0)); then - return 0 + # Allow bfs to fail, but not crash + if ((status > 125)); then + exit $status else - return 1 + return $status fi } -- cgit v1.2.3 From e50c19f284dad6b4b7b79f91cc8576a97626be8a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Dec 2022 15:32:04 -0500 Subject: tests: Turn on set -e --- tests/bfs/D_unknown.sh | 2 +- tests/bfs/O9.sh | 2 +- tests/bfs/execdir_plus_nonexistent.sh | 7 +++---- tests/bfs/help.sh | 10 ++++------ tests/bsd/X.sh | 3 +-- tests/bsd/exit.sh | 10 ++-------- tests/common/L_mount.sh | 7 +++---- tests/common/execdir_nonexistent.sh | 7 +++---- tests/common/inum_bind_mount.sh | 6 ++---- tests/common/inum_mount.sh | 6 ++---- tests/common/mount.sh | 7 +++---- tests/gnu/L_delete.sh | 2 +- tests/gnu/L_loops_continue.sh | 3 +-- tests/gnu/inum_automount.sh | 8 +++----- tests/gnu/printf_Y_error.sh | 10 +++------- tests/gnu/xtype_bind_mount.sh | 8 +++----- tests/posix/L_loops.sh | 2 +- tests/posix/L_xdev.sh | 7 +++---- tests/posix/depth_error.sh | 10 +++------- tests/posix/exec_nonexistent.sh | 7 +++---- tests/posix/exec_plus_nonexistent.sh | 7 +++---- tests/posix/exec_plus_status.sh | 3 +-- tests/posix/readdir_error.sh | 2 +- tests/posix/type_bind_mount.sh | 6 ++---- tests/posix/xdev.sh | 7 +++---- tests/tests.sh | 18 +++++++++++++----- 26 files changed, 69 insertions(+), 98 deletions(-) diff --git a/tests/bfs/D_unknown.sh b/tests/bfs/D_unknown.sh index e3614ba..cac9bd9 100644 --- a/tests/bfs/D_unknown.sh +++ b/tests/bfs/D_unknown.sh @@ -1,4 +1,4 @@ stderr=$(invoke_bfs -warn -D unknown basic 2>&1 >"$OUT") -[ -n "$stderr" ] || return 1 +[ -n "$stderr" ] sort_output diff_output diff --git a/tests/bfs/O9.sh b/tests/bfs/O9.sh index 12f6c2d..c12a7a3 100644 --- a/tests/bfs/O9.sh +++ b/tests/bfs/O9.sh @@ -1,4 +1,4 @@ stderr=$(invoke_bfs -warn -O9 basic 2>&1 >"$OUT") -[ -n "$stderr" ] || return 1 +[ -n "$stderr" ] sort_output diff_output diff --git a/tests/bfs/execdir_plus_nonexistent.sh b/tests/bfs/execdir_plus_nonexistent.sh index 8436953..88f3e90 100644 --- a/tests/bfs/execdir_plus_nonexistent.sh +++ b/tests/bfs/execdir_plus_nonexistent.sh @@ -1,5 +1,4 @@ -stderr=$(invoke_bfs basic -execdir "$TESTS/nonexistent" {} + 2>&1 >/dev/null) -[ -n "$stderr" ] || return 1 +! stderr=$(invoke_bfs basic -execdir "$TESTS/nonexistent" {} + 2>&1 >/dev/null) +[ -n "$stderr" ] -bfs_diff basic -execdir "$TESTS/nonexistent" {} + -print -(($? == EX_BFS)) +check_exit $EX_BFS bfs_diff basic -execdir "$TESTS/nonexistent" {} + -print diff --git a/tests/bfs/help.sh b/tests/bfs/help.sh index 5e5c684..2c0b28a 100644 --- a/tests/bfs/help.sh +++ b/tests/bfs/help.sh @@ -1,6 +1,4 @@ -invoke_bfs -help | grep -E '\{...?\}' && return 1 -invoke_bfs -D help | grep -E '\{...?\}' && return 1 -invoke_bfs -S help | grep -E '\{...?\}' && return 1 -invoke_bfs -regextype help | grep -E '\{...?\}' && return 1 - -return 0 +! invoke_bfs -help | grep -E '\{...?\}' +! invoke_bfs -D help | grep -E '\{...?\}' +! invoke_bfs -S help | grep -E '\{...?\}' +! invoke_bfs -regextype help | grep -E '\{...?\}' diff --git a/tests/bsd/X.sh b/tests/bsd/X.sh index 03d9eee..df9a261 100644 --- a/tests/bsd/X.sh +++ b/tests/bsd/X.sh @@ -1,2 +1 @@ -bfs_diff -X weirdnames -[ $? -eq $EX_BFS ] +check_exit $EX_BFS bfs_diff -X weirdnames diff --git a/tests/bsd/exit.sh b/tests/bsd/exit.sh index 524a75f..248349c 100644 --- a/tests/bsd/exit.sh +++ b/tests/bsd/exit.sh @@ -1,11 +1,5 @@ -invoke_bfs basic -name foo -exit 42 -if [ $? -ne 42 ]; then - return 1 -fi +check_exit 42 invoke_bfs basic -name foo -exit 42 -invoke_bfs basic -name qux -exit 42 -if [ $? -ne 0 ]; then - return 1 -fi +check_exit 0 invoke_bfs basic -name qux -exit 42 bfs_diff basic/g -print -name g -exit diff --git a/tests/common/L_mount.sh b/tests/common/L_mount.sh index 5b56762..9ab785e 100644 --- a/tests/common/L_mount.sh +++ b/tests/common/L_mount.sh @@ -3,13 +3,12 @@ test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} + sudo mount -t tmpfs tmpfs scratch/mnt +trap "sudo umount scratch/mnt" EXIT + ln -s ../mnt scratch/foo/bar "$XTOUCH" scratch/mnt/baz ln -s ../mnt/baz scratch/foo/qux bfs_diff -L scratch -mount -ret=$? - -sudo umount scratch/mnt -return $ret diff --git a/tests/common/execdir_nonexistent.sh b/tests/common/execdir_nonexistent.sh index 5d116e5..af17fe5 100644 --- a/tests/common/execdir_nonexistent.sh +++ b/tests/common/execdir_nonexistent.sh @@ -1,5 +1,4 @@ -stderr=$(invoke_bfs basic -execdir "$TESTS/nonexistent" {} \; 2>&1 >/dev/null) -[ -n "$stderr" ] || return 1 +! stderr=$(invoke_bfs basic -execdir "$TESTS/nonexistent" {} \; 2>&1 >/dev/null) +[ -n "$stderr" ] -bfs_diff basic -print -execdir "$TESTS/nonexistent" {} \; -print -(($? == EX_BFS)) +check_exit $EX_BFS bfs_diff basic -print -execdir "$TESTS/nonexistent" {} \; -print diff --git a/tests/common/inum_bind_mount.sh b/tests/common/inum_bind_mount.sh index a9e01bf..625ee3d 100644 --- a/tests/common/inum_bind_mount.sh +++ b/tests/common/inum_bind_mount.sh @@ -3,10 +3,8 @@ test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{foo,bar} + sudo mount --bind scratch/{foo,bar} +trap "sudo umount scratch/bar" EXIT bfs_diff scratch -inum "$(inum scratch/bar)" -ret=$? - -sudo umount scratch/bar -return $ret diff --git a/tests/common/inum_mount.sh b/tests/common/inum_mount.sh index 7cc5e40..91d06e2 100644 --- a/tests/common/inum_mount.sh +++ b/tests/common/inum_mount.sh @@ -3,10 +3,8 @@ test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} + sudo mount -t tmpfs tmpfs scratch/mnt +trap "sudo umount scratch/mnt" EXIT bfs_diff scratch -inum "$(inum scratch/mnt)" -ret=$? - -sudo umount scratch/mnt -return $ret diff --git a/tests/common/mount.sh b/tests/common/mount.sh index f077ea2..3f3ed38 100644 --- a/tests/common/mount.sh +++ b/tests/common/mount.sh @@ -3,11 +3,10 @@ test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} + sudo mount -t tmpfs tmpfs scratch/mnt +trap "sudo umount scratch/mnt" EXIT + "$XTOUCH" scratch/foo/bar scratch/mnt/baz bfs_diff scratch -mount -ret=$? - -sudo umount scratch/mnt -return $ret diff --git a/tests/gnu/L_delete.sh b/tests/gnu/L_delete.sh index 6ec167c..8fdb12a 100644 --- a/tests/gnu/L_delete.sh +++ b/tests/gnu/L_delete.sh @@ -4,6 +4,6 @@ mkdir scratch/bar ln -s ../foo scratch/bar/baz # Don't try to rmdir() a symlink -invoke_bfs -L scratch/bar -delete || return 1 +invoke_bfs -L scratch/bar -delete bfs_diff scratch diff --git a/tests/gnu/L_loops_continue.sh b/tests/gnu/L_loops_continue.sh index 0244137..d4c95f1 100644 --- a/tests/gnu/L_loops_continue.sh +++ b/tests/gnu/L_loops_continue.sh @@ -1,2 +1 @@ -bfs_diff -L loops -[ $? -eq $EX_BFS ] +check_exit $EX_BFS bfs_diff -L loops diff --git a/tests/gnu/inum_automount.sh b/tests/gnu/inum_automount.sh index 6bf2977..da2e3b0 100644 --- a/tests/gnu/inum_automount.sh +++ b/tests/gnu/inum_automount.sh @@ -5,13 +5,11 @@ command -v systemd-mount &>/dev/null || skip clean_scratch mkdir scratch/{foo,automnt} + sudo systemd-mount -A -o bind basic scratch/automnt || skip +trap "sudo systemd-umount scratch/automnt" EXIT before=$(inum scratch/automnt) bfs_diff scratch -inum "$before" -prune -ret=$? after=$(inum scratch/automnt) - -sudo systemd-umount scratch/automnt - -((ret == 0 && before == after)) +((before == after)) diff --git a/tests/gnu/printf_Y_error.sh b/tests/gnu/printf_Y_error.sh index 6487711..e9a2083 100644 --- a/tests/gnu/printf_Y_error.sh +++ b/tests/gnu/printf_Y_error.sh @@ -1,12 +1,8 @@ clean_scratch mkdir scratch/foo -chmod -x scratch/foo ln -s foo/bar scratch/bar -bfs_diff scratch -printf '(%p) (%l) %y %Y\n' -ret=$? - -chmod +x scratch/foo -clean_scratch +chmod -x scratch/foo +trap "chmod +x scratch/foo" EXIT -[ $ret -eq $EX_BFS ] +check_exit $EX_BFS bfs_diff scratch -printf '(%p) (%l) %y %Y\n' diff --git a/tests/gnu/xtype_bind_mount.sh b/tests/gnu/xtype_bind_mount.sh index a6dbed3..9babd9d 100644 --- a/tests/gnu/xtype_bind_mount.sh +++ b/tests/gnu/xtype_bind_mount.sh @@ -3,11 +3,9 @@ test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{file,null} -sudo mount --bind /dev/null scratch/null ln -s /dev/null scratch/link -bfs_diff -L scratch -type c -ret=$? +sudo mount --bind /dev/null scratch/null +trap "sudo umount scratch/null" EXIT -sudo umount scratch/null -return $ret +bfs_diff -L scratch -type c diff --git a/tests/posix/L_loops.sh b/tests/posix/L_loops.sh index f737cea..1314401 100644 --- a/tests/posix/L_loops.sh +++ b/tests/posix/L_loops.sh @@ -1,4 +1,4 @@ # POSIX says it's okay to either stop or keep going on seeing a filesystem # loop, as long as a diagnostic is printed -errors=$(invoke_bfs -L loops 2>&1 >/dev/null) +! errors=$(invoke_bfs -L loops 2>&1 >/dev/null) [ -n "$errors" ] diff --git a/tests/posix/L_xdev.sh b/tests/posix/L_xdev.sh index 2fc99dd..587c8bb 100644 --- a/tests/posix/L_xdev.sh +++ b/tests/posix/L_xdev.sh @@ -3,13 +3,12 @@ test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} + sudo mount -t tmpfs tmpfs scratch/mnt +trap "sudo umount scratch/mnt" EXIT + ln -s ../mnt scratch/foo/bar "$XTOUCH" scratch/mnt/baz ln -s ../mnt/baz scratch/foo/qux bfs_diff -L scratch -xdev -ret=$? - -sudo umount scratch/mnt -return $ret diff --git a/tests/posix/depth_error.sh b/tests/posix/depth_error.sh index f770210..15cc82d 100644 --- a/tests/posix/depth_error.sh +++ b/tests/posix/depth_error.sh @@ -1,11 +1,7 @@ clean_scratch "$XTOUCH" -p scratch/foo/bar -chmod a-r scratch/foo - -bfs_diff scratch -depth -ret=$? -chmod +r scratch/foo -clean_scratch +chmod a-r scratch/foo +trap "chmod +r scratch/foo" EXIT -[ $ret -eq $EX_BFS ] +check_exit $EX_BFS bfs_diff scratch -depth diff --git a/tests/posix/exec_nonexistent.sh b/tests/posix/exec_nonexistent.sh index b4e08e0..d4ad92a 100644 --- a/tests/posix/exec_nonexistent.sh +++ b/tests/posix/exec_nonexistent.sh @@ -1,8 +1,7 @@ # Failure to execute the command should lead to an error message and # non-zero exit status. See https://unix.stackexchange.com/q/704522/56202 -stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} \; 2>&1 >/dev/null) -[ -n "$stderr" ] || return 1 +! stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} \; 2>&1 >/dev/null) +[ -n "$stderr" ] -bfs_diff basic -print -exec "$TESTS/nonexistent" {} \; -print -(($? == EX_BFS)) +check_exit $EX_BFS bfs_diff basic -print -exec "$TESTS/nonexistent" {} \; -print diff --git a/tests/posix/exec_plus_nonexistent.sh b/tests/posix/exec_plus_nonexistent.sh index f96099e..6c9cb8c 100644 --- a/tests/posix/exec_plus_nonexistent.sh +++ b/tests/posix/exec_plus_nonexistent.sh @@ -1,5 +1,4 @@ -stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} + 2>&1 >/dev/null) -[ -n "$stderr" ] || return 1 +! stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} + 2>&1 >/dev/null) +[ -n "$stderr" ] -bfs_diff basic -exec "$TESTS/nonexistent" {} + -print -(($? == EX_BFS)) +check_exit $EX_BFS bfs_diff basic -exec "$TESTS/nonexistent" {} + -print diff --git a/tests/posix/exec_plus_status.sh b/tests/posix/exec_plus_status.sh index ea9e5ef..f44062e 100644 --- a/tests/posix/exec_plus_status.sh +++ b/tests/posix/exec_plus_status.sh @@ -1,4 +1,3 @@ # -exec ... {} + should always return true, but if the command fails, bfs # should exit with a non-zero status -bfs_diff basic -exec false {} + -print -(($? == EX_BFS)) +check_exit $EX_BFS bfs_diff basic -exec false {} + -print diff --git a/tests/posix/readdir_error.sh b/tests/posix/readdir_error.sh index e45ec5c..9a002a1 100644 --- a/tests/posix/readdir_error.sh +++ b/tests/posix/readdir_error.sh @@ -29,7 +29,7 @@ kill -9 "$pid" # Wait until it's really a zombie state=R while [ "$state" != "Z" ]; do - read -r _ _ state _ <"/proc/$pid/stat" || exit 1 + read -r _ _ state _ <"/proc/$pid/stat" done # On Linux, open(/proc/$pid/net) will succeed but readdir() will fail diff --git a/tests/posix/type_bind_mount.sh b/tests/posix/type_bind_mount.sh index fe32875..2d913db 100644 --- a/tests/posix/type_bind_mount.sh +++ b/tests/posix/type_bind_mount.sh @@ -3,10 +3,8 @@ test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{file,null} + sudo mount --bind /dev/null scratch/null +trap "sudo umount scratch/null" EXIT bfs_diff scratch -type c -ret=$? - -sudo umount scratch/null -return $ret diff --git a/tests/posix/xdev.sh b/tests/posix/xdev.sh index 44e04dd..9d21b14 100644 --- a/tests/posix/xdev.sh +++ b/tests/posix/xdev.sh @@ -3,11 +3,10 @@ test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} + sudo mount -t tmpfs tmpfs scratch/mnt +trap "sudo umount scratch/mnt" EXIT + "$XTOUCH" scratch/foo/bar scratch/mnt/baz bfs_diff scratch -xdev -ret=$? - -sudo umount scratch/mnt -return $ret diff --git a/tests/tests.sh b/tests/tests.sh index 844de5c..7ccd9b5 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -476,16 +476,24 @@ function bfs_verbose() { function invoke_bfs() { bfs_verbose "$@" "${BFS[@]}" "$@" - local status=$? + local status="$?" # Allow bfs to fail, but not crash if ((status > 125)); then - exit $status + exit "$status" else - return $status + return "$status" fi } +function check_exit() { + local expected="$1" + local actual="0" + shift + "$@" || actual="$?" + ((actual == expected)) +} + # Detect colored diff support if [ -t 2 ] && diff --color=always /dev/null /dev/null 2>/dev/null; then DIFF="diff --color=always" @@ -655,9 +663,9 @@ for TEST in "${TEST_CASES[@]}"; do mkdir -p "${OUT%/*}" if [ "$VERBOSE_ERRORS" ]; then - (. "$TESTS/$TEST.sh") + (set -e; . "$TESTS/$TEST.sh") else - (. "$TESTS/$TEST.sh") 2>"$TMP/stderr" + (set -e; . "$TESTS/$TEST.sh") 2>"$TMP/stderr" fi status=$? -- cgit v1.2.3 From e82a7f51d4b4e90df868e5410a0445b009b54ff2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Dec 2022 15:51:59 -0500 Subject: tests: Print the skip reason for --verbose=skipped --- tests/bfs/execdir_plus.sh | 5 +---- tests/tests.sh | 13 +++++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/tests/bfs/execdir_plus.sh b/tests/bfs/execdir_plus.sh index 9ae7764..f66b898 100644 --- a/tests/bfs/execdir_plus.sh +++ b/tests/bfs/execdir_plus.sh @@ -1,7 +1,4 @@ tree=$(invoke_bfs -D tree 2>&1 -quit) - -if [[ "$tree" == *"-S dfs"* ]]; then - skip -fi +[[ "$tree" == *"-S dfs"* ]] && skip bfs_diff basic -execdir "$TESTS/sort-args.sh" {} + diff --git a/tests/tests.sh b/tests/tests.sh index 7ccd9b5..4a95fca 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -191,7 +191,6 @@ for arg; do VERBOSE_SKIPPED=yes ;; --verbose=tests) - VERBOSE_SKIPPED=yes VERBOSE_TESTS=yes ;; --verbose) @@ -542,6 +541,15 @@ function bfs_diff() ( ) function skip() { + if [ "$VERBOSE_SKIPPED" ]; then + caller | { + read -r line file + printf "${BOL}${CYN}%s skipped!${RST} (%s)\n" "$TEST" "$(awk "NR == $line" "$file")" + } + elif [ "$VERBOSE_TESTS" ]; then + printf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" + fi + exit $EX_SKIP } @@ -673,9 +681,6 @@ for TEST in "${TEST_CASES[@]}"; do ((++passed)) elif ((status == EX_SKIP)); then ((++skipped)) - if [ "$VERBOSE_SKIPPED" ]; then - printf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" - fi else ((++failed)) [ "$VERBOSE_ERRORS" ] || cat "$TMP/stderr" >&2 -- cgit v1.2.3 From e2b540c9e2a52500b17fa1005b26b2dd5a652c09 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Dec 2022 16:43:25 -0500 Subject: tests: Fail early in bfs_diff if the diff fails Otherwise, propagate the exit code from bfs --- tests/bfs/execdir_plus_nonexistent.sh | 2 +- tests/bsd/X.sh | 2 +- tests/common/execdir_nonexistent.sh | 2 +- tests/gnu/L_loops_continue.sh | 2 +- tests/gnu/printf_Y_error.sh | 2 +- tests/posix/depth_error.sh | 2 +- tests/posix/exec_nonexistent.sh | 2 +- tests/posix/exec_plus_nonexistent.sh | 2 +- tests/posix/exec_plus_status.sh | 2 +- tests/tests.sh | 13 +++---------- 10 files changed, 12 insertions(+), 19 deletions(-) diff --git a/tests/bfs/execdir_plus_nonexistent.sh b/tests/bfs/execdir_plus_nonexistent.sh index 88f3e90..e3b4d2d 100644 --- a/tests/bfs/execdir_plus_nonexistent.sh +++ b/tests/bfs/execdir_plus_nonexistent.sh @@ -1,4 +1,4 @@ ! stderr=$(invoke_bfs basic -execdir "$TESTS/nonexistent" {} + 2>&1 >/dev/null) [ -n "$stderr" ] -check_exit $EX_BFS bfs_diff basic -execdir "$TESTS/nonexistent" {} + -print +! bfs_diff basic -execdir "$TESTS/nonexistent" {} + -print diff --git a/tests/bsd/X.sh b/tests/bsd/X.sh index df9a261..54000cf 100644 --- a/tests/bsd/X.sh +++ b/tests/bsd/X.sh @@ -1 +1 @@ -check_exit $EX_BFS bfs_diff -X weirdnames +! bfs_diff -X weirdnames diff --git a/tests/common/execdir_nonexistent.sh b/tests/common/execdir_nonexistent.sh index af17fe5..4bb4fdb 100644 --- a/tests/common/execdir_nonexistent.sh +++ b/tests/common/execdir_nonexistent.sh @@ -1,4 +1,4 @@ ! stderr=$(invoke_bfs basic -execdir "$TESTS/nonexistent" {} \; 2>&1 >/dev/null) [ -n "$stderr" ] -check_exit $EX_BFS bfs_diff basic -print -execdir "$TESTS/nonexistent" {} \; -print +! bfs_diff basic -print -execdir "$TESTS/nonexistent" {} \; -print diff --git a/tests/gnu/L_loops_continue.sh b/tests/gnu/L_loops_continue.sh index d4c95f1..55aeb33 100644 --- a/tests/gnu/L_loops_continue.sh +++ b/tests/gnu/L_loops_continue.sh @@ -1 +1 @@ -check_exit $EX_BFS bfs_diff -L loops +! bfs_diff -L loops diff --git a/tests/gnu/printf_Y_error.sh b/tests/gnu/printf_Y_error.sh index e9a2083..3aa816e 100644 --- a/tests/gnu/printf_Y_error.sh +++ b/tests/gnu/printf_Y_error.sh @@ -5,4 +5,4 @@ ln -s foo/bar scratch/bar chmod -x scratch/foo trap "chmod +x scratch/foo" EXIT -check_exit $EX_BFS bfs_diff scratch -printf '(%p) (%l) %y %Y\n' +! bfs_diff scratch -printf '(%p) (%l) %y %Y\n' diff --git a/tests/posix/depth_error.sh b/tests/posix/depth_error.sh index 15cc82d..e91fbf6 100644 --- a/tests/posix/depth_error.sh +++ b/tests/posix/depth_error.sh @@ -4,4 +4,4 @@ clean_scratch chmod a-r scratch/foo trap "chmod +r scratch/foo" EXIT -check_exit $EX_BFS bfs_diff scratch -depth +! bfs_diff scratch -depth diff --git a/tests/posix/exec_nonexistent.sh b/tests/posix/exec_nonexistent.sh index d4ad92a..901be86 100644 --- a/tests/posix/exec_nonexistent.sh +++ b/tests/posix/exec_nonexistent.sh @@ -4,4 +4,4 @@ ! stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} \; 2>&1 >/dev/null) [ -n "$stderr" ] -check_exit $EX_BFS bfs_diff basic -print -exec "$TESTS/nonexistent" {} \; -print +! bfs_diff basic -print -exec "$TESTS/nonexistent" {} \; -print diff --git a/tests/posix/exec_plus_nonexistent.sh b/tests/posix/exec_plus_nonexistent.sh index 6c9cb8c..6bddc67 100644 --- a/tests/posix/exec_plus_nonexistent.sh +++ b/tests/posix/exec_plus_nonexistent.sh @@ -1,4 +1,4 @@ ! stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} + 2>&1 >/dev/null) [ -n "$stderr" ] -check_exit $EX_BFS bfs_diff basic -exec "$TESTS/nonexistent" {} + -print +! bfs_diff basic -exec "$TESTS/nonexistent" {} + -print diff --git a/tests/posix/exec_plus_status.sh b/tests/posix/exec_plus_status.sh index f44062e..a814c4e 100644 --- a/tests/posix/exec_plus_status.sh +++ b/tests/posix/exec_plus_status.sh @@ -1,3 +1,3 @@ # -exec ... {} + should always return true, but if the command fails, bfs # should exit with a non-zero status -check_exit $EX_BFS bfs_diff basic -exec false {} + -print +! bfs_diff basic -exec false {} + -print diff --git a/tests/tests.sh b/tests/tests.sh index 4a95fca..9bf9b8b 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -500,8 +500,6 @@ else DIFF="diff" fi -# Return value when bfs fails -EX_BFS=10 # Return value when a difference is detected EX_DIFF=20 # Return value when a test is skipped @@ -529,15 +527,10 @@ function bfs_diff() ( exec 3>&- "${BFS[@]}" "$@" | sort >"$OUT" - local STATUS="${PIPESTATUS[0]}" + local status="${PIPESTATUS[0]}" - diff_output || return $EX_DIFF - - if [ "$STATUS" -eq 0 ]; then - return 0 - else - return $EX_BFS - fi + diff_output || exit $EX_DIFF + return "$status" ) function skip() { -- cgit v1.2.3 From d056ec6d2d4728b695cc146c5119b251e8234393 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Dec 2022 16:46:06 -0500 Subject: tests: Save test stderr to separate files --- tests/tests.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 9bf9b8b..962a2cf 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -666,7 +666,7 @@ for TEST in "${TEST_CASES[@]}"; do if [ "$VERBOSE_ERRORS" ]; then (set -e; . "$TESTS/$TEST.sh") else - (set -e; . "$TESTS/$TEST.sh") 2>"$TMP/stderr" + (set -e; . "$TESTS/$TEST.sh") 2>"$TMP/$TEST.err" fi status=$? @@ -676,7 +676,7 @@ for TEST in "${TEST_CASES[@]}"; do ((++skipped)) else ((++failed)) - [ "$VERBOSE_ERRORS" ] || cat "$TMP/stderr" >&2 + [ "$VERBOSE_ERRORS" ] || cat "$TMP/$TEST.err" >&2 printf "${BOL}${RED}%s failed!${RST}\n" "$TEST" [ "$STOP" ] && break fi -- cgit v1.2.3 From f92fa445c278ffa601b13b4ba98f35c8af16b761 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Dec 2022 17:44:29 -0500 Subject: tests: New test for -fstype on a stacked mount point --- tests/gnu/fstype_stacked.out | 1 + tests/gnu/fstype_stacked.sh | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 tests/gnu/fstype_stacked.out create mode 100644 tests/gnu/fstype_stacked.sh diff --git a/tests/gnu/fstype_stacked.out b/tests/gnu/fstype_stacked.out new file mode 100644 index 0000000..99c7511 --- /dev/null +++ b/tests/gnu/fstype_stacked.out @@ -0,0 +1 @@ +scratch/mnt diff --git a/tests/gnu/fstype_stacked.sh b/tests/gnu/fstype_stacked.sh new file mode 100644 index 0000000..7110402 --- /dev/null +++ b/tests/gnu/fstype_stacked.sh @@ -0,0 +1,13 @@ +test "$SUDO" || skip +test "$UNAME" = "Linux" || skip + +clean_scratch +mkdir scratch/mnt + +sudo mount -t tmpfs tmpfs scratch/mnt +trap "sudo umount scratch/mnt" EXIT + +sudo mount -t ramfs ramfs scratch/mnt +trap "sudo umount scratch/mnt; sudo umount scratch/mnt" EXIT + +bfs_diff scratch/mnt -fstype ramfs -print -o -printf '%p: %F\n' -- cgit v1.2.3 From 0040a91a6b3a192acfeec0ae1e24516b54ba872a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Dec 2022 18:03:44 -0500 Subject: tests/bfs/printf_everything: Simplify double negation --- tests/bfs/printf_everything.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/bfs/printf_everything.sh b/tests/bfs/printf_everything.sh index 5e95830..07d574a 100644 --- a/tests/bfs/printf_everything.sh +++ b/tests/bfs/printf_everything.sh @@ -1,14 +1,14 @@ everything=(%{a,b,c,d,D,f,g,G,h,H,i,k,l,m,M,n,p,P,s,S,t,u,U,y,Y}) # Check if we have fstypes -if ! ! invoke_bfs basic -printf '%F' -quit >/dev/null; then +if invoke_bfs basic -printf '%F' -quit >/dev/null; then everything+=(%F) fi everything+=(%{A,C,T}{%,+,@,a,A,b,B,c,C,d,D,e,F,g,G,h,H,I,j,k,l,m,M,n,p,r,R,s,S,t,T,u,U,V,w,W,x,X,y,Y,z,Z}) # Check if we have birth times -if ! ! invoke_bfs basic -printf '%w' -quit >/dev/null; then +if invoke_bfs basic -printf '%w' -quit >/dev/null; then everything+=(%w %{B,W}{%,+,@,a,A,b,B,c,C,d,D,e,F,g,G,h,H,I,j,k,l,m,M,n,p,r,R,s,S,t,T,u,U,V,w,W,x,X,y,Y,z,Z}) fi -- cgit v1.2.3 From b6859d7a6f7e0b3a3cb70fa75e7e46998e8f0f03 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Dec 2022 13:05:06 -0500 Subject: tests: Use bfs_sudo wrapper instead of testing $SUDO --- tests/bfs/L_capable.sh | 3 +-- tests/bfs/capable.sh | 3 +-- tests/common/L_mount.sh | 5 ++--- tests/common/inum_bind_mount.sh | 5 ++--- tests/common/inum_mount.sh | 5 ++--- tests/common/mount.sh | 5 ++--- tests/gnu/fstype_stacked.sh | 9 ++++----- tests/gnu/inum_automount.sh | 5 ++--- tests/gnu/xtype_bind_mount.sh | 5 ++--- tests/posix/L_xdev.sh | 5 ++--- tests/posix/type_bind_mount.sh | 5 ++--- tests/posix/xdev.sh | 5 ++--- tests/tests.sh | 40 +++++++++++++++++++++++++--------------- 13 files changed, 49 insertions(+), 51 deletions(-) diff --git a/tests/bfs/L_capable.sh b/tests/bfs/L_capable.sh index a349677..232d6ac 100644 --- a/tests/bfs/L_capable.sh +++ b/tests/bfs/L_capable.sh @@ -1,4 +1,3 @@ -test "$SUDO" || skip test "$UNAME" = "Linux" || skip clean_scratch @@ -6,7 +5,7 @@ clean_scratch invoke_bfs scratch -quit -capable || skip "$XTOUCH" scratch/{normal,capable} -sudo setcap all+ep scratch/capable +bfs_sudo setcap all+ep scratch/capable || skip ln -s capable scratch/link bfs_diff -L scratch -capable diff --git a/tests/bfs/capable.sh b/tests/bfs/capable.sh index 8b60ea6..e5cad63 100644 --- a/tests/bfs/capable.sh +++ b/tests/bfs/capable.sh @@ -1,4 +1,3 @@ -test "$SUDO" || skip test "$UNAME" = "Linux" || skip clean_scratch @@ -6,7 +5,7 @@ clean_scratch invoke_bfs scratch -quit -capable || skip "$XTOUCH" scratch/{normal,capable} -sudo setcap all+ep scratch/capable +bfs_sudo setcap all+ep scratch/capable || skip ln -s capable scratch/link bfs_diff scratch -capable diff --git a/tests/common/L_mount.sh b/tests/common/L_mount.sh index 9ab785e..b04acd0 100644 --- a/tests/common/L_mount.sh +++ b/tests/common/L_mount.sh @@ -1,11 +1,10 @@ -test "$SUDO" || skip test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} -sudo mount -t tmpfs tmpfs scratch/mnt -trap "sudo umount scratch/mnt" EXIT +bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip +trap "bfs_sudo umount scratch/mnt" EXIT ln -s ../mnt scratch/foo/bar "$XTOUCH" scratch/mnt/baz diff --git a/tests/common/inum_bind_mount.sh b/tests/common/inum_bind_mount.sh index 625ee3d..ecb4ec3 100644 --- a/tests/common/inum_bind_mount.sh +++ b/tests/common/inum_bind_mount.sh @@ -1,10 +1,9 @@ -test "$SUDO" || skip test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{foo,bar} -sudo mount --bind scratch/{foo,bar} -trap "sudo umount scratch/bar" EXIT +bfs_sudo mount --bind scratch/{foo,bar} || skip +trap "bfs_sudo umount scratch/bar" EXIT bfs_diff scratch -inum "$(inum scratch/bar)" diff --git a/tests/common/inum_mount.sh b/tests/common/inum_mount.sh index 91d06e2..a4832e4 100644 --- a/tests/common/inum_mount.sh +++ b/tests/common/inum_mount.sh @@ -1,10 +1,9 @@ -test "$SUDO" || skip test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} -sudo mount -t tmpfs tmpfs scratch/mnt -trap "sudo umount scratch/mnt" EXIT +bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip +trap "bfs_sudo umount scratch/mnt" EXIT bfs_diff scratch -inum "$(inum scratch/mnt)" diff --git a/tests/common/mount.sh b/tests/common/mount.sh index 3f3ed38..b13b43c 100644 --- a/tests/common/mount.sh +++ b/tests/common/mount.sh @@ -1,11 +1,10 @@ -test "$SUDO" || skip test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} -sudo mount -t tmpfs tmpfs scratch/mnt -trap "sudo umount scratch/mnt" EXIT +bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip +trap "bfs_sudo umount scratch/mnt" EXIT "$XTOUCH" scratch/foo/bar scratch/mnt/baz diff --git a/tests/gnu/fstype_stacked.sh b/tests/gnu/fstype_stacked.sh index 7110402..16f428f 100644 --- a/tests/gnu/fstype_stacked.sh +++ b/tests/gnu/fstype_stacked.sh @@ -1,13 +1,12 @@ -test "$SUDO" || skip test "$UNAME" = "Linux" || skip clean_scratch mkdir scratch/mnt -sudo mount -t tmpfs tmpfs scratch/mnt -trap "sudo umount scratch/mnt" EXIT +bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip +trap "bfs_sudo umount scratch/mnt" EXIT -sudo mount -t ramfs ramfs scratch/mnt -trap "sudo umount scratch/mnt; sudo umount scratch/mnt" EXIT +bfs_sudo mount -t ramfs ramfs scratch/mnt || skip +trap "bfs_sudo umount scratch/mnt; bfs_sudo umount scratch/mnt" EXIT bfs_diff scratch/mnt -fstype ramfs -print -o -printf '%p: %F\n' diff --git a/tests/gnu/inum_automount.sh b/tests/gnu/inum_automount.sh index da2e3b0..c4450ef 100644 --- a/tests/gnu/inum_automount.sh +++ b/tests/gnu/inum_automount.sh @@ -1,13 +1,12 @@ # bfs shouldn't trigger automounts unless it descends into them -test "$SUDO" || skip command -v systemd-mount &>/dev/null || skip clean_scratch mkdir scratch/{foo,automnt} -sudo systemd-mount -A -o bind basic scratch/automnt || skip -trap "sudo systemd-umount scratch/automnt" EXIT +bfs_sudo systemd-mount -A -o bind basic scratch/automnt || skip +trap "bfs_sudo systemd-umount scratch/automnt" EXIT before=$(inum scratch/automnt) bfs_diff scratch -inum "$before" -prune diff --git a/tests/gnu/xtype_bind_mount.sh b/tests/gnu/xtype_bind_mount.sh index 9babd9d..99a11ab 100644 --- a/tests/gnu/xtype_bind_mount.sh +++ b/tests/gnu/xtype_bind_mount.sh @@ -1,11 +1,10 @@ -test "$SUDO" || skip test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{file,null} ln -s /dev/null scratch/link -sudo mount --bind /dev/null scratch/null -trap "sudo umount scratch/null" EXIT +bfs_sudo mount --bind /dev/null scratch/null || skip +trap "bfs_sudo umount scratch/null" EXIT bfs_diff -L scratch -type c diff --git a/tests/posix/L_xdev.sh b/tests/posix/L_xdev.sh index 587c8bb..172ea23 100644 --- a/tests/posix/L_xdev.sh +++ b/tests/posix/L_xdev.sh @@ -1,11 +1,10 @@ -test "$SUDO" || skip test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} -sudo mount -t tmpfs tmpfs scratch/mnt -trap "sudo umount scratch/mnt" EXIT +bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip +trap "bfs_sudo umount scratch/mnt" EXIT ln -s ../mnt scratch/foo/bar "$XTOUCH" scratch/mnt/baz diff --git a/tests/posix/type_bind_mount.sh b/tests/posix/type_bind_mount.sh index 2d913db..c9a161d 100644 --- a/tests/posix/type_bind_mount.sh +++ b/tests/posix/type_bind_mount.sh @@ -1,10 +1,9 @@ -test "$SUDO" || skip test "$UNAME" = "Linux" || skip clean_scratch "$XTOUCH" scratch/{file,null} -sudo mount --bind /dev/null scratch/null -trap "sudo umount scratch/null" EXIT +bfs_sudo mount --bind /dev/null scratch/null || skip +trap "bfs_sudo umount scratch/null" EXIT bfs_diff scratch -type c diff --git a/tests/posix/xdev.sh b/tests/posix/xdev.sh index 9d21b14..33412bf 100644 --- a/tests/posix/xdev.sh +++ b/tests/posix/xdev.sh @@ -1,11 +1,10 @@ -test "$SUDO" || skip test "$UNAME" = "Darwin" && skip clean_scratch mkdir scratch/{foo,mnt} -sudo mount -t tmpfs tmpfs scratch/mnt -trap "sudo umount scratch/mnt" EXIT +bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip +trap "bfs_sudo umount scratch/mnt" EXIT "$XTOUCH" scratch/foo/bar scratch/mnt/baz diff --git a/tests/tests.sh b/tests/tests.sh index 962a2cf..e57db4e 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -101,18 +101,15 @@ fi function usage() { local pad=$(printf "%*s" ${#0} "") cat </dev/null; then + if ((${#SUDO[@]})) && command -v mountpoint &>/dev/null; then for path in "$TMP"/scratch/*; do if mountpoint -q "$path"; then sudo umount "$path" @@ -608,10 +619,9 @@ function make_xattrs() { *) # Linux tmpfs doesn't support the user.* namespace, so we use the security.* # namespace, which is writable by root and readable by others - [ "$SUDO" ] \ - && sudo setfattr -n security.bfs_test scratch/xattr \ - && sudo setfattr -n security.bfs_test_2 scratch/xattr_2 \ - && sudo setfattr -h -n security.bfs_test scratch/xattr_link + bfs_sudo setfattr -n security.bfs_test scratch/xattr \ + && bfs_sudo setfattr -n security.bfs_test_2 scratch/xattr_2 \ + && bfs_sudo setfattr -h -n security.bfs_test scratch/xattr_link ;; esac } -- cgit v1.2.3 From 944cd72f40a84d318453c320a84d443273956859 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 31 Dec 2022 11:31:35 -0500 Subject: build: New $(LIBBFS) variable shared between the main binary and tests --- Makefile | 84 +++++++++++++++++++++++++++++++++------------------------------- 1 file changed, 44 insertions(+), 40 deletions(-) diff --git a/Makefile b/Makefile index afd3c77..49a3a87 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ ############################################################################ # bfs # -# Copyright (C) 2015-2021 Tavian Barnes # +# Copyright (C) 2015-2023 Tavian Barnes # # # # Permission to use, copy, modify, and/or distribute this software for any # # purpose with or without fee is hereby granted. # @@ -174,6 +174,10 @@ ALL_CFLAGS = $(ALL_CPPFLAGS) $(LOCAL_CFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(DEPFLAG ALL_LDFLAGS = $(ALL_CFLAGS) $(LOCAL_LDFLAGS) $(LDFLAGS) $(EXTRA_LDFLAGS) ALL_LDLIBS = $(LOCAL_LDLIBS) $(LDLIBS) $(EXTRA_LDLIBS) +# Default make target +bfs: $(BIN)/bfs +.PHONY: bfs + # Goals that are treated like flags by this Makefile FLAG_GOALS := asan lsan msan tsan ubsan gcov release @@ -186,18 +190,10 @@ ifndef GOALS FLAG_PREREQS += bfs endif -# The different search strategies that we test -STRATEGIES := bfs dfs ids eds -STRATEGY_CHECKS := $(STRATEGIES:%=check-%) - -# All the different checks we run -CHECKS := $(STRATEGY_CHECKS) check-trie check-xtimegm - -# Custom test flags for distcheck -DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" - -bfs: $(BIN)/bfs -.PHONY: bfs +# Make sure that "make release" builds everything, but "make release obj/src/main.o" doesn't +$(FLAG_GOALS): $(FLAG_PREREQS) + @: +.PHONY: $(FLAG_GOALS) all: \ $(BIN)/bfs \ @@ -207,7 +203,26 @@ all: \ $(BIN)/tests/xtouch .PHONY: all -$(BIN)/bfs: \ +$(BIN)/%: + @$(MKDIR) $(@D) + +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ + +$(OBJ)/%.o: %.c $(OBJ)/FLAGS + @$(MKDIR) $(@D) + $(CC) $(ALL_CFLAGS) -c $< -o $@ + +# Save the full set of flags to rebuild everything when they change +$(OBJ)/FLAGS.new: + @$(MKDIR) $(@D) + @echo $(CC) : $(ALL_CFLAGS) : $(ALL_LDFLAGS) : $(ALL_LDLIBS) >$@ +.PHONY: $(OBJ)/FLAGS.new + +# Only update obj/FLAGS if obj/FLAGS.new is different +$(OBJ)/FLAGS: $(OBJ)/FLAGS.new + @test -e $@ && cmp -s $@ $< && rm $< || mv $< $@ + +# All object files except the entry point +LIBBFS := \ $(OBJ)/src/bar.o \ $(OBJ)/src/bfstd.o \ $(OBJ)/src/bftw.o \ @@ -220,7 +235,6 @@ $(BIN)/bfs: \ $(OBJ)/src/eval.o \ $(OBJ)/src/exec.o \ $(OBJ)/src/fsade.o \ - $(OBJ)/src/main.o \ $(OBJ)/src/mtab.o \ $(OBJ)/src/opt.o \ $(OBJ)/src/parse.o \ @@ -233,33 +247,15 @@ $(BIN)/bfs: \ $(OBJ)/src/xspawn.o \ $(OBJ)/src/xtime.o -$(BIN)/tests/mksock: $(OBJ)/tests/mksock.o -$(BIN)/tests/trie: $(OBJ)/src/trie.o $(OBJ)/tests/trie.o -$(BIN)/tests/xtimegm: $(OBJ)/src/xtime.o $(OBJ)/tests/xtimegm.o -$(BIN)/tests/xtouch: $(OBJ)/src/xtime.o $(OBJ)/tests/xtouch.o - -$(BIN)/%: - @$(MKDIR) $(@D) - +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ - -$(OBJ)/%.o: %.c $(OBJ)/FLAGS - @$(MKDIR) $(@D) - $(CC) $(ALL_CFLAGS) -c $< -o $@ - -# Save the full set of flags to rebuild everything when they change -$(OBJ)/FLAGS.new: - @$(MKDIR) $(@D) - @echo $(CC) : $(ALL_CFLAGS) : $(ALL_LDFLAGS) : $(ALL_LDLIBS) >$@ -.PHONY: $(OBJ)/FLAGS.new +# The main executable +$(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) -# Only update obj/FLAGS if obj/FLAGS.new is different -$(OBJ)/FLAGS: $(OBJ)/FLAGS.new - @test -e $@ && cmp -s $@ $< && rm $< || mv $< $@ +# The different search strategies that we test +STRATEGIES := bfs dfs ids eds +STRATEGY_CHECKS := $(STRATEGIES:%=check-%) -# Make sure that "make release" builds everything, but "make release obj/src/main.o" doesn't -$(FLAG_GOALS): $(FLAG_PREREQS) - @: -.PHONY: $(FLAG_GOALS) +# All the different checks we run +CHECKS := $(STRATEGY_CHECKS) check-trie check-xtimegm check: $(CHECKS) .PHONY: check $(CHECKS) @@ -270,6 +266,14 @@ $(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(BIN)/tests/mksock $(BIN)/tests/xtouch check-trie check-xtimegm: check-%: $(BIN)/tests/% $< +$(BIN)/tests/mksock: $(OBJ)/tests/mksock.o $(LIBBFS) +$(BIN)/tests/trie: $(OBJ)/tests/trie.o $(LIBBFS) +$(BIN)/tests/xtimegm: $(OBJ)/tests/xtimegm.o $(LIBBFS) +$(BIN)/tests/xtouch: $(OBJ)/tests/xtouch.o $(LIBBFS) + +# Custom test flags for distcheck +DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" + distcheck: +$(MAKE) -B asan ubsan check $(DISTCHECK_FLAGS) ifneq ($(OS),Darwin) -- cgit v1.2.3 From 683552c4c9a3dfee4ce603157bb2cf18d64fbcfc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 30 Dec 2022 14:49:46 -0500 Subject: bfstd: New wrappers for dirname()/basename() --- Makefile | 13 +++++++++---- src/bfstd.c | 45 ++++++++++++++++++++++++++++++++++++++++----- src/bfstd.h | 28 +++++++++++++++++++++++++--- src/bftw.c | 2 +- src/color.c | 2 +- src/diag.c | 11 ++++++++--- src/eval.c | 10 +--------- src/exec.c | 2 +- src/mtab.c | 5 +++-- src/parse.c | 6 ++++-- tests/bfstd.c | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/mksock.c | 22 ++++++++-------------- tests/xtouch.c | 1 - 13 files changed, 158 insertions(+), 46 deletions(-) create mode 100644 tests/bfstd.c diff --git a/Makefile b/Makefile index 49a3a87..b39a88a 100644 --- a/Makefile +++ b/Makefile @@ -197,6 +197,7 @@ $(FLAG_GOALS): $(FLAG_PREREQS) all: \ $(BIN)/bfs \ + $(BIN)/tests/bfstd \ $(BIN)/tests/mksock \ $(BIN)/tests/trie \ $(BIN)/tests/xtimegm \ @@ -250,22 +251,26 @@ LIBBFS := \ # The main executable $(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) +# Standalone binary tests +STANDALONE_CHECKS := check-bfstd check-trie check-xtimegm + # The different search strategies that we test STRATEGIES := bfs dfs ids eds STRATEGY_CHECKS := $(STRATEGIES:%=check-%) # All the different checks we run -CHECKS := $(STRATEGY_CHECKS) check-trie check-xtimegm +CHECKS := $(STANDALONE_CHECKS) $(STRATEGY_CHECKS) check: $(CHECKS) .PHONY: check $(CHECKS) +$(STANDALONE_CHECKS): check-%: $(BIN)/tests/% + $< + $(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(BIN)/tests/mksock $(BIN)/tests/xtouch ./tests/tests.sh --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) -check-trie check-xtimegm: check-%: $(BIN)/tests/% - $< - +$(BIN)/tests/bfstd: $(OBJ)/tests/bfstd.o $(LIBBFS) $(BIN)/tests/mksock: $(OBJ)/tests/mksock.o $(LIBBFS) $(BIN)/tests/trie: $(OBJ)/tests/trie.o $(LIBBFS) $(BIN)/tests/xtimegm: $(OBJ)/tests/xtimegm.o $(LIBBFS) diff --git a/src/bfstd.c b/src/bfstd.c index 1561796..3a37250 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -45,17 +45,52 @@ bool is_nonexistence_error(int error) { return error == ENOENT || errno == ENOTDIR; } -const char *xbasename(const char *path) { - const char *i; +char *xdirname(const char *path) { + size_t i = xbaseoff(path); // Skip trailing slashes - for (i = path + strlen(path); i > path && i[-1] == '/'; --i); + while (i > 0 && path[i - 1] == '/') { + --i; + } + + if (i > 0) { + return strndup(path, i); + } else if (path[i] == '/') { + return strdup("/"); + } else { + return strdup("."); + } +} + +char *xbasename(const char *path) { + size_t i = xbaseoff(path); + size_t len = strcspn(path + i, "/"); + if (len > 0) { + return strndup(path + i, len); + } else if (path[i] == '/') { + return strdup("/"); + } else { + return strdup("."); + } +} + +size_t xbaseoff(const char *path) { + size_t i = strlen(path); + + // Skip trailing slashes + while (i > 0 && path[i - 1] == '/') { + --i; + } // Find the beginning of the name - for (; i > path && i[-1] != '/'; --i); + while (i > 0 && path[i - 1] != '/') { + --i; + } // Skip leading slashes - for (; i[0] == '/' && i[1]; ++i); + while (path[i] == '/' && path[i + 1]) { + ++i; + } return i; } diff --git a/src/bfstd.h b/src/bfstd.h index 6bf6ec8..d46fa02 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -22,6 +22,7 @@ #define BFS_BFSTD_H #include +#include // #include @@ -45,13 +46,34 @@ bool is_nonexistence_error(int error); // #include /** - * basename() variant that doesn't modify the input. + * Re-entrant dirname() variant that always allocates a copy. * * @param path * The path in question. - * @return A pointer into path at the base name offset. + * @return + * The parent directory of the path. + */ +char *xdirname(const char *path); + +/** + * Re-entrant basename() variant that always allocates a copy. + * + * @param path + * The path in question. + * @return + * The final component of the path. + */ +char *xbasename(const char *path); + +/** + * Find the offset of the final component of a path. + * + * @param path + * The path in question. + * @return + * The offset of the basename. */ -const char *xbasename(const char *path); +size_t xbaseoff(const char *path); #include diff --git a/src/bftw.c b/src/bftw.c index 8c88101..5f3ebde 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -793,7 +793,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { if (ftwbuf->depth == 0) { // Compute the name offset for root paths like "foo/bar" - ftwbuf->nameoff = xbasename(ftwbuf->path) - ftwbuf->path; + ftwbuf->nameoff = xbaseoff(ftwbuf->path); } if (ftwbuf->error != 0) { diff --git a/src/color.c b/src/color.c index cc37e96..7c16ec5 100644 --- a/src/color.c +++ b/src/color.c @@ -809,7 +809,7 @@ static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW if (path == ftwbuf->path) { nameoff = ftwbuf->nameoff; } else { - nameoff = xbasename(path) - path; + nameoff = xbaseoff(path); } if (print_dirs_colored(cfile, path, ftwbuf, flags, nameoff) != 0) { diff --git a/src/diag.c b/src/diag.c index a0c11f2..b02473a 100644 --- a/src/diag.c +++ b/src/diag.c @@ -84,13 +84,18 @@ bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *fo } } +/** Get the command name without any leading directories. */ +static const char *bfs_cmd(const struct bfs_ctx *ctx) { + return ctx->argv[0] + xbaseoff(ctx->argv[0]); +} + void bfs_error_prefix(const struct bfs_ctx *ctx) { - cfprintf(ctx->cerr, "${bld}%s:${rs} ${err}error:${rs} ", xbasename(ctx->argv[0])); + cfprintf(ctx->cerr, "${bld}%s:${rs} ${err}error:${rs} ", bfs_cmd(ctx)); } bool bfs_warning_prefix(const struct bfs_ctx *ctx) { if (ctx->warn) { - cfprintf(ctx->cerr, "${bld}%s:${rs} ${wrn}warning:${rs} ", xbasename(ctx->argv[0])); + cfprintf(ctx->cerr, "${bld}%s:${rs} ${wrn}warning:${rs} ", bfs_cmd(ctx)); return true; } else { return false; @@ -99,7 +104,7 @@ bool bfs_warning_prefix(const struct bfs_ctx *ctx) { bool bfs_debug_prefix(const struct bfs_ctx *ctx, enum debug_flags flag) { if (ctx->debug & flag) { - cfprintf(ctx->cerr, "${bld}%s:${rs} ${cyn}-D %s${rs}: ", xbasename(ctx->argv[0]), debug_flag_name(flag)); + cfprintf(ctx->cerr, "${bld}%s:${rs} ${cyn}-D %s${rs}: ", bfs_cmd(ctx), debug_flag_name(flag)); return true; } else { return false; diff --git a/src/eval.c b/src/eval.c index 89591b2..32b2e0e 100644 --- a/src/eval.c +++ b/src/eval.c @@ -590,15 +590,7 @@ bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) { if (ftwbuf->depth == 0) { // Any trailing slashes are not part of the name. This can only // happen for the root path. - const char *slash = strchr(name, '/'); - if (slash && slash > name) { - copy = strndup(name, slash - name); - if (!copy) { - eval_report_error(state); - return false; - } - name = copy; - } + name = copy = xbasename(name); } bool ret = fnmatch(expr->argv[1], name, expr->num) == 0; diff --git a/src/exec.c b/src/exec.c index a1cbde1..8630469 100644 --- a/src/exec.c +++ b/src/exec.c @@ -295,7 +295,7 @@ static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) if (ftwbuf->at_fd != AT_FDCWD) { // Rely on at_fd being the immediate parent - assert(ftwbuf->at_path == xbasename(ftwbuf->at_path)); + assert(xbaseoff(ftwbuf->at_path) == 0); execbuf->wd_fd = ftwbuf->at_fd; if (!(execbuf->flags & BFS_EXEC_MULTI)) { diff --git a/src/mtab.c b/src/mtab.c index 316ec6e..39676e5 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -86,7 +86,8 @@ static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *typ goto fail_entry; } - if (!trie_insert_str(&mtab->names, xbasename(path))) { + const char *name = path + xbaseoff(path); + if (!trie_insert_str(&mtab->names, name)) { goto fail; } @@ -223,7 +224,7 @@ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statb } bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *path) { - const char *name = xbasename(path); + const char *name = path + xbaseoff(path); return trie_find_str(&mtab->names, name); } diff --git a/src/parse.c b/src/parse.c index ad32714..51a9d0a 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2888,7 +2888,8 @@ static CFILE *launch_pager(pid_t *pid, CFILE *cout) { NULL, }; - if (strcmp(xbasename(exe), "less") == 0) { + const char *cmd = exe + xbaseoff(exe); + if (strcmp(cmd, "less") == 0) { // We know less supports colors, other pagers may not ret->colors = cout->colors; argv[1] = "-FKRX"; @@ -3931,7 +3932,8 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .now = ctx->now, }; - if (strcmp(xbasename(state.command), "find") == 0) { + const char *cmd = state.command + xbaseoff(state.command); + if (strcmp(cmd, "find") == 0) { // Operate depth-first when invoked as "find" ctx->strategy = BFTW_DFS; } diff --git a/tests/bfstd.c b/tests/bfstd.c new file mode 100644 index 0000000..4a8181b --- /dev/null +++ b/tests/bfstd.c @@ -0,0 +1,57 @@ +/**************************************************************************** + * bfs * + * Copyright (C) 2022 Tavian Barnes * + * * + * Permission to use, copy, modify, and/or distribute this software for any * + * purpose with or without fee is hereby granted. * + * * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * + ****************************************************************************/ + +#include "../src/bfstd.h" +#include +#include +#include + +/** Check the result of xdirname()/xbasename(). */ +static void check_base_dir(const char *path, const char *dir, const char *base) { + char *xdir = xdirname(path); + if (!xdir) { + perror("xdirname()"); + abort(); + } else if (strcmp(xdir, dir) != 0) { + fprintf(stderr, "xdirname(\"%s\") == \"%s\" (!= \"%s\")\n", path, xdir, dir); + abort(); + } + free(xdir); + + char *xbase = xbasename(path); + if (!xbase) { + perror("xbasename()"); + abort(); + } else if (strcmp(xbase, base) != 0) { + fprintf(stderr, "xbasename(\"%s\") == \"%s\" (!= \"%s\")\n", path, xbase, base); + abort(); + } + free(xbase); +} + +int main(void) { + // From man 3p basename + check_base_dir("usr", ".", "usr"); + check_base_dir("usr/", ".", "usr"); + check_base_dir("", ".", "."); + check_base_dir("/", "/", "/"); + // check_base_dir("//", "/" or "//", "/" or "//"); + check_base_dir("///", "/", "/"); + check_base_dir("/usr/", "/", "usr"); + check_base_dir("/usr/lib", "/usr", "lib"); + check_base_dir("//usr//lib//", "//usr", "lib"); + check_base_dir("/home//dwc//test", "/home//dwc", "test"); +} diff --git a/tests/mksock.c b/tests/mksock.c index d1776b3..5068bc8 100644 --- a/tests/mksock.c +++ b/tests/mksock.c @@ -19,8 +19,8 @@ * program does the job. */ +#include "../src/bfstd.h" #include -#include #include #include #include @@ -41,18 +41,13 @@ static void errmsg(const char *cmd, const char *path) { * file name is not. */ static int chdir_parent(const char *path) { - char *copy = strdup(path); - if (!copy) { + char *dir = xdirname(path); + if (!dir) { return -1; } - const char *dir = dirname(copy); int ret = chdir(dir); - - int error = errno; - free(copy); - errno = error; - + free(dir); return ret; } @@ -66,22 +61,21 @@ static int init_sun(struct sockaddr_un *sock, const char *path) { return -1; } - char *copy = strdup(path); - if (!copy) { + char *base = xbasename(path); + if (!base) { return -1; } - const char *base = basename(copy); len = strlen(base); if (len >= sizeof(sock->sun_path)) { - free(copy); + free(base); errno = ENAMETOOLONG; return -1; } sock->sun_family = AF_UNIX; memcpy(sock->sun_path, base, len + 1); - free(copy); + free(base); return 0; } diff --git a/tests/xtouch.c b/tests/xtouch.c index 9a91ec7..262fc33 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -14,7 +14,6 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ -#include "../src/bfstd.h" #include "../src/xtime.h" #include #include -- cgit v1.2.3 From ca00b621eba1bf8b2a591b943090ac8af605922d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Jan 2023 14:49:32 -0500 Subject: tests/xtouch: Fix macOS build --- tests/xtouch.c | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/xtouch.c b/tests/xtouch.c index 262fc33..9a91ec7 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -14,6 +14,7 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * ****************************************************************************/ +#include "../src/bfstd.h" #include "../src/xtime.h" #include #include -- cgit v1.2.3 From d2f3a10f4373610e912e586814e26e2a06fd7d88 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 22 Jan 2023 14:20:51 -0500 Subject: bfstd: Add O_SEARCH/O_EXEC fallbacks --- src/bfstd.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/bfstd.h b/src/bfstd.h index d46fa02..79307cc 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -33,6 +33,22 @@ bool is_nonexistence_error(int error); #include +#ifndef O_EXEC +# ifdef O_PATH +# define O_EXEC O_PATH +# else +# define O_EXEC O_RDONLY +# endif +#endif + +#ifndef O_SEARCH +# ifdef O_PATH +# define O_SEARCH O_PATH +# else +# define O_SEARCH O_RDONLY +# endif +#endif + #ifndef O_DIRECTORY # define O_DIRECTORY 0 #endif -- cgit v1.2.3 From 869e4010433c8610ba59f9a6a310df8be228d718 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 22 Jan 2023 14:21:36 -0500 Subject: mtab: Mitigate the race between bfs_mtab_parse() and bfs_mtab_fill_types() Fixes #97. --- src/mtab.c | 37 +++++++++++++++++++++++++++++++++++-- tests/gnu/fstype_umount.out | 0 tests/gnu/fstype_umount.sh | 12 ++++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 tests/gnu/fstype_umount.out create mode 100644 tests/gnu/fstype_umount.sh diff --git a/src/mtab.c b/src/mtab.c index 39676e5..35ae51d 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -193,18 +193,51 @@ fail: } static void bfs_mtab_fill_types(struct bfs_mtab *mtab) { + const enum bfs_stat_flags flags = BFS_STAT_NOFOLLOW | BFS_STAT_NOSYNC; + for (size_t i = 0; i < darray_length(mtab->entries); ++i) { struct bfs_mtab_entry *entry = &mtab->entries[i]; + // It's possible that /path/to/mount was unmounted between bfs_mtab_parse() and bfs_mtab_fill_types(). + // In that case, the dev_t of /path/to/mount will be the same as /path/to, which should not get its + // fstype from the old mount record of /path/to/mount. + int fd = -1; + const char *path = entry->path; + char *dir = xdirname(path); + if (dir) { + fd = open(dir, O_SEARCH | O_CLOEXEC | O_DIRECTORY); + } + if (fd >= 0) { + path += xbaseoff(path); + } else { + fd = AT_FDCWD; + } + struct bfs_stat sb; - if (bfs_stat(AT_FDCWD, entry->path, BFS_STAT_NOFOLLOW | BFS_STAT_NOSYNC, &sb) != 0) { - continue; + if (bfs_stat(fd, path, flags, &sb) != 0) { + goto next; + } + + if (fd >= 0) { + struct bfs_stat parent; + if (bfs_stat(fd, NULL, flags, &parent) == 0) { + if (parent.dev == sb.dev) { + // Not a mount point any more (or a bind mount, but with the same fstype) + goto next; + } + } } struct trie_leaf *leaf = trie_insert_mem(&mtab->types, &sb.dev, sizeof(sb.dev)); if (leaf) { leaf->value = entry->type; } + + next: + free(dir); + if (fd >= 0) { + xclose(fd); + } } mtab->types_filled = true; diff --git a/tests/gnu/fstype_umount.out b/tests/gnu/fstype_umount.out new file mode 100644 index 0000000..e69de29 diff --git a/tests/gnu/fstype_umount.sh b/tests/gnu/fstype_umount.sh new file mode 100644 index 0000000..e817831 --- /dev/null +++ b/tests/gnu/fstype_umount.sh @@ -0,0 +1,12 @@ +test "$UNAME" = "Linux" || skip + +clean_scratch + +mkdir scratch/tmp +bfs_sudo mount -t tmpfs tmpfs scratch/tmp || skip +trap "bfs_sudo umount -R scratch/tmp" EXIT + +mkdir scratch/tmp/ram +bfs_sudo mount -t ramfs ramfs scratch/tmp/ram || skip + +bfs_diff scratch/tmp -path scratch/tmp -exec "${SUDO[@]}" umount scratch/tmp/ram \; , -fstype ramfs -print -- cgit v1.2.3 From fcbd5d315ec71e1890a2d8a0d7e4107bfbb7bca6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 24 Jan 2023 16:41:34 -0500 Subject: opt: Use a table to look up optimizer functions --- src/opt.c | 115 ++++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 71 insertions(+), 44 deletions(-) diff --git a/src/opt.c b/src/opt.c index 3250331..6ec3414 100644 --- a/src/opt.c +++ b/src/opt.c @@ -746,19 +746,6 @@ static void infer_pred_facts(struct opt_state *state, enum pred_type pred) { constrain_pred(&state->facts_when_false.preds[pred], false); } -/** Infer data flow facts about an -{execut,read,writ}able expression. */ -static void infer_access_facts(struct opt_state *state, const struct bfs_expr *expr) { - if (expr->num & R_OK) { - infer_pred_facts(state, READABLE_PRED); - } - if (expr->num & W_OK) { - infer_pred_facts(state, WRITABLE_PRED); - } - if (expr->num & X_OK) { - infer_pred_facts(state, EXECUTABLE_PRED); - } -} - /** Infer data flow facts about an icmp-style ([+-]N) expression. */ static void infer_icmp_facts(struct opt_state *state, const struct bfs_expr *expr, enum range_type type) { struct range *range_when_true = &state->facts_when_true.ranges[type]; @@ -786,8 +773,22 @@ static void infer_icmp_facts(struct opt_state *state, const struct bfs_expr *exp } } -/** Infer data flow facts about a -gid expression. */ -static void infer_gid_facts(struct opt_state *state, const struct bfs_expr *expr) { +/** Optimize -{execut,read,writ}able. */ +static struct bfs_expr *optimize_access(struct opt_state *state, struct bfs_expr *expr) { + if (expr->num & R_OK) { + infer_pred_facts(state, READABLE_PRED); + } + if (expr->num & W_OK) { + infer_pred_facts(state, WRITABLE_PRED); + } + if (expr->num & X_OK) { + infer_pred_facts(state, EXECUTABLE_PRED); + } + return expr; +} + +/** Optimize -gid. */ +static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *expr) { infer_icmp_facts(state, expr, GID_RANGE); struct range *range = &state->facts_when_true.ranges[GID_RANGE]; @@ -798,10 +799,12 @@ static void infer_gid_facts(struct opt_state *state, const struct bfs_expr *expr constrain_pred(&state->facts_when_true.preds[NOGROUP_PRED], nogroup); } } + + return expr; } -/** Infer data flow facts about a -uid expression. */ -static void infer_uid_facts(struct opt_state *state, const struct bfs_expr *expr) { +/** Optimize -uid. */ +static struct bfs_expr *optimize_uid(struct opt_state *state, struct bfs_expr *expr) { infer_icmp_facts(state, expr, UID_RANGE); struct range *range = &state->facts_when_true.ranges[UID_RANGE]; @@ -812,25 +815,68 @@ static void infer_uid_facts(struct opt_state *state, const struct bfs_expr *expr constrain_pred(&state->facts_when_true.preds[NOUSER_PRED], nouser); } } + + return expr; } -/** Infer data flow facts about a -samefile expression. */ -static void infer_samefile_facts(struct opt_state *state, const struct bfs_expr *expr) { +/** Optimize -samefile. */ +static struct bfs_expr *optimize_samefile(struct opt_state *state, struct bfs_expr *expr) { struct range *range_when_true = &state->facts_when_true.ranges[INUM_RANGE]; constrain_min(range_when_true, expr->ino); constrain_max(range_when_true, expr->ino); + return expr; } -/** Infer data flow facts about a -type expression. */ -static void infer_type_facts(struct opt_state *state, const struct bfs_expr *expr) { +/** Optimize -type. */ +static struct bfs_expr *optimize_type(struct opt_state *state, struct bfs_expr *expr) { state->facts_when_true.types &= expr->num; state->facts_when_false.types &= ~expr->num; + return expr; } -/** Infer data flow facts about an -xtype expression. */ -static void infer_xtype_facts(struct opt_state *state, const struct bfs_expr *expr) { +/** Optimize -xtype. */ +static struct bfs_expr *optimize_xtype(struct opt_state *state, struct bfs_expr *expr) { state->facts_when_true.xtypes &= expr->num; state->facts_when_false.xtypes &= ~expr->num; + return expr; +} + +/** Signature for custom optimizer functions. */ +typedef struct bfs_expr *bfs_opt_fn(struct opt_state *state, struct bfs_expr *expr); + +/** Table of custom optimizer functions. */ +static const struct { + /** The evaluation function this optimizer applies to. */ + bfs_eval_fn *eval_fn; + /** The corresponding optimizer function. */ + bfs_opt_fn *opt_fn; +} opt_fns[] = { + // Primaries + {eval_access, optimize_access}, + {eval_gid, optimize_gid}, + {eval_samefile, optimize_samefile}, + {eval_type, optimize_type}, + {eval_uid, optimize_uid}, + {eval_xtype, optimize_xtype}, + + // Operators + {eval_and, optimize_and_expr_recursive}, + {eval_comma, optimize_comma_expr_recursive}, + {eval_not, optimize_not_expr_recursive}, + {eval_or, optimize_or_expr_recursive}, +}; + +/** + * Look up the appropriate optimizer for an expression and call it. + */ +static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs_expr *expr) { + for (size_t i = 0; i < BFS_COUNTOF(opt_fns); ++i) { + if (opt_fns[i].eval_fn == expr->eval_fn) { + return opt_fns[i].opt_fn(state, expr); + } + } + + return expr; } static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct bfs_expr *expr) { @@ -851,9 +897,7 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); } - if (expr->eval_fn == eval_access) { - infer_access_facts(state, expr); - } else if (expr->eval_fn == eval_acl) { + if (expr->eval_fn == eval_acl) { infer_pred_facts(state, ACL_PRED); } else if (expr->eval_fn == eval_capable) { infer_pred_facts(state, CAPABLE_PRED); @@ -861,8 +905,6 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct infer_icmp_facts(state, expr, DEPTH_RANGE); } else if (expr->eval_fn == eval_empty) { infer_pred_facts(state, EMPTY_PRED); - } else if (expr->eval_fn == eval_gid) { - infer_gid_facts(state, expr); } else if (expr->eval_fn == eval_hidden) { infer_pred_facts(state, HIDDEN_PRED); } else if (expr->eval_fn == eval_inum) { @@ -873,30 +915,15 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct infer_pred_facts(state, NOGROUP_PRED); } else if (expr->eval_fn == eval_nouser) { infer_pred_facts(state, NOUSER_PRED); - } else if (expr->eval_fn == eval_samefile) { - infer_samefile_facts(state, expr); } else if (expr->eval_fn == eval_size) { infer_icmp_facts(state, expr, SIZE_RANGE); } else if (expr->eval_fn == eval_sparse) { infer_pred_facts(state, SPARSE_PRED); - } else if (expr->eval_fn == eval_type) { - infer_type_facts(state, expr); - } else if (expr->eval_fn == eval_uid) { - infer_uid_facts(state, expr); } else if (expr->eval_fn == eval_xattr) { infer_pred_facts(state, XATTR_PRED); - } else if (expr->eval_fn == eval_xtype) { - infer_xtype_facts(state, expr); - } else if (expr->eval_fn == eval_not) { - expr = optimize_not_expr_recursive(state, expr); - } else if (expr->eval_fn == eval_and) { - expr = optimize_and_expr_recursive(state, expr); - } else if (expr->eval_fn == eval_or) { - expr = optimize_or_expr_recursive(state, expr); - } else if (expr->eval_fn == eval_comma) { - expr = optimize_comma_expr_recursive(state, expr); } + expr = optimize_expr_lookup(state, expr); if (!expr) { return NULL; } -- cgit v1.2.3 From 0c0dbcbcdf04e55c2805eb71aecd968ea2ffe1a0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 24 Jan 2023 16:47:28 -0500 Subject: opt: Use a table for simple predicates --- src/opt.c | 44 +++++++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 17 deletions(-) diff --git a/src/opt.c b/src/opt.c index 6ec3414..a33ffea 100644 --- a/src/opt.c +++ b/src/opt.c @@ -841,6 +841,25 @@ static struct bfs_expr *optimize_xtype(struct opt_state *state, struct bfs_expr return expr; } +/** + * Table of simple predicates. + */ +static const struct { + /** The evaluation function this optimizer applies to. */ + bfs_eval_fn *eval_fn; + /** The corresponding predicate. */ + enum pred_type pred; +} opt_preds[] = { + {eval_acl, ACL_PRED}, + {eval_capable, CAPABLE_PRED}, + {eval_empty, EMPTY_PRED}, + {eval_hidden, HIDDEN_PRED}, + {eval_nogroup, NOGROUP_PRED}, + {eval_nouser, NOUSER_PRED}, + {eval_sparse, SPARSE_PRED}, + {eval_xattr, XATTR_PRED}, +}; + /** Signature for custom optimizer functions. */ typedef struct bfs_expr *bfs_opt_fn(struct opt_state *state, struct bfs_expr *expr); @@ -870,6 +889,13 @@ static const struct { * Look up the appropriate optimizer for an expression and call it. */ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs_expr *expr) { + for (size_t i = 0; i < BFS_COUNTOF(opt_preds); ++i) { + if (opt_preds[i].eval_fn == expr->eval_fn) { + infer_pred_facts(state, opt_preds[i].pred); + break; + } + } + for (size_t i = 0; i < BFS_COUNTOF(opt_fns); ++i) { if (opt_fns[i].eval_fn == expr->eval_fn) { return opt_fns[i].opt_fn(state, expr); @@ -897,30 +923,14 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); } - if (expr->eval_fn == eval_acl) { - infer_pred_facts(state, ACL_PRED); - } else if (expr->eval_fn == eval_capable) { - infer_pred_facts(state, CAPABLE_PRED); - } else if (expr->eval_fn == eval_depth) { + if (expr->eval_fn == eval_depth) { infer_icmp_facts(state, expr, DEPTH_RANGE); - } else if (expr->eval_fn == eval_empty) { - infer_pred_facts(state, EMPTY_PRED); - } else if (expr->eval_fn == eval_hidden) { - infer_pred_facts(state, HIDDEN_PRED); } else if (expr->eval_fn == eval_inum) { infer_icmp_facts(state, expr, INUM_RANGE); } else if (expr->eval_fn == eval_links) { infer_icmp_facts(state, expr, LINKS_RANGE); - } else if (expr->eval_fn == eval_nogroup) { - infer_pred_facts(state, NOGROUP_PRED); - } else if (expr->eval_fn == eval_nouser) { - infer_pred_facts(state, NOUSER_PRED); } else if (expr->eval_fn == eval_size) { infer_icmp_facts(state, expr, SIZE_RANGE); - } else if (expr->eval_fn == eval_sparse) { - infer_pred_facts(state, SPARSE_PRED); - } else if (expr->eval_fn == eval_xattr) { - infer_pred_facts(state, XATTR_PRED); } expr = optimize_expr_lookup(state, expr); -- cgit v1.2.3 From b922add3a5597020a0ad5357b5fc390b3fae709f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 24 Jan 2023 16:51:33 -0500 Subject: opt: Use a table for simple range comparisons --- src/opt.c | 38 ++++++++++++++++++++++++-------------- 1 file changed, 24 insertions(+), 14 deletions(-) diff --git a/src/opt.c b/src/opt.c index a33ffea..50fcd20 100644 --- a/src/opt.c +++ b/src/opt.c @@ -789,8 +789,6 @@ static struct bfs_expr *optimize_access(struct opt_state *state, struct bfs_expr /** Optimize -gid. */ static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *expr) { - infer_icmp_facts(state, expr, GID_RANGE); - struct range *range = &state->facts_when_true.ranges[GID_RANGE]; if (range->min == range->max) { gid_t gid = range->min; @@ -805,8 +803,6 @@ static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *e /** Optimize -uid. */ static struct bfs_expr *optimize_uid(struct opt_state *state, struct bfs_expr *expr) { - infer_icmp_facts(state, expr, UID_RANGE); - struct range *range = &state->facts_when_true.ranges[UID_RANGE]; if (range->min == range->max) { uid_t uid = range->min; @@ -860,6 +856,23 @@ static const struct { {eval_xattr, XATTR_PRED}, }; +/** + * Table of simple range comparisons. + */ +static const struct { + /** The evaluation function this optimizer applies to. */ + bfs_eval_fn *eval_fn; + /** The corresponding range. */ + enum range_type range; +} opt_ranges[] = { + {eval_depth, DEPTH_RANGE}, + {eval_gid, GID_RANGE}, + {eval_inum, INUM_RANGE}, + {eval_links, LINKS_RANGE}, + {eval_size, SIZE_RANGE}, + {eval_uid, UID_RANGE}, +}; + /** Signature for custom optimizer functions. */ typedef struct bfs_expr *bfs_opt_fn(struct opt_state *state, struct bfs_expr *expr); @@ -896,6 +909,13 @@ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs } } + for (size_t i = 0; i < BFS_COUNTOF(opt_ranges); ++i) { + if (opt_ranges[i].eval_fn == expr->eval_fn) { + infer_icmp_facts(state, expr, opt_ranges[i].range); + break; + } + } + for (size_t i = 0; i < BFS_COUNTOF(opt_fns); ++i) { if (opt_fns[i].eval_fn == expr->eval_fn) { return opt_fns[i].opt_fn(state, expr); @@ -923,16 +943,6 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); } - if (expr->eval_fn == eval_depth) { - infer_icmp_facts(state, expr, DEPTH_RANGE); - } else if (expr->eval_fn == eval_inum) { - infer_icmp_facts(state, expr, INUM_RANGE); - } else if (expr->eval_fn == eval_links) { - infer_icmp_facts(state, expr, LINKS_RANGE); - } else if (expr->eval_fn == eval_size) { - infer_icmp_facts(state, expr, SIZE_RANGE); - } - expr = optimize_expr_lookup(state, expr); if (!expr) { return NULL; -- cgit v1.2.3 From 693b5f60dc9787d9237920cc0c87fe0e010194ee Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 24 Jan 2023 16:59:49 -0500 Subject: opt: Move purity out of the parser --- src/opt.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.c | 27 ++------------------------- 2 files changed, 64 insertions(+), 25 deletions(-) diff --git a/src/opt.c b/src/opt.c index 50fcd20..934a4b4 100644 --- a/src/opt.c +++ b/src/opt.c @@ -787,6 +787,18 @@ static struct bfs_expr *optimize_access(struct opt_state *state, struct bfs_expr return expr; } +/** Optimize -empty. */ +static struct bfs_expr *optimize_empty(struct opt_state *state, struct bfs_expr *expr) { + if (state->ctx->optlevel >= 4) { + // Since -empty attempts to open and read directories, it may + // have side effects such as reporting permission errors, and + // thus shouldn't be re-ordered without aggressive optimizations + expr->pure = true; + } + + return expr; +} + /** Optimize -gid. */ static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *expr) { struct range *range = &state->facts_when_true.ranges[GID_RANGE]; @@ -832,11 +844,53 @@ static struct bfs_expr *optimize_type(struct opt_state *state, struct bfs_expr * /** Optimize -xtype. */ static struct bfs_expr *optimize_xtype(struct opt_state *state, struct bfs_expr *expr) { + if (state->ctx->optlevel >= 4) { + // Since -xtype dereferences symbolic links, it may have side + // effects such as reporting permission errors, and thus + // shouldn't be re-ordered without aggressive optimizations + expr->pure = true; + } + state->facts_when_true.xtypes &= expr->num; state->facts_when_false.xtypes &= ~expr->num; return expr; } +/** + * Table of pure expressions. + */ +static bfs_eval_fn *const opt_pure[] = { + eval_access, + eval_acl, + eval_capable, + eval_depth, + eval_false, + eval_flags, + eval_fstype, + eval_gid, + eval_hidden, + eval_inum, + eval_links, + eval_lname, + eval_name, + eval_newer, + eval_nogroup, + eval_nouser, + eval_path, + eval_perm, + eval_regex, + eval_samefile, + eval_size, + eval_sparse, + eval_time, + eval_true, + eval_type, + eval_uid, + eval_used, + eval_xattr, + eval_xattrname, +}; + /** * Table of simple predicates. */ @@ -885,6 +939,7 @@ static const struct { } opt_fns[] = { // Primaries {eval_access, optimize_access}, + {eval_empty, optimize_empty}, {eval_gid, optimize_gid}, {eval_samefile, optimize_samefile}, {eval_type, optimize_type}, @@ -902,6 +957,13 @@ static const struct { * Look up the appropriate optimizer for an expression and call it. */ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs_expr *expr) { + for (size_t i = 0; i < BFS_COUNTOF(opt_pure); ++i) { + if (opt_pure[i] == expr->eval_fn) { + expr->pure = true; + break; + } + } + for (size_t i = 0; i < BFS_COUNTOF(opt_preds); ++i) { if (opt_preds[i].eval_fn == expr->eval_fn) { infer_pred_facts(state, opt_preds[i].pred); diff --git a/src/parse.c b/src/parse.c index 51a9d0a..0b4224c 100644 --- a/src/parse.c +++ b/src/parse.c @@ -85,6 +85,7 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { expr->argv = argv; expr->persistent_fds = 0; expr->ephemeral_fds = 0; + expr->pure = false; expr->cost = FAST_COST; expr->evaluations = 0; expr->successes = 0; @@ -92,17 +93,14 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { expr->elapsed.tv_nsec = 0; if (eval_fn == eval_true) { - expr->pure = true; expr->always_true = true; expr->always_false = false; expr->probability = 1.0; } else if (eval_fn == eval_false) { - expr->pure = true; expr->always_true = false; expr->always_false = true; expr->probability = 0.0; } else { - expr->pure = false; expr->always_true = false; expr->always_false = false; expr->probability = 0.5; @@ -814,11 +812,7 @@ static struct bfs_expr *parse_unary_option(struct parser_state *state) { */ static struct bfs_expr *parse_test(struct parser_state *state, bfs_eval_fn *eval_fn, size_t argc) { char **argv = parser_advance(state, T_TEST, argc); - struct bfs_expr *expr = bfs_expr_new(eval_fn, argc, argv); - if (expr) { - expr->pure = true; - } - return expr; + return bfs_expr_new(eval_fn, argc, argv); } /** @@ -1344,16 +1338,7 @@ static struct bfs_expr *parse_empty(struct parser_state *state, int arg1, int ar expr->cost = 2000.0; expr->probability = 0.01; - - if (state->ctx->optlevel < 4) { - // Since -empty attempts to open and read directories, it may - // have side effects such as reporting permission errors, and - // thus shouldn't be re-ordered without aggressive optimizations - expr->pure = false; - } - expr->ephemeral_fds = 1; - return expr; } @@ -2030,7 +2015,6 @@ static struct bfs_expr *parse_nohidden(struct parser_state *state, int arg1, int } hidden->probability = 0.01; - hidden->pure = true; if (parse_exclude(state, hidden) != 0) { return NULL; @@ -2766,13 +2750,6 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) expr->num = types; expr->probability = probability; - if (x && state->ctx->optlevel < 4) { - // Since -xtype dereferences symbolic links, it may have side - // effects such as reporting permission errors, and thus - // shouldn't be re-ordered without aggressive optimizations - expr->pure = false; - } - return expr; fail: -- cgit v1.2.3 From 60b51a3bda3f3fc220a2fa56de5f86c4bac35f2e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 24 Jan 2023 17:08:58 -0500 Subject: opt: Move always_{true,false} out of the parser --- src/opt.c | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.c | 56 ++++++-------------------------------------------------- 2 files changed, 61 insertions(+), 50 deletions(-) diff --git a/src/opt.c b/src/opt.c index 934a4b4..c80d584 100644 --- a/src/opt.c +++ b/src/opt.c @@ -45,6 +45,7 @@ #include "ctx.h" #include "diag.h" #include "eval.h" +#include "exec.h" #include "expr.h" #include "pwcache.h" #include @@ -799,6 +800,15 @@ static struct bfs_expr *optimize_empty(struct opt_state *state, struct bfs_expr return expr; } +/** Optimize -{exec,ok}{,dir}. */ +static struct bfs_expr *optimize_exec(struct opt_state *state, struct bfs_expr *expr) { + if (expr->exec->flags & BFS_EXEC_MULTI) { + expr->always_true = true; + } + + return expr; +} + /** Optimize -gid. */ static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *expr) { struct range *range = &state->facts_when_true.ranges[GID_RANGE]; @@ -891,6 +901,34 @@ static bfs_eval_fn *const opt_pure[] = { eval_xattrname, }; +/** + * Table of always-true expressions. + */ +static bfs_eval_fn *const opt_always_true[] = { + eval_fls, + eval_fprint, + eval_fprint0, + eval_fprintf, + eval_fprintx, + eval_prune, + eval_true, + + // Non-returning + eval_exit, + eval_quit, +}; + +/** + * Table of always-false expressions. + */ +static bfs_eval_fn *const opt_always_false[] = { + eval_false, + + // Non-returning + eval_exit, + eval_quit, +}; + /** * Table of simple predicates. */ @@ -940,6 +978,7 @@ static const struct { // Primaries {eval_access, optimize_access}, {eval_empty, optimize_empty}, + {eval_exec, optimize_exec}, {eval_gid, optimize_gid}, {eval_samefile, optimize_samefile}, {eval_type, optimize_type}, @@ -964,6 +1003,20 @@ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs } } + for (size_t i = 0; i < BFS_COUNTOF(opt_always_true); ++i) { + if (opt_always_true[i] == expr->eval_fn) { + expr->always_true = true; + break; + } + } + + for (size_t i = 0; i < BFS_COUNTOF(opt_always_false); ++i) { + if (opt_always_false[i] == expr->eval_fn) { + expr->always_false = true; + break; + } + } + for (size_t i = 0; i < BFS_COUNTOF(opt_preds); ++i) { if (opt_preds[i].eval_fn == expr->eval_fn) { infer_pred_facts(state, opt_preds[i].pred); @@ -1026,9 +1079,11 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct } if (expr->always_true) { + expr->probability = 1.0; set_facts_impossible(&state->facts_when_false); } if (expr->always_false) { + expr->probability = 0.0; set_facts_impossible(&state->facts_when_true); } diff --git a/src/parse.c b/src/parse.c index 0b4224c..5766237 100644 --- a/src/parse.c +++ b/src/parse.c @@ -86,26 +86,15 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { expr->persistent_fds = 0; expr->ephemeral_fds = 0; expr->pure = false; + expr->always_true = false; + expr->always_false = false; expr->cost = FAST_COST; + expr->probability = 0.5; expr->evaluations = 0; expr->successes = 0; expr->elapsed.tv_sec = 0; expr->elapsed.tv_nsec = 0; - if (eval_fn == eval_true) { - expr->always_true = true; - expr->always_false = false; - expr->probability = 1.0; - } else if (eval_fn == eval_false) { - expr->always_true = false; - expr->always_false = true; - expr->probability = 0.0; - } else { - expr->always_true = false; - expr->always_false = false; - expr->probability = 0.5; - } - // Prevent bfs_expr_free() from freeing uninitialized pointers on error paths if (bfs_expr_is_parent(expr)) { expr->lhs = NULL; @@ -196,21 +185,6 @@ static struct bfs_expr *new_binary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *l return expr; } -/** - * Set an expression to always return true. - */ -static void expr_set_always_true(struct bfs_expr *expr) { - expr->always_true = true; - expr->probability = 1.0; -} - -/** - * Set an expression to never return. - */ -static void expr_set_never_returns(struct bfs_expr *expr) { - expr->always_true = expr->always_false = true; -} - /** * Color use flags. */ @@ -462,7 +436,6 @@ static bool parse_expr_warning(const struct parser_state *state, const struct bf * Fill in a "-print"-type expression. */ static void init_print_expr(struct parser_state *state, struct bfs_expr *expr) { - expr_set_always_true(expr); expr->cost = PRINT_COST; expr->cfile = state->ctx->cout; expr->path = NULL; @@ -1359,9 +1332,7 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar expr->exec = execbuf; - if (execbuf->flags & BFS_EXEC_MULTI) { - expr_set_always_true(expr); - } else { + if (!(execbuf->flags & BFS_EXEC_MULTI)) { expr->cost = 1000000.0; } @@ -1395,7 +1366,6 @@ static struct bfs_expr *parse_exit(struct parser_state *state, int arg1, int arg struct bfs_expr *expr = parse_action(state, eval_exit, argc); if (expr) { - expr_set_never_returns(expr); expr->num = status; } return expr; @@ -1525,7 +1495,6 @@ static struct bfs_expr *parse_fls(struct parser_state *state, int arg1, int arg2 goto fail; } - expr_set_always_true(expr); expr->cost = PRINT_COST; return expr; @@ -1540,7 +1509,6 @@ fail: static struct bfs_expr *parse_fprint(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(state, eval_fprint); if (expr) { - expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, expr->argv[1]) != 0) { goto fail; @@ -1559,7 +1527,6 @@ fail: static struct bfs_expr *parse_fprint0(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(state, eval_fprint0); if (expr) { - expr_set_always_true(expr); expr->cost = PRINT_COST; if (expr_open(state, expr, expr->argv[1]) != 0) { goto fail; @@ -1595,8 +1562,6 @@ static struct bfs_expr *parse_fprintf(struct parser_state *state, int arg1, int return NULL; } - expr_set_always_true(expr); - expr->cost = PRINT_COST; if (expr_open(state, expr, file) != 0) { @@ -2373,23 +2338,14 @@ static struct bfs_expr *parse_printx(struct parser_state *state, int arg1, int a */ static struct bfs_expr *parse_prune(struct parser_state *state, int arg1, int arg2) { state->prune_arg = state->argv; - - struct bfs_expr *expr = parse_nullary_action(state, eval_prune); - if (expr) { - expr_set_always_true(expr); - } - return expr; + return parse_nullary_action(state, eval_prune); } /** * Parse -quit. */ static struct bfs_expr *parse_quit(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_quit); - if (expr) { - expr_set_never_returns(expr); - } - return expr; + return parse_nullary_action(state, eval_quit); } /** -- cgit v1.2.3 From cb3805257092cea2fa6d1fce8d2f99f6b01f44ed Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 24 Jan 2023 17:17:06 -0500 Subject: opt: Move costs out of the parser --- src/opt.c | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.c | 54 +++--------------------------------------------------- 2 files changed, 57 insertions(+), 51 deletions(-) diff --git a/src/opt.c b/src/opt.c index c80d584..1daf390 100644 --- a/src/opt.c +++ b/src/opt.c @@ -804,6 +804,8 @@ static struct bfs_expr *optimize_empty(struct opt_state *state, struct bfs_expr static struct bfs_expr *optimize_exec(struct opt_state *state, struct bfs_expr *expr) { if (expr->exec->flags & BFS_EXEC_MULTI) { expr->always_true = true; + } else { + expr->cost = 1000000.0; } return expr; @@ -929,6 +931,50 @@ static bfs_eval_fn *const opt_always_false[] = { eval_quit, }; +#define FAST_COST 40.0 +#define FNMATCH_COST 400.0 +#define STAT_COST 1000.0 +#define PRINT_COST 20000.0 + +/** + * Table of expression costs. + */ +static const struct { + /** The evaluation function with this cost. */ + bfs_eval_fn *eval_fn; + /** The matching cost. */ + float cost; +} opt_costs[] = { + {eval_access, STAT_COST}, + {eval_acl, STAT_COST}, + {eval_capable, STAT_COST}, + {eval_empty, 2 * STAT_COST}, // readdir() is worse than stat() + {eval_fls, PRINT_COST}, + {eval_fprint, PRINT_COST}, + {eval_fprint0, PRINT_COST}, + {eval_fprintf, PRINT_COST}, + {eval_fprintx, PRINT_COST}, + {eval_fstype, STAT_COST}, + {eval_gid, STAT_COST}, + {eval_inum, STAT_COST}, + {eval_links, STAT_COST}, + {eval_lname, FNMATCH_COST}, + {eval_name, FNMATCH_COST}, + {eval_newer, STAT_COST}, + {eval_nogroup, STAT_COST}, + {eval_nouser, STAT_COST}, + {eval_path, FNMATCH_COST}, + {eval_perm, STAT_COST}, + {eval_samefile, STAT_COST}, + {eval_size, STAT_COST}, + {eval_sparse, STAT_COST}, + {eval_time, STAT_COST}, + {eval_uid, STAT_COST}, + {eval_used, STAT_COST}, + {eval_xattr, STAT_COST}, + {eval_xattrname, STAT_COST}, +}; + /** * Table of simple predicates. */ @@ -1017,6 +1063,14 @@ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs } } + expr->cost = FAST_COST; + for (size_t i = 0; i < BFS_COUNTOF(opt_costs); ++i) { + if (opt_costs[i].eval_fn == expr->eval_fn) { + expr->cost = opt_costs[i].cost; + break; + } + } + for (size_t i = 0; i < BFS_COUNTOF(opt_preds); ++i) { if (opt_preds[i].eval_fn == expr->eval_fn) { infer_pred_facts(state, opt_preds[i].pred); diff --git a/src/parse.c b/src/parse.c index 5766237..eee185b 100644 --- a/src/parse.c +++ b/src/parse.c @@ -68,11 +68,6 @@ static char *fake_or_arg = "-o"; static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; -// Cost estimation constants -#define FAST_COST 40.0 -#define STAT_COST 1000.0 -#define PRINT_COST 20000.0 - struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { struct bfs_expr *expr = malloc(sizeof(*expr)); if (!expr) { @@ -88,7 +83,7 @@ struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { expr->pure = false; expr->always_true = false; expr->always_false = false; - expr->cost = FAST_COST; + expr->cost = 0.0; expr->probability = 0.5; expr->evaluations = 0; expr->successes = 0; @@ -436,7 +431,6 @@ static bool parse_expr_warning(const struct parser_state *state, const struct bf * Fill in a "-print"-type expression. */ static void init_print_expr(struct parser_state *state, struct bfs_expr *expr) { - expr->cost = PRINT_COST; expr->cfile = state->ctx->cout; expr->path = NULL; } @@ -1021,7 +1015,6 @@ static struct bfs_expr *parse_access(struct parser_state *state, int flag, int a } expr->num = flag; - expr->cost = STAT_COST; switch (flag) { case R_OK: @@ -1045,7 +1038,6 @@ static struct bfs_expr *parse_acl(struct parser_state *state, int flag, int arg2 #if BFS_CAN_CHECK_ACL struct bfs_expr *expr = parse_nullary_test(state, eval_acl); if (expr) { - expr->cost = STAT_COST; expr->probability = 0.00002; } return expr; @@ -1069,7 +1061,6 @@ static struct bfs_expr *parse_newer(struct parser_state *state, int field, int a goto fail; } - expr->cost = STAT_COST; expr->reftime = sb.mtime; expr->stat_field = field; return expr; @@ -1088,7 +1079,6 @@ static struct bfs_expr *parse_min(struct parser_state *state, int field, int arg return NULL; } - expr->cost = STAT_COST; expr->reftime = state->now; expr->stat_field = field; expr->time_unit = BFS_MINUTES; @@ -1104,7 +1094,6 @@ static struct bfs_expr *parse_time(struct parser_state *state, int field, int ar return NULL; } - expr->cost = STAT_COST; expr->reftime = state->now; expr->stat_field = field; @@ -1173,7 +1162,6 @@ static struct bfs_expr *parse_capable(struct parser_state *state, int flag, int #if BFS_CAN_CHECK_CAPABILITIES struct bfs_expr *expr = parse_nullary_test(state, eval_capable); if (expr) { - expr->cost = STAT_COST; expr->probability = 0.000002; } return expr; @@ -1309,7 +1297,6 @@ static struct bfs_expr *parse_empty(struct parser_state *state, int arg1, int ar return NULL; } - expr->cost = 2000.0; expr->probability = 0.01; expr->ephemeral_fds = 1; return expr; @@ -1332,10 +1319,6 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar expr->exec = execbuf; - if (!(execbuf->flags & BFS_EXEC_MULTI)) { - expr->cost = 1000000.0; - } - expr->ephemeral_fds = 2; if (execbuf->flags & BFS_EXEC_CHDIR) { if (execbuf->flags & BFS_EXEC_MULTI) { @@ -1495,7 +1478,6 @@ static struct bfs_expr *parse_fls(struct parser_state *state, int arg1, int arg2 goto fail; } - expr->cost = PRINT_COST; return expr; fail: @@ -1509,7 +1491,6 @@ fail: static struct bfs_expr *parse_fprint(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(state, eval_fprint); if (expr) { - expr->cost = PRINT_COST; if (expr_open(state, expr, expr->argv[1]) != 0) { goto fail; } @@ -1527,7 +1508,6 @@ fail: static struct bfs_expr *parse_fprint0(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(state, eval_fprint0); if (expr) { - expr->cost = PRINT_COST; if (expr_open(state, expr, expr->argv[1]) != 0) { goto fail; } @@ -1562,8 +1542,6 @@ static struct bfs_expr *parse_fprintf(struct parser_state *state, int arg1, int return NULL; } - expr->cost = PRINT_COST; - if (expr_open(state, expr, file) != 0) { goto fail; } @@ -1594,7 +1572,6 @@ static struct bfs_expr *parse_fstype(struct parser_state *state, int arg1, int a return NULL; } - expr->cost = STAT_COST; return expr; } @@ -1623,7 +1600,6 @@ static struct bfs_expr *parse_group(struct parser_state *state, int arg1, int ar goto fail; } - expr->cost = STAT_COST; return expr; fail: @@ -1643,11 +1619,7 @@ static struct bfs_expr *parse_unique(struct parser_state *state, int arg1, int a * Parse -used N. */ static struct bfs_expr *parse_used(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_test_icmp(state, eval_used); - if (expr) { - expr->cost = STAT_COST; - } - return expr; + return parse_test_icmp(state, eval_used); } /** @@ -1675,7 +1647,6 @@ static struct bfs_expr *parse_user(struct parser_state *state, int arg1, int arg goto fail; } - expr->cost = STAT_COST; return expr; fail: @@ -1708,7 +1679,6 @@ static struct bfs_expr *parse_ignore_races(struct parser_state *state, int ignor static struct bfs_expr *parse_inum(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_test_icmp(state, eval_inum); if (expr) { - expr->cost = STAT_COST; expr->probability = expr->int_cmp == BFS_INT_EQUAL ? 0.01 : 0.50; } return expr; @@ -1720,7 +1690,6 @@ static struct bfs_expr *parse_inum(struct parser_state *state, int arg1, int arg static struct bfs_expr *parse_links(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_test_icmp(state, eval_links); if (expr) { - expr->cost = STAT_COST; expr->probability = bfs_expr_cmp(expr, 1) ? 0.99 : 0.01; } return expr; @@ -1795,8 +1764,6 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b return expr; } - expr->cost = 400.0; - if (strchr(pattern, '*')) { expr->probability = 0.5; } else { @@ -1942,8 +1909,6 @@ static struct bfs_expr *parse_newerxy(struct parser_state *state, int arg1, int expr->reftime = *reftime; } - expr->cost = STAT_COST; - return expr; fail: @@ -1960,7 +1925,6 @@ static struct bfs_expr *parse_nogroup(struct parser_state *state, int arg1, int return NULL; } - expr->cost = STAT_COST; expr->probability = 0.01; // Who knows how many FDs getgrgid_r() needs? Probably at least one for @@ -2006,7 +1970,6 @@ static struct bfs_expr *parse_nouser(struct parser_state *state, int arg1, int a return NULL; } - expr->cost = STAT_COST; expr->probability = 0.01; // Who knows how many FDs getpwuid_r() needs? Probably at least one for @@ -2272,8 +2235,6 @@ static struct bfs_expr *parse_perm(struct parser_state *state, int field, int ar goto fail; } - expr->cost = STAT_COST; - return expr; fail: @@ -2468,7 +2429,6 @@ static struct bfs_expr *parse_samefile(struct parser_state *state, int arg1, int expr->dev = sb.dev; expr->ino = sb.ino; - expr->cost = STAT_COST; expr->probability = 0.01; return expr; @@ -2531,7 +2491,6 @@ static struct bfs_expr *parse_since(struct parser_state *state, int field, int a goto fail; } - expr->cost = STAT_COST; expr->stat_field = field; return expr; @@ -2589,7 +2548,6 @@ static struct bfs_expr *parse_size(struct parser_state *state, int arg1, int arg goto bad_unit; } - expr->cost = STAT_COST; expr->probability = expr->int_cmp == BFS_INT_EQUAL ? 0.01 : 0.50; return expr; @@ -2605,11 +2563,7 @@ fail: * Parse -sparse. */ static struct bfs_expr *parse_sparse(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_sparse); - if (expr) { - expr->cost = STAT_COST; - } - return expr; + return parse_nullary_test(state, eval_sparse); } /** @@ -2728,7 +2682,6 @@ static struct bfs_expr *parse_xattr(struct parser_state *state, int arg1, int ar #if BFS_CAN_CHECK_XATTRS struct bfs_expr *expr = parse_nullary_test(state, eval_xattr); if (expr) { - expr->cost = STAT_COST; expr->probability = 0.01; } return expr; @@ -2745,7 +2698,6 @@ static struct bfs_expr *parse_xattrname(struct parser_state *state, int arg1, in #if BFS_CAN_CHECK_XATTRS struct bfs_expr *expr = parse_unary_test(state, eval_xattrname); if (expr) { - expr->cost = STAT_COST; expr->probability = 0.01; } return expr; -- cgit v1.2.3 From 220fb782da63fd648dedb02a7c43c837792b3d4a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 24 Jan 2023 17:23:18 -0500 Subject: opt: Move probabilities out of the parser --- src/opt.c | 132 +++++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.c | 149 ++++++++++++------------------------------------------------ 2 files changed, 161 insertions(+), 120 deletions(-) diff --git a/src/opt.c b/src/opt.c index 1daf390..e76e216 100644 --- a/src/opt.c +++ b/src/opt.c @@ -54,6 +54,7 @@ #include #include #include +#include #include static char *fake_and_arg = "-a"; @@ -776,15 +777,23 @@ static void infer_icmp_facts(struct opt_state *state, const struct bfs_expr *exp /** Optimize -{execut,read,writ}able. */ static struct bfs_expr *optimize_access(struct opt_state *state, struct bfs_expr *expr) { + expr->probability = 1.0; + if (expr->num & R_OK) { infer_pred_facts(state, READABLE_PRED); + expr->probability *= 0.99; } + if (expr->num & W_OK) { infer_pred_facts(state, WRITABLE_PRED); + expr->probability *= 0.8; } + if (expr->num & X_OK) { infer_pred_facts(state, EXECUTABLE_PRED); + expr->probability *= 0.2; } + return expr; } @@ -811,6 +820,17 @@ static struct bfs_expr *optimize_exec(struct opt_state *state, struct bfs_expr * return expr; } +/** Optimize -name/-lname/-path. */ +static struct bfs_expr *optimize_fnmatch(struct opt_state *state, struct bfs_expr *expr) { + if (strchr(expr->argv[1], '*')) { + expr->probability = 0.5; + } else { + expr->probability = 0.1; + } + + return expr; +} + /** Optimize -gid. */ static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *expr) { struct range *range = &state->facts_when_true.ranges[GID_RANGE]; @@ -825,6 +845,30 @@ static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *e return expr; } +/** Optimize -inum. */ +static struct bfs_expr *optimize_inum(struct opt_state *state, struct bfs_expr *expr) { + struct range *range = &state->facts_when_true.ranges[INUM_RANGE]; + if (range->min == range->max) { + expr->probability = 0.01; + } else { + expr->probability = 0.5; + } + + return expr; +} + +/** Optimize -links. */ +static struct bfs_expr *optimize_links(struct opt_state *state, struct bfs_expr *expr) { + struct range *range = &state->facts_when_true.ranges[LINKS_RANGE]; + if (1 >= range->min && 1 <= range->max) { + expr->probability = 0.99; + } else { + expr->probability = 0.5; + } + + return expr; +} + /** Optimize -uid. */ static struct bfs_expr *optimize_uid(struct opt_state *state, struct bfs_expr *expr) { struct range *range = &state->facts_when_true.ranges[UID_RANGE]; @@ -847,10 +891,59 @@ static struct bfs_expr *optimize_samefile(struct opt_state *state, struct bfs_ex return expr; } +/** Optimize -size. */ +static struct bfs_expr *optimize_size(struct opt_state *state, struct bfs_expr *expr) { + struct range *range = &state->facts_when_true.ranges[SIZE_RANGE]; + if (range->min == range->max) { + expr->probability = 0.01; + } else { + expr->probability = 0.5; + } + + return expr; +} + +/** Estimate probability for -x?type. */ +static void estimate_type_probability(struct bfs_expr *expr) { + unsigned int types = expr->num; + + expr->probability = 0.0; + if (types & (1 << BFS_BLK)) { + expr->probability += 0.00000721183; + } + if (types & (1 << BFS_CHR)) { + expr->probability += 0.0000499855; + } + if (types & (1 << BFS_DIR)) { + expr->probability += 0.114475; + } + if (types & (1 << BFS_DOOR)) { + expr->probability += 0.000001; + } + if (types & (1 << BFS_FIFO)) { + expr->probability += 0.00000248684; + } + if (types & (1 << BFS_REG)) { + expr->probability += 0.859772; + } + if (types & (1 << BFS_LNK)) { + expr->probability += 0.0256816; + } + if (types & (1 << BFS_SOCK)) { + expr->probability += 0.0000116881; + } + if (types & (1 << BFS_WHT)) { + expr->probability += 0.000001; + } +} + /** Optimize -type. */ static struct bfs_expr *optimize_type(struct opt_state *state, struct bfs_expr *expr) { state->facts_when_true.types &= expr->num; state->facts_when_false.types &= ~expr->num; + + estimate_type_probability(expr); + return expr; } @@ -865,6 +958,9 @@ static struct bfs_expr *optimize_xtype(struct opt_state *state, struct bfs_expr state->facts_when_true.xtypes &= expr->num; state->facts_when_false.xtypes &= ~expr->num; + + estimate_type_probability(expr); + return expr; } @@ -975,6 +1071,29 @@ static const struct { {eval_xattrname, STAT_COST}, }; +/** + * Table of expression probabilities. + */ +static const struct { + /** The evaluation function with this cost. */ + bfs_eval_fn *eval_fn; + /** The matching probability. */ + float probability; +} opt_probs[] = { + {eval_acl, 0.00002}, + {eval_capable, 0.000002}, + {eval_empty, 0.01}, + {eval_false, 0.0}, + {eval_hidden, 0.01}, + {eval_nogroup, 0.01}, + {eval_nouser, 0.01}, + {eval_samefile, 0.01}, + {eval_true, 1.0}, + {eval_xattr, 0.01}, + {eval_xattrname, 0.01}, +}; + + /** * Table of simple predicates. */ @@ -1026,7 +1145,13 @@ static const struct { {eval_empty, optimize_empty}, {eval_exec, optimize_exec}, {eval_gid, optimize_gid}, + {eval_inum, optimize_inum}, + {eval_links, optimize_links}, + {eval_lname, optimize_fnmatch}, + {eval_name, optimize_fnmatch}, + {eval_path, optimize_fnmatch}, {eval_samefile, optimize_samefile}, + {eval_size, optimize_size}, {eval_type, optimize_type}, {eval_uid, optimize_uid}, {eval_xtype, optimize_xtype}, @@ -1071,6 +1196,13 @@ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs } } + for (size_t i = 0; i < BFS_COUNTOF(opt_probs); ++i) { + if (opt_probs[i].eval_fn == expr->eval_fn) { + expr->probability = opt_probs[i].probability; + break; + } + } + for (size_t i = 0; i < BFS_COUNTOF(opt_preds); ++i) { if (opt_preds[i].eval_fn == expr->eval_fn) { infer_pred_facts(state, opt_preds[i].pred); diff --git a/src/parse.c b/src/parse.c index eee185b..f2582e0 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1010,24 +1010,9 @@ static struct bfs_expr *parse_xargs_safe(struct parser_state *state, int arg1, i */ static struct bfs_expr *parse_access(struct parser_state *state, int flag, int arg2) { struct bfs_expr *expr = parse_nullary_test(state, eval_access); - if (!expr) { - return NULL; - } - - expr->num = flag; - - switch (flag) { - case R_OK: - expr->probability = 0.99; - break; - case W_OK: - expr->probability = 0.8; - break; - case X_OK: - expr->probability = 0.2; - break; + if (expr) { + expr->num = flag; } - return expr; } @@ -1036,11 +1021,7 @@ static struct bfs_expr *parse_access(struct parser_state *state, int flag, int a */ static struct bfs_expr *parse_acl(struct parser_state *state, int flag, int arg2) { #if BFS_CAN_CHECK_ACL - struct bfs_expr *expr = parse_nullary_test(state, eval_acl); - if (expr) { - expr->probability = 0.00002; - } - return expr; + return parse_nullary_test(state, eval_acl); #else parse_error(state, "Missing platform support.\n"); return NULL; @@ -1160,11 +1141,7 @@ fail: */ static struct bfs_expr *parse_capable(struct parser_state *state, int flag, int arg2) { #if BFS_CAN_CHECK_CAPABILITIES - struct bfs_expr *expr = parse_nullary_test(state, eval_capable); - if (expr) { - expr->probability = 0.000002; - } - return expr; + return parse_nullary_test(state, eval_capable); #else parse_error(state, "Missing platform support.\n"); return NULL; @@ -1293,12 +1270,9 @@ static struct bfs_expr *parse_depth_limit(struct parser_state *state, int is_min */ static struct bfs_expr *parse_empty(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(state, eval_empty); - if (!expr) { - return NULL; + if (expr) { + expr->ephemeral_fds = 1; } - - expr->probability = 0.01; - expr->ephemeral_fds = 1; return expr; } @@ -1658,11 +1632,7 @@ fail: * Parse -hidden. */ static struct bfs_expr *parse_hidden(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_hidden); - if (expr) { - expr->probability = 0.01; - } - return expr; + return parse_nullary_test(state, eval_hidden); } /** @@ -1677,22 +1647,14 @@ static struct bfs_expr *parse_ignore_races(struct parser_state *state, int ignor * Parse -inum N. */ static struct bfs_expr *parse_inum(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_test_icmp(state, eval_inum); - if (expr) { - expr->probability = expr->int_cmp == BFS_INT_EQUAL ? 0.01 : 0.50; - } - return expr; + return parse_test_icmp(state, eval_inum); } /** * Parse -links N. */ static struct bfs_expr *parse_links(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_test_icmp(state, eval_links); - if (expr) { - expr->probability = bfs_expr_cmp(expr, 1) ? 0.99 : 0.01; - } - return expr; + return parse_test_icmp(state, eval_links); } /** @@ -1764,12 +1726,6 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b return expr; } - if (strchr(pattern, '*')) { - expr->probability = 0.5; - } else { - expr->probability = 0.1; - } - return expr; } @@ -1921,16 +1877,11 @@ fail: */ static struct bfs_expr *parse_nogroup(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(state, eval_nogroup); - if (!expr) { - return NULL; + if (expr) { + // Who knows how many FDs getgrgid_r() needs? Probably at least + // one for /etc/group + expr->ephemeral_fds = 1; } - - expr->probability = 0.01; - - // Who knows how many FDs getgrgid_r() needs? Probably at least one for - // /etc/group - expr->ephemeral_fds = 1; - return expr; } @@ -1943,8 +1894,6 @@ static struct bfs_expr *parse_nohidden(struct parser_state *state, int arg1, int return NULL; } - hidden->probability = 0.01; - if (parse_exclude(state, hidden) != 0) { return NULL; } @@ -1966,16 +1915,11 @@ static struct bfs_expr *parse_noleaf(struct parser_state *state, int arg1, int a */ static struct bfs_expr *parse_nouser(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(state, eval_nouser); - if (!expr) { - return NULL; + if (expr) { + // Who knows how many FDs getpwuid_r() needs? Probably at least + // one for /etc/passwd + expr->ephemeral_fds = 1; } - - expr->probability = 0.01; - - // Who knows how many FDs getpwuid_r() needs? Probably at least one for - // /etc/passwd - expr->ephemeral_fds = 1; - return expr; } @@ -2428,9 +2372,6 @@ static struct bfs_expr *parse_samefile(struct parser_state *state, int arg1, int expr->dev = sb.dev; expr->ino = sb.ino; - - expr->probability = 0.01; - return expr; } @@ -2548,8 +2489,6 @@ static struct bfs_expr *parse_size(struct parser_state *state, int arg1, int arg goto bad_unit; } - expr->probability = expr->int_cmp == BFS_INT_EQUAL ? 0.01 : 0.50; - return expr; bad_unit: @@ -2584,50 +2523,37 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) return NULL; } - unsigned int types = 0; - float probability = 0.0; + expr->num = 0; const char *c = expr->argv[1]; while (true) { - enum bfs_type type; - float type_prob; - switch (*c) { case 'b': - type = BFS_BLK; - type_prob = 0.00000721183; + expr->num |= 1 << BFS_BLK; break; case 'c': - type = BFS_CHR; - type_prob = 0.0000499855; + expr->num |= 1 << BFS_CHR; break; case 'd': - type = BFS_DIR; - type_prob = 0.114475; + expr->num |= 1 << BFS_DIR; break; case 'D': - type = BFS_DOOR; - type_prob = 0.000001; + expr->num |= 1 << BFS_DOOR; break; case 'p': - type = BFS_FIFO; - type_prob = 0.00000248684; + expr->num |= 1 << BFS_FIFO; break; case 'f': - type = BFS_REG; - type_prob = 0.859772; + expr->num |= 1 << BFS_REG; break; case 'l': - type = BFS_LNK; - type_prob = 0.0256816; + expr->num |= 1 << BFS_LNK; break; case 's': - type = BFS_SOCK; - type_prob = 0.0000116881; + expr->num |= 1 << BFS_SOCK; break; case 'w': - type = BFS_WHT; - type_prob = 0.000001; + expr->num |= 1 << BFS_WHT; break; case '\0': @@ -2639,12 +2565,6 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) goto fail; } - unsigned int flag = 1 << type; - if (!(types & flag)) { - types |= flag; - probability += type_prob; - } - ++c; if (*c == '\0') { break; @@ -2657,9 +2577,6 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) } } - expr->num = types; - expr->probability = probability; - return expr; fail: @@ -2680,11 +2597,7 @@ static struct bfs_expr *parse_warn(struct parser_state *state, int warn, int arg */ static struct bfs_expr *parse_xattr(struct parser_state *state, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS - struct bfs_expr *expr = parse_nullary_test(state, eval_xattr); - if (expr) { - expr->probability = 0.01; - } - return expr; + return parse_nullary_test(state, eval_xattr); #else parse_error(state, "Missing platform support.\n"); return NULL; @@ -2696,11 +2609,7 @@ static struct bfs_expr *parse_xattr(struct parser_state *state, int arg1, int ar */ static struct bfs_expr *parse_xattrname(struct parser_state *state, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS - struct bfs_expr *expr = parse_unary_test(state, eval_xattrname); - if (expr) { - expr->probability = 0.01; - } - return expr; + return parse_unary_test(state, eval_xattrname); #else parse_error(state, "Missing platform support.\n"); return NULL; -- cgit v1.2.3 From 8fbb75d6cdc4692ca1e480ab077d718489e36353 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 25 Jan 2023 12:32:55 -0500 Subject: mtab: Always fill in the root fstype Fixes: 869e4010433c8610ba59f9a6a310df8be228d718 --- src/mtab.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mtab.c b/src/mtab.c index 35ae51d..a997183 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -221,7 +221,7 @@ static void bfs_mtab_fill_types(struct bfs_mtab *mtab) { if (fd >= 0) { struct bfs_stat parent; if (bfs_stat(fd, NULL, flags, &parent) == 0) { - if (parent.dev == sb.dev) { + if (parent.dev == sb.dev && parent.ino != sb.ino) { // Not a mount point any more (or a bind mount, but with the same fstype) goto next; } -- cgit v1.2.3 From 18492f8142274081adeceb7a5757f86721687799 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 25 Jan 2023 13:33:52 -0500 Subject: mtab: Keep parent dirs open during fill_types() --- src/eval.c | 5 ++++ src/mtab.c | 83 ++++++++++++++++++++++++++++++++++++++++-------------------- src/printf.c | 4 +++ 3 files changed, 65 insertions(+), 27 deletions(-) diff --git a/src/eval.c b/src/eval.c index 32b2e0e..d297af1 100644 --- a/src/eval.c +++ b/src/eval.c @@ -509,6 +509,11 @@ bool eval_fstype(const struct bfs_expr *expr, struct bfs_eval *state) { } const char *type = bfs_fstype(mtab, statbuf); + if (!type) { + eval_report_error(state); + return false; + } + return strcmp(type, expr->argv[1]) == 0; } diff --git a/src/mtab.c b/src/mtab.c index a997183..68b4c07 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -192,60 +192,89 @@ fail: return NULL; } -static void bfs_mtab_fill_types(struct bfs_mtab *mtab) { +static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { const enum bfs_stat_flags flags = BFS_STAT_NOFOLLOW | BFS_STAT_NOSYNC; + int ret = -1; + + // It's possible that /path/to/mount was unmounted between bfs_mtab_parse() and bfs_mtab_fill_types(). + // In that case, the dev_t of /path/to/mount will be the same as /path/to, which should not get its + // fstype from the old mount record of /path/to/mount. + // + // Detect this by comparing the st_dev of the parent (/path/to) and child (/path/to/mount). Only when + // they differ can the filesystem type actually change between them. As a minor optimization, we keep + // the parent directory open in case multiple mounts have the same parent (e.g. /mnt). + char *parent_dir = NULL; + int parent_fd = -1; + struct bfs_stat parent_stat; + int parent_ret; for (size_t i = 0; i < darray_length(mtab->entries); ++i) { struct bfs_mtab_entry *entry = &mtab->entries[i]; - - // It's possible that /path/to/mount was unmounted between bfs_mtab_parse() and bfs_mtab_fill_types(). - // In that case, the dev_t of /path/to/mount will be the same as /path/to, which should not get its - // fstype from the old mount record of /path/to/mount. - int fd = -1; const char *path = entry->path; + int fd = AT_FDCWD; + char *dir = xdirname(path); - if (dir) { - fd = open(dir, O_SEARCH | O_CLOEXEC | O_DIRECTORY); + if (!dir) { + goto fail; } - if (fd >= 0) { - path += xbaseoff(path); + + if (parent_dir && strcmp(parent_dir, dir) == 0) { + // Same parent + free(dir); } else { - fd = AT_FDCWD; + free(parent_dir); + parent_dir = dir; + + if (parent_fd >= 0) { + xclose(parent_fd); + } + parent_fd = open(parent_dir, O_SEARCH, O_CLOEXEC, O_DIRECTORY); + + parent_ret = -1; + if (parent_fd >= 0) { + parent_ret = bfs_stat(parent_fd, NULL, flags, &parent_stat); + } + } + + if (parent_fd >= 0) { + fd = parent_fd; + path += xbaseoff(path); } struct bfs_stat sb; if (bfs_stat(fd, path, flags, &sb) != 0) { - goto next; + continue; } - if (fd >= 0) { - struct bfs_stat parent; - if (bfs_stat(fd, NULL, flags, &parent) == 0) { - if (parent.dev == sb.dev && parent.ino != sb.ino) { - // Not a mount point any more (or a bind mount, but with the same fstype) - goto next; - } - } + if (parent_ret == 0 && parent_stat.dev == sb.dev && parent_stat.ino != sb.ino) { + // Not a mount point any more (or a bind mount, but with the same fstype) + continue; } struct trie_leaf *leaf = trie_insert_mem(&mtab->types, &sb.dev, sizeof(sb.dev)); if (leaf) { leaf->value = entry->type; - } - - next: - free(dir); - if (fd >= 0) { - xclose(fd); + } else { + goto fail; } } mtab->types_filled = true; + ret = 0; + +fail: + if (parent_fd >= 0) { + xclose(parent_fd); + } + free(parent_dir); + return ret; } const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statbuf) { if (!mtab->types_filled) { - bfs_mtab_fill_types((struct bfs_mtab *)mtab); + if (bfs_mtab_fill_types((struct bfs_mtab *)mtab) != 0) { + return NULL; + } } const struct trie_leaf *leaf = trie_find_mem(&mtab->types, &statbuf->dev, sizeof(statbuf->dev)); diff --git a/src/printf.c b/src/printf.c index 5af8362..7c0c8db 100644 --- a/src/printf.c +++ b/src/printf.c @@ -255,6 +255,10 @@ static int bfs_printf_F(CFILE *cfile, const struct bfs_printf *directive, const } const char *type = bfs_fstype(directive->ptr, statbuf); + if (!type) { + return -1; + } + return dyn_fprintf(cfile->file, directive, type); } -- cgit v1.2.3 From 5a9bd10815b4216e8c1a15e7730bb82cbc43af47 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 25 Jan 2023 13:45:57 -0500 Subject: mtab: Flags are separated with | not , --- src/mtab.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mtab.c b/src/mtab.c index 68b4c07..07d7a53 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -228,7 +228,7 @@ static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { if (parent_fd >= 0) { xclose(parent_fd); } - parent_fd = open(parent_dir, O_SEARCH, O_CLOEXEC, O_DIRECTORY); + parent_fd = open(parent_dir, O_SEARCH | O_CLOEXEC | O_DIRECTORY); parent_ret = -1; if (parent_fd >= 0) { -- cgit v1.2.3 From 9463fdd30d392c98de7b5712d30dfbaeada40e99 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 25 Jan 2023 16:14:11 -0500 Subject: Replace license boilerplate with SPDX tags And while I'm at it, remove years from copyright declarations. Link: https://spdx.dev/about/ Link: https://daniel.haxx.se/blog/2023/01/08/copyright-without-years/ --- LICENSE | 33 +++++++++++++++++++++------------ Makefile | 17 ++--------------- completions/bfs.bash | 21 ++++----------------- src/bar.c | 17 ++--------------- src/bar.h | 17 ++--------------- src/bfstd.c | 17 ++--------------- src/bfstd.h | 17 ++--------------- src/bftw.c | 17 ++--------------- src/bftw.h | 17 ++--------------- src/color.c | 17 ++--------------- src/color.h | 17 ++--------------- src/config.h | 17 ++--------------- src/ctx.c | 17 ++--------------- src/ctx.h | 17 ++--------------- src/darray.c | 17 ++--------------- src/darray.h | 17 ++--------------- src/diag.c | 17 ++--------------- src/diag.h | 17 ++--------------- src/dir.c | 17 ++--------------- src/dir.h | 17 ++--------------- src/dstring.c | 17 ++--------------- src/dstring.h | 17 ++--------------- src/eval.c | 17 ++--------------- src/eval.h | 17 ++--------------- src/exec.c | 17 ++--------------- src/exec.h | 17 ++--------------- src/expr.h | 17 ++--------------- src/fsade.c | 17 ++--------------- src/fsade.h | 17 ++--------------- src/main.c | 17 ++--------------- src/mtab.c | 17 ++--------------- src/mtab.h | 17 ++--------------- src/opt.c | 17 ++--------------- src/opt.h | 17 ++--------------- src/parse.c | 17 ++--------------- src/parse.h | 17 ++--------------- src/printf.c | 17 ++--------------- src/printf.h | 17 ++--------------- src/pwcache.c | 17 ++--------------- src/pwcache.h | 17 ++--------------- src/stat.c | 17 ++--------------- src/stat.h | 17 ++--------------- src/trie.c | 17 ++--------------- src/trie.h | 17 ++--------------- src/typo.c | 17 ++--------------- src/typo.h | 17 ++--------------- src/xregex.c | 17 ++--------------- src/xregex.h | 18 ++---------------- src/xspawn.c | 17 ++--------------- src/xspawn.h | 17 ++--------------- src/xtime.c | 17 ++--------------- src/xtime.h | 17 ++--------------- tests/bfstd.c | 17 ++--------------- tests/find-color.sh | 17 ++--------------- tests/ls-color.sh | 17 ++--------------- tests/mksock.c | 17 ++--------------- tests/tests.sh | 17 ++--------------- tests/trie.c | 17 ++--------------- tests/xtimegm.c | 17 ++--------------- tests/xtouch.c | 17 ++--------------- 60 files changed, 141 insertions(+), 900 deletions(-) diff --git a/LICENSE b/LICENSE index 069b145..290e3d3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,12 +1,21 @@ -Copyright (C) 2015-2021 Tavian Barnes - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +Copyright © 2015-2023 Tavian Barnes and the bfs contributors + +Permission to use, copy, modify, and/or distribute this software for any purpose with or +without fee is hereby granted. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO +THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT +SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR +ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION +OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE +USE OR PERFORMANCE OF THIS SOFTWARE. + +--- + +bfs is licensed under the Zero Clause BSD License. Individual files contain the following +tag instead of the full license text: + + SPDX-License-Identifier: 0BSD + +This enables machine processing of license information based on the SPDX License +Identifiers that are available here: https://spdx.org/licenses/ diff --git a/Makefile b/Makefile index b39a88a..0247b47 100644 --- a/Makefile +++ b/Makefile @@ -1,18 +1,5 @@ -############################################################################ -# bfs # -# Copyright (C) 2015-2023 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD ifneq ($(wildcard .git),) VERSION := $(shell git describe --always 2>/dev/null) diff --git a/completions/bfs.bash b/completions/bfs.bash index f734ab1..2f52e8d 100644 --- a/completions/bfs.bash +++ b/completions/bfs.bash @@ -1,21 +1,8 @@ -# bash completion script for bfs +# Copyright © Benjamin Mundt +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD -############################################################################ -# bfs # -# Copyright (C) 2020 Benjamin Mundt # -# Copyright (C) 2021 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# bash completion script for bfs _bfs() { local cur prev words cword diff --git a/src/bar.c b/src/bar.c index 37d33c8..bd5d381 100644 --- a/src/bar.c +++ b/src/bar.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "bar.h" #include "bfstd.h" diff --git a/src/bar.h b/src/bar.h index 3e509d6..20d92a9 100644 --- a/src/bar.h +++ b/src/bar.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A terminal status bar. diff --git a/src/bfstd.c b/src/bfstd.c index 3a37250..437e9c9 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "bfstd.h" #include "config.h" diff --git a/src/bfstd.h b/src/bfstd.h index 79307cc..0e11b66 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Standard library wrappers and polyfills. diff --git a/src/bftw.c b/src/bftw.c index 5f3ebde..9feca79 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The bftw() implementation consists of the following components: diff --git a/src/bftw.h b/src/bftw.h index c458e1b..77697ed 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2021 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A file-walking API based on nftw(). diff --git a/src/color.c b/src/color.c index 7c16ec5..589e631 100644 --- a/src/color.c +++ b/src/color.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "color.h" #include "bfstd.h" diff --git a/src/color.h b/src/color.h index 5b350cc..737c34b 100644 --- a/src/color.h +++ b/src/color.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2021 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Utilities for colored output on ANSI terminals. diff --git a/src/config.h b/src/config.h index 810f913..5ae9c82 100644 --- a/src/config.h +++ b/src/config.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Configuration and feature/platform detection. diff --git a/src/ctx.c b/src/ctx.c index 0403299..ff4a2a7 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "ctx.h" #include "color.h" diff --git a/src/ctx.h b/src/ctx.h index 6755d02..4c748b7 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * bfs execution context. diff --git a/src/darray.c b/src/darray.c index 6585d30..42b8397 100644 --- a/src/darray.c +++ b/src/darray.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "darray.h" #include diff --git a/src/darray.h b/src/darray.h index 4464381..cc6cc42 100644 --- a/src/darray.h +++ b/src/darray.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A dynamic array library. diff --git a/src/diag.c b/src/diag.c index b02473a..53db98e 100644 --- a/src/diag.c +++ b/src/diag.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "diag.h" #include "bfstd.h" diff --git a/src/diag.h b/src/diag.h index 8d0b19f..2952e30 100644 --- a/src/diag.h +++ b/src/diag.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Formatters for diagnostic messages. diff --git a/src/dir.c b/src/dir.c index 2081cc5..3d18eb2 100644 --- a/src/dir.c +++ b/src/dir.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2021-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "dir.h" #include "bfstd.h" diff --git a/src/dir.h b/src/dir.h index 69344c6..01eaaba 100644 --- a/src/dir.h +++ b/src/dir.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2021 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Directories and their contents. diff --git a/src/dstring.c b/src/dstring.c index f344d09..9112e54 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "dstring.h" #include diff --git a/src/dstring.h b/src/dstring.h index df20a04..ee3b345 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A dynamic string library. diff --git a/src/eval.c b/src/eval.c index d297af1..53ce605 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Implementation of all the primary expressions. diff --git a/src/eval.h b/src/eval.h index a50dc4e..3d70319 100644 --- a/src/eval.h +++ b/src/eval.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The evaluation functions that implement primary expressions like -name, diff --git a/src/exec.c b/src/exec.c index 8630469..6bde1c1 100644 --- a/src/exec.c +++ b/src/exec.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "exec.h" #include "bfstd.h" diff --git a/src/exec.h b/src/exec.h index a3e3c71..9d4192d 100644 --- a/src/exec.h +++ b/src/exec.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Implementation of -exec/-execdir/-ok/-okdir. diff --git a/src/expr.h b/src/expr.h index a52007a..1628cac 100644 --- a/src/expr.h +++ b/src/expr.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The expression tree representation. diff --git a/src/fsade.c b/src/fsade.c index 45969d1..a61a6b8 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2021 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "fsade.h" #include "config.h" diff --git a/src/fsade.h b/src/fsade.h index f45c6fd..9bef892 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A facade over (file)system features that are (un)implemented differently diff --git a/src/main.c b/src/main.c index fbddbe5..4f99580 100644 --- a/src/main.c +++ b/src/main.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * - main(): the entry point for bfs(1), a breadth-first version of find(1) diff --git a/src/mtab.c b/src/mtab.c index 07d7a53..ae6dfd2 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "mtab.h" #include "bfstd.h" diff --git a/src/mtab.h b/src/mtab.h index 807539d..5dfdf6c 100644 --- a/src/mtab.h +++ b/src/mtab.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A facade over platform-specific APIs for enumerating mounted filesystems. diff --git a/src/opt.c b/src/opt.c index e76e216..5505b7b 100644 --- a/src/opt.c +++ b/src/opt.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The expression optimizer. Different optimization levels are supported: diff --git a/src/opt.h b/src/opt.h index 5f8180d..28cadb9 100644 --- a/src/opt.h +++ b/src/opt.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Optimization. diff --git a/src/parse.c b/src/parse.c index f2582e0..30bf56d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2015-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * The command line parser. Expressions are parsed by recursive descent, with a diff --git a/src/parse.h b/src/parse.h index 7e29a03..6895c9f 100644 --- a/src/parse.h +++ b/src/parse.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * bfs command line parsing. diff --git a/src/printf.c b/src/printf.c index 7c0c8db..1b4f2d4 100644 --- a/src/printf.c +++ b/src/printf.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "printf.h" #include "bfstd.h" diff --git a/src/printf.h b/src/printf.h index a8c5f2a..2bff087 100644 --- a/src/printf.h +++ b/src/printf.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2017-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Implementation of -printf/-fprintf. diff --git a/src/pwcache.c b/src/pwcache.c index 868ec8f..5026dee 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "pwcache.h" #include "darray.h" diff --git a/src/pwcache.h b/src/pwcache.h index f1ca0bf..b6c0b67 100644 --- a/src/pwcache.h +++ b/src/pwcache.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A caching wrapper for /etc/{passwd,group}. diff --git a/src/stat.c b/src/stat.c index 94dedef..aaa5eac 100644 --- a/src/stat.c +++ b/src/stat.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "stat.h" #include "bfstd.h" diff --git a/src/stat.h b/src/stat.h index 44cbfad..7a70146 100644 --- a/src/stat.h +++ b/src/stat.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2019 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A facade over the stat() API that unifies some details that diverge between diff --git a/src/trie.c b/src/trie.c index 08a99b5..77c43cc 100644 --- a/src/trie.c +++ b/src/trie.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * This is an implementation of a "qp trie," as documented at diff --git a/src/trie.h b/src/trie.h index 2ede6ea..03ee64d 100644 --- a/src/trie.h +++ b/src/trie.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #ifndef BFS_TRIE_H #define BFS_TRIE_H diff --git a/src/typo.c b/src/typo.c index c16cab4..305711d 100644 --- a/src/typo.c +++ b/src/typo.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "typo.h" #include diff --git a/src/typo.h b/src/typo.h index 0347aae..13eaa67 100644 --- a/src/typo.h +++ b/src/typo.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2016 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #ifndef BFS_TYPO_H #define BFS_TYPO_H diff --git a/src/xregex.c b/src/xregex.c index 6f0e5a1..5f5480f 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "xregex.h" #include "config.h" diff --git a/src/xregex.h b/src/xregex.h index b2f56a5..998a2b0 100644 --- a/src/xregex.h +++ b/src/xregex.h @@ -1,19 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes and bfs * - * contributors * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes and the bfs contributors +// SPDX-License-Identifier: 0BSD #ifndef BFS_XREGEX_H #define BFS_XREGEX_H diff --git a/src/xspawn.c b/src/xspawn.c index f76267b..a30c264 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "xspawn.h" #include "bfstd.h" diff --git a/src/xspawn.h b/src/xspawn.h index cd6a42e..3dbf5d2 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2018-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * A process-spawning library inspired by posix_spawn(). diff --git a/src/xtime.c b/src/xtime.c index 079d42a..82690d0 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "xtime.h" #include diff --git a/src/xtime.h b/src/xtime.h index b49cd04..75d1f4e 100644 --- a/src/xtime.h +++ b/src/xtime.h @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * Date/time handling. diff --git a/tests/bfstd.c b/tests/bfstd.c index 4a8181b..8c61072 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "../src/bfstd.h" #include diff --git a/tests/find-color.sh b/tests/find-color.sh index ecdd5af..47de2a2 100755 --- a/tests/find-color.sh +++ b/tests/find-color.sh @@ -1,20 +1,7 @@ #!/usr/bin/env bash -############################################################################ -# bfs # -# Copyright (C) 2019 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD set -e diff --git a/tests/ls-color.sh b/tests/ls-color.sh index c82a58d..6d33f53 100755 --- a/tests/ls-color.sh +++ b/tests/ls-color.sh @@ -1,20 +1,7 @@ #!/usr/bin/env bash -############################################################################ -# bfs # -# Copyright (C) 2019 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD # Prints the "ground truth" coloring of a path using ls diff --git a/tests/mksock.c b/tests/mksock.c index 5068bc8..05edbeb 100644 --- a/tests/mksock.c +++ b/tests/mksock.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2019 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD /** * There's no standard Unix utility that creates a socket file, so this small diff --git a/tests/tests.sh b/tests/tests.sh index e57db4e..98d332c 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -1,20 +1,7 @@ #!/usr/bin/env bash -############################################################################ -# bfs # -# Copyright (C) 2015-2022 Tavian Barnes # -# # -# Permission to use, copy, modify, and/or distribute this software for any # -# purpose with or without fee is hereby granted. # -# # -# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES # -# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF # -# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR # -# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES # -# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN # -# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF # -# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. # -############################################################################ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD set -euP umask 022 diff --git a/tests/trie.c b/tests/trie.c index 88e92da..c2af18a 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020-2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #undef NDEBUG diff --git a/tests/xtimegm.c b/tests/xtimegm.c index d774b9e..bab64ba 100644 --- a/tests/xtimegm.c +++ b/tests/xtimegm.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2020 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "../src/xtime.h" #include diff --git a/tests/xtouch.c b/tests/xtouch.c index 9a91ec7..506c73d 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -1,18 +1,5 @@ -/**************************************************************************** - * bfs * - * Copyright (C) 2022 Tavian Barnes * - * * - * Permission to use, copy, modify, and/or distribute this software for any * - * purpose with or without fee is hereby granted. * - * * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * - ****************************************************************************/ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD #include "../src/bfstd.h" #include "../src/xtime.h" -- cgit v1.2.3 From 52537ad1cb46450f0b4eed090f5adfa9cbaeeb02 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 31 Jan 2023 11:57:25 -0500 Subject: mtab: Explicitly initialize parent_ret --- src/mtab.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mtab.c b/src/mtab.c index ae6dfd2..0e377a1 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -192,8 +192,8 @@ static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { // the parent directory open in case multiple mounts have the same parent (e.g. /mnt). char *parent_dir = NULL; int parent_fd = -1; + int parent_ret = -1; struct bfs_stat parent_stat; - int parent_ret; for (size_t i = 0; i < darray_length(mtab->entries); ++i) { struct bfs_mtab_entry *entry = &mtab->entries[i]; -- cgit v1.2.3 From 88525617e977b2b24315e8a7bc565c2975cdf345 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 31 Jan 2023 12:16:41 -0500 Subject: tests: Use close() wrappers --- tests/mksock.c | 2 +- tests/xtouch.c | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/tests/mksock.c b/tests/mksock.c index 05edbeb..42ef322 100644 --- a/tests/mksock.c +++ b/tests/mksock.c @@ -100,7 +100,7 @@ int main(int argc, char *argv[]) { ret = EXIT_FAILURE; } - if (close(fd) != 0) { + if (xclose(fd) != 0) { errmsg(cmd, path); ret = EXIT_FAILURE; } diff --git a/tests/xtouch.c b/tests/xtouch.c index 506c73d..7e29547 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -104,13 +104,11 @@ static int xtouch(const struct args *args, const char *path) { } if (futimens(fd, args->times) != 0) { - int error = errno; - close(fd); - errno = error; + close_quietly(fd); return -1; } - return close(fd); + return xclose(fd); } } -- cgit v1.2.3 From a1bd70db67ddbe167caf15b7334b3588653d3b91 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 31 Jan 2023 11:26:41 -0500 Subject: Release 2.6.3 --- Makefile | 2 +- docs/CHANGELOG.md | 40 ++++++++++++++++++++++++++++++++++++++++ src/config.h | 2 +- 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 0247b47..b509065 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 2.6.2 +VERSION := 2.6.3 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index ff1c1f7..f67e68a 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,46 @@ 2.* === +2.6.3 +----- + +**January 31, 2023** + +- Fixed running the tests as root on Linux [`8b24de3`] + +- Fixed some tests on Android [`2724dfb`] [`0a5a80c`] + +- Stopped relying on non-POSIX touch(1) features in the tests. + This should fix the tests on at least OpenBSD. + [`2d5edb3`] + +- User/group caches are now filled lazily instead of eagerly [`b41dca5`] + +- More caches and I/O streams are flushed before -exec/-ok [`f98a1c4`] + +- Fixed various memory safety issues found by fuzzing \ + [`712b137`] [`5ce883d`] [`da02def`] [`c55e855`] + +- Fixed a test failure on certain macOS versions [`8b24de3`] + +- Mitigated a race condition when determining filesystem types ([#97]) + +- Lots of refactoring and optimization + +[`8b24de3`]: https://github.com/tavianator/bfs/commit/8b24de3882ff5a3e33b82ab20bb4eadf134cf559 +[`2724dfb`]: https://github.com/tavianator/bfs/commit/2724dfbd17552f892a0d8b39b96cbe9e49d66fdb +[`0a5a80c`]: https://github.com/tavianator/bfs/commit/0a5a80c98cc7e5d8735b615fa197a6cff2bb08cc +[`2d5edb3`]: https://github.com/tavianator/bfs/commit/2d5edb37b924715b4fbee4d917ac334c773fca61 +[`b41dca5`]: https://github.com/tavianator/bfs/commit/b41dca52762c5188638236ae81b9f4597bb29ac9 +[`f98a1c4`]: https://github.com/tavianator/bfs/commit/f98a1c4a1cf61ff7d6483388ca1fac365fb0b31b +[`712b137`]: https://github.com/tavianator/bfs/commit/712b13756a09014ef730c8f9b96da4dc2f09b762 +[`5ce883d`]: https://github.com/tavianator/bfs/commit/5ce883daaafc69f83b01dac5db0647e9662a6e87 +[`da02def`]: https://github.com/tavianator/bfs/commit/da02defb91c3a1bda0ea7e653d81f997f1c8884a +[`c55e855`]: https://github.com/tavianator/bfs/commit/c55e85580df10c5afdc6fc0710e756a456aa8e93 +[`8b24de3`]: https://github.com/tavianator/bfs/commit/8b24de3882ff5a3e33b82ab20bb4eadf134cf559 +[#97]: https://github.com/tavianator/bfs/issues/97 + + 2.6.2 ----- diff --git a/src/config.h b/src/config.h index 5ae9c82..34ae11d 100644 --- a/src/config.h +++ b/src/config.h @@ -17,7 +17,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "2.6.2" +# define BFS_VERSION "2.6.3" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From 6f39ea72e51e8e5bc5616ffe69d8f363a1bb4c5e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 31 Jan 2023 17:17:51 -0500 Subject: README: Adjust badge alignment --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index b7707bb..aa3d666 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,13 @@
-`bfs` -===== - +

+bfs +
Version License -CI Status +CI Status Code coverage - -***Breadth-first search for your files.*** +

**[Features]   •   [Installation]   •   [Usage]   •   [Building]   •   [Hacking]   •   [Changelog]** -- cgit v1.2.3 From a2b723200c0e077d1720956ccfa65ec2546259a9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 3 Feb 2023 09:49:13 -0500 Subject: Move SPDX blurb out of LICENSE so that it can be autodetected --- LICENSE | 10 ---------- docs/HACKING.md | 6 ++++++ 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/LICENSE b/LICENSE index 290e3d3..240d2ac 100644 --- a/LICENSE +++ b/LICENSE @@ -9,13 +9,3 @@ SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL D ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - ---- - -bfs is licensed under the Zero Clause BSD License. Individual files contain the following -tag instead of the full license text: - - SPDX-License-Identifier: 0BSD - -This enables machine processing of license information based on the SPDX License -Identifiers that are available here: https://spdx.org/licenses/ diff --git a/docs/HACKING.md b/docs/HACKING.md index c9bbe14..d129a28 100644 --- a/docs/HACKING.md +++ b/docs/HACKING.md @@ -7,6 +7,12 @@ License `bfs` is licensed under the [Zero-Clause BSD License](https://opensource.org/licenses/0BSD), a maximally permissive license. Contributions must use the same license. +Individual files contain the following tag instead of the full license text: + + SPDX-License-Identifier: 0BSD + +This enables machine processing of license information based on the SPDX License Identifiers that are available here: https://spdx.org/licenses/ + Implementation -------------- -- cgit v1.2.3 From cb8afc73185e8ddc9b9eb2963ecf69f3ab7d24cd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 27 Mar 2023 10:57:31 -0400 Subject: dir: Use libc's getdents64() wrapper if possible --- src/dir.c | 185 ++++++++++++++++++++++++++------------------------------------ 1 file changed, 76 insertions(+), 109 deletions(-) diff --git a/src/dir.c b/src/dir.c index 3d18eb2..f56648e 100644 --- a/src/dir.c +++ b/src/dir.c @@ -14,7 +14,39 @@ #include #if __linux__ -# include + +#include + +#if __has_feature(memory_sanitizer) +# include +#endif + +/** Directory entry type for bfs_getdents() */ +typedef struct dirent64 sys_dirent; + +/** getdents() syscall wrapper. */ +static ssize_t bfs_getdents(int fd, void *buf, size_t size) { +#if __has_feature(memory_sanitizer) + __msan_allocated_memory(buf, size); +#endif + +#if __GLIBC__ && !__GLIBC_PREREQ(2, 30) + ssize_t ret = syscall(__NR_getdents64, fd, buf, size); +#else + ssize_t ret = getdents64(fd, buf, size); +#endif + +#if __has_feature(memory_sanitizer) + if (ret > 0) { + __msan_unpoison(buf, ret); + } +#endif + + return ret; +} + +#else // !__linux__ +typedef struct dirent sys_dirent; #endif enum bfs_type bfs_mode_to_type(mode_t mode) { @@ -65,23 +97,6 @@ enum bfs_type bfs_mode_to_type(mode_t mode) { } } -#if __linux__ -/** - * This is not defined in the kernel headers for some reason, callers have to - * define it themselves. - */ -struct linux_dirent64 { - ino64_t d_ino; - off64_t d_off; - unsigned short d_reclen; - unsigned char d_type; - char d_name[]; -}; - -// Make the whole allocation 64k -#define BUF_SIZE ((64 << 10) - 8) -#endif - struct bfs_dir { #if __linux__ int fd; @@ -93,6 +108,10 @@ struct bfs_dir { #endif }; +#if __linux__ +# define BUF_SIZE ((64 << 10) - sizeof(struct bfs_dir)) +#endif + struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { #if __linux__ struct bfs_dir *dir = malloc(sizeof(*dir) + BUF_SIZE); @@ -147,64 +166,44 @@ int bfs_dirfd(const struct bfs_dir *dir) { #endif } -/** Convert a dirent type to a bfs_type. */ -static enum bfs_type translate_type(int d_type) { - switch (d_type) { -#ifdef DT_BLK - case DT_BLK: - return BFS_BLK; -#endif -#ifdef DT_CHR - case DT_CHR: - return BFS_CHR; -#endif -#ifdef DT_DIR - case DT_DIR: - return BFS_DIR; -#endif -#ifdef DT_DOOR - case DT_DOOR: - return BFS_DOOR; -#endif -#ifdef DT_FIFO - case DT_FIFO: - return BFS_FIFO; -#endif -#ifdef DT_LNK - case DT_LNK: - return BFS_LNK; -#endif -#ifdef DT_PORT - case DT_PORT: - return BFS_PORT; -#endif -#ifdef DT_REG - case DT_REG: - return BFS_REG; -#endif -#ifdef DT_SOCK - case DT_SOCK: - return BFS_SOCK; -#endif -#ifdef DT_WHT - case DT_WHT: - return BFS_WHT; -#endif - } - +/** Convert de->d_type to a bfs_type, if it exists. */ +static enum bfs_type bfs_d_type(const sys_dirent *de) { +#ifdef DTTOIF + return bfs_mode_to_type(DTTOIF(de->d_type)); +#else return BFS_UNKNOWN; +#endif } -#if !__linux__ -/** Get the type from a struct dirent if it exists, and convert it. */ -static enum bfs_type dirent_type(const struct dirent *de) { -#if defined(_DIRENT_HAVE_D_TYPE) || defined(DT_UNKNOWN) - return translate_type(de->d_type); +/** Read a single directory entry. */ +static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { +#if __linux__ + char *buf = (char *)(dir + 1); + + if (dir->pos >= dir->size) { + ssize_t ret = bfs_getdents(dir->fd, buf, BUF_SIZE); + if (ret <= 0) { + return ret; + } + dir->pos = 0; + dir->size = ret; + } + + *de = (void *)(buf + dir->pos); + dir->pos += (*de)->d_reclen; + return 1; #else - return BFS_UNKNOWN; + errno = 0; + *de = readdir(dir->dir); + if (*de) { + return 1; + } else if (errno == 0) { + return 0; + } else { + return -1; + } #endif } -#endif /** Check if a name is . or .. */ static bool is_dot(const char *name) { @@ -213,54 +212,22 @@ static bool is_dot(const char *name) { int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) { while (true) { -#if __linux__ - char *buf = (char *)(dir + 1); - - if (dir->pos >= dir->size) { -#if __has_feature(memory_sanitizer) - // Make sure msan knows the buffer is initialized - memset(buf, 0, BUF_SIZE); -#endif - - ssize_t size = syscall(__NR_getdents64, dir->fd, buf, BUF_SIZE); - if (size <= 0) { - return size; - } - dir->pos = 0; - dir->size = size; + const sys_dirent *sysde; + int ret = bfs_getdent(dir, &sysde); + if (ret <= 0) { + return ret; } - const struct linux_dirent64 *lde = (void *)(buf + dir->pos); - dir->pos += lde->d_reclen; - - if (is_dot(lde->d_name)) { + if (is_dot(sysde->d_name)) { continue; } if (de) { - de->type = translate_type(lde->d_type); - de->name = lde->d_name; + de->type = bfs_d_type(sysde); + de->name = sysde->d_name; } return 1; -#else // !__linux__ - errno = 0; - dir->de = readdir(dir->dir); - if (dir->de) { - if (is_dot(dir->de->d_name)) { - continue; - } - if (de) { - de->type = dirent_type(dir->de); - de->name = dir->de->d_name; - } - return 1; - } else if (errno != 0) { - return -1; - } else { - return 0; - } -#endif // !__linux__ } } -- cgit v1.2.3 From cef0869f62487b7a729b005f345e5ecd9a9bbfc7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 27 Mar 2023 12:22:51 -0400 Subject: dir: Use getdents() on FreeBSD too --- src/dir.c | 69 ++++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/src/dir.c b/src/dir.c index f56648e..83e8a66 100644 --- a/src/dir.c +++ b/src/dir.c @@ -13,16 +13,22 @@ #include #include -#if __linux__ - -#include - #if __has_feature(memory_sanitizer) # include #endif +#if __linux__ +# include + /** Directory entry type for bfs_getdents() */ typedef struct dirent64 sys_dirent; +#else +typedef struct dirent sys_dirent; +#endif + +#define BFS_GETDENTS (__linux__ || __FreeBSD__) + +#if BFS_GETDENTS /** getdents() syscall wrapper. */ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { @@ -30,10 +36,12 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { __msan_allocated_memory(buf, size); #endif -#if __GLIBC__ && !__GLIBC_PREREQ(2, 30) +#if __linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30) ssize_t ret = syscall(__NR_getdents64, fd, buf, size); -#else +#elif __linux__ ssize_t ret = getdents64(fd, buf, size); +#else + ssize_t ret = getdents(fd, buf, size); #endif #if __has_feature(memory_sanitizer) @@ -45,9 +53,7 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { return ret; } -#else // !__linux__ -typedef struct dirent sys_dirent; -#endif +#endif // BFS_GETDENTS enum bfs_type bfs_mode_to_type(mode_t mode) { switch (mode & S_IFMT) { @@ -98,7 +104,7 @@ enum bfs_type bfs_mode_to_type(mode_t mode) { } struct bfs_dir { -#if __linux__ +#if BFS_GETDENTS int fd; unsigned short pos; unsigned short size; @@ -108,16 +114,15 @@ struct bfs_dir { #endif }; -#if __linux__ -# define BUF_SIZE ((64 << 10) - sizeof(struct bfs_dir)) +#if BFS_GETDENTS +# define DIR_SIZE (64 << 10) +# define BUF_SIZE (DIR_SIZE - sizeof(struct bfs_dir)) +#else +# define DIR_SIZE sizeof(struct bfs_dir) #endif struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { -#if __linux__ - struct bfs_dir *dir = malloc(sizeof(*dir) + BUF_SIZE); -#else - struct bfs_dir *dir = malloc(sizeof(*dir)); -#endif + struct bfs_dir *dir = malloc(DIR_SIZE); if (!dir) { return NULL; } @@ -138,7 +143,7 @@ struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { return NULL; } -#if __linux__ +#if BFS_GETDENTS dir->fd = fd; dir->pos = 0; dir->size = 0; @@ -153,13 +158,13 @@ struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { } dir->de = NULL; -#endif // __linux__ +#endif return dir; } int bfs_dirfd(const struct bfs_dir *dir) { -#if __linux__ +#if BFS_GETDENTS return dir->fd; #else return dirfd(dir->dir); @@ -177,7 +182,7 @@ static enum bfs_type bfs_d_type(const sys_dirent *de) { /** Read a single directory entry. */ static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { -#if __linux__ +#if BFS_GETDENTS char *buf = (char *)(dir + 1); if (dir->pos >= dir->size) { @@ -205,8 +210,16 @@ static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { #endif } -/** Check if a name is . or .. */ -static bool is_dot(const char *name) { +/** Skip ".", "..", and deleted/empty dirents. */ +static bool skip_dirent(const sys_dirent *de) { +#if __FreeBSD__ + // NFS mounts on FreeBSD can return empty dirents with inode number 0 + if (de->d_fileno == 0) { + return true; + } +#endif + + const char *name = de->d_name; return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); } @@ -218,7 +231,7 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) { return ret; } - if (is_dot(sysde->d_name)) { + if (skip_dirent(sysde)) { continue; } @@ -232,7 +245,7 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) { } int bfs_closedir(struct bfs_dir *dir) { -#if __linux__ +#if BFS_GETDENTS int ret = xclose(dir->fd); #else int ret = closedir(dir->dir); @@ -242,14 +255,10 @@ int bfs_closedir(struct bfs_dir *dir) { } int bfs_freedir(struct bfs_dir *dir) { -#if __linux__ +#if BFS_GETDENTS int ret = dir->fd; free(dir); return ret; -#elif __FreeBSD__ - int ret = fdclosedir(dir->dir); - free(dir); - return ret; #else int ret = dup_cloexec(dirfd(dir->dir)); bfs_closedir(dir); -- cgit v1.2.3 From ccfc70a1ccca27a1481ae5dae11d8ea9f5fc8ef9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 27 Mar 2023 14:48:36 -0400 Subject: dir: Explicitly align struct bfs_dir for the trailing dirent buffer --- src/dir.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/dir.c b/src/dir.c index 83e8a66..be2c44e 100644 --- a/src/dir.c +++ b/src/dir.c @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -105,9 +106,10 @@ enum bfs_type bfs_mode_to_type(mode_t mode) { struct bfs_dir { #if BFS_GETDENTS - int fd; + alignas(sys_dirent) int fd; unsigned short pos; unsigned short size; + // sys_dirent buf[]; #else DIR *dir; struct dirent *de; -- cgit v1.2.3 From 9f06eb0c0dfef4b6276253fe29f26e47d1ef7b30 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 27 Mar 2023 15:24:36 -0400 Subject: dir: s/d_fileno/d_ino/ --- src/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dir.c b/src/dir.c index be2c44e..b92b7c2 100644 --- a/src/dir.c +++ b/src/dir.c @@ -216,7 +216,7 @@ static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { static bool skip_dirent(const sys_dirent *de) { #if __FreeBSD__ // NFS mounts on FreeBSD can return empty dirents with inode number 0 - if (de->d_fileno == 0) { + if (de->d_ino == 0) { return true; } #endif -- cgit v1.2.3 From e40855d3b98aa3f65c19608b3d8c70f54b714063 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 28 Mar 2023 10:41:39 -0400 Subject: bftw: Refactor bftw_queue --- src/bftw.c | 143 ++++++++++++++++++++++++------------------------------------- 1 file changed, 55 insertions(+), 88 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 9feca79..fdc6be6 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -10,8 +10,7 @@ * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors, * used for openat() to minimize the amount of path re-traversals. * - * - struct bftw_queue: The queue of bftw_file's left to explore. Implemented - * as a simple circular buffer. + * - struct bftw_queue: A linked list of bftw_file's left to explore. * * - struct bftw_state: Represents the current state of the traversal, allowing * various helper functions to take fewer parameters. @@ -395,28 +394,33 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { } /** - * A queue of bftw_file's to examine. + * A queue of bftw_file's. */ struct bftw_queue { - /** The head of the queue. */ struct bftw_file *head; - /** The insertion target. */ - struct bftw_file **target; + struct bftw_file **tail; }; /** Initialize a bftw_queue. */ static void bftw_queue_init(struct bftw_queue *queue) { queue->head = NULL; - queue->target = &queue->head; + queue->tail = &queue->head; } /** Add a file to a bftw_queue. */ static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { assert(file->next == NULL); + *queue->tail = file; + queue->tail = &file->next; +} - file->next = *queue->target; - *queue->target = file; - queue->target = &file->next; +/** Append a whole queue to the tail of another. */ +static void bftw_queue_extend(struct bftw_queue *dest, struct bftw_queue *src) { + if (src->head) { + *dest->tail = src->head; + dest->tail = src->tail; + bftw_queue_init(src); + } } /** Pop the next file from the head of the queue. */ @@ -424,71 +428,42 @@ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { struct bftw_file *file = queue->head; queue->head = file->next; file->next = NULL; - if (queue->target == &file->next) { - queue->target = &queue->head; + if (!queue->head) { + queue->tail = &queue->head; } return file; } -/** The split phase of mergesort. */ -static struct bftw_file **bftw_sort_split(struct bftw_file **head, struct bftw_file **tail) { - struct bftw_file **tortoise = head, **hare = head; - - while (*hare != *tail) { - tortoise = &(*tortoise)->next; - hare = &(*hare)->next; - if (*hare != *tail) { - hare = &(*hare)->next; - } +/** Sort a queue by filename. */ +static void bftw_queue_sort(struct bftw_queue *queue) { + if (!queue->head || !queue->head->next) { + return; } - return tortoise; -} + struct bftw_queue left, right; + bftw_queue_init(&left); + bftw_queue_init(&right); -/** The merge phase of mergesort. */ -static struct bftw_file **bftw_sort_merge(struct bftw_file **head, struct bftw_file **mid, struct bftw_file **tail) { - struct bftw_file *left = *head, *right = *mid, *end = *tail; - *mid = NULL; - *tail = NULL; - - while (left || right) { - struct bftw_file *next; - if (left && (!right || strcoll(left->name, right->name) <= 0)) { - next = left; - left = left->next; - } else { - next = right; - right = right->next; - } - - *head = next; - head = &next->next; + // Split + for (struct bftw_file *hare = queue->head; hare && (hare = hare->next); hare = hare->next) { + bftw_queue_push(&left, bftw_queue_pop(queue)); } + bftw_queue_extend(&right, queue); - *head = end; - return head; -} + // Recurse + bftw_queue_sort(&left); + bftw_queue_sort(&right); -/** - * Sort a (sub-)list of files. - * - * @param head - * The head of the (sub-)list to sort. - * @param tail - * The tail of the (sub-)list to sort. - * @return - * The new tail of the (sub-)list. - */ -static struct bftw_file **bftw_sort_files(struct bftw_file **head, struct bftw_file **tail) { - struct bftw_file **mid = bftw_sort_split(head, tail); - if (*mid == *head || *mid == *tail) { - return tail; + // Merge + while (left.head && right.head) { + if (strcoll(left.head->name, right.head->name) <= 0) { + bftw_queue_push(queue, bftw_queue_pop(&left)); + } else { + bftw_queue_push(queue, bftw_queue_pop(&right)); + } } - - mid = bftw_sort_files(head, mid); - tail = bftw_sort_files(mid, tail); - - return bftw_sort_merge(head, mid, tail); + bftw_queue_extend(queue, &left); + bftw_queue_extend(queue, &right); } /** @@ -511,10 +486,10 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; - /** The queue of directories left to explore. */ + /** The queue of files left to explore. */ struct bftw_queue queue; - /** The start of the current batch of files. */ - struct bftw_file **batch; + /** A batch of files to enqueue. */ + struct bftw_queue batch; /** The current path. */ char *path; @@ -560,7 +535,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg bftw_cache_init(&state->cache, args->nopenfd); bftw_queue_init(&state->queue); - state->batch = NULL; + bftw_queue_init(&state->batch); state->file = NULL; state->previous = NULL; @@ -920,8 +895,7 @@ static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { bftw_fill_id(file, &state->ftwbuf); } - bftw_queue_push(&state->queue, file); - + bftw_queue_push(&state->batch, file); return 0; } @@ -1101,11 +1075,11 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag } /** - * Drain all the entries from a bftw_queue. + * Drain all the files from the queue. */ -static void bftw_drain_queue(struct bftw_state *state, struct bftw_queue *queue) { - while (queue->head) { - state->file = bftw_queue_pop(queue); +static void bftw_drain_queue(struct bftw_state *state) { + while (state->queue.head) { + state->file = bftw_queue_pop(&state->queue); bftw_gc_file(state, BFTW_VISIT_NONE); } } @@ -1122,7 +1096,7 @@ static int bftw_state_destroy(struct bftw_state *state) { bftw_closedir(state, BFTW_VISIT_NONE); bftw_gc_file(state, BFTW_VISIT_NONE); - bftw_drain_queue(state, &state->queue); + bftw_drain_queue(state); bftw_cache_destroy(&state->cache); @@ -1130,19 +1104,16 @@ static int bftw_state_destroy(struct bftw_state *state) { return state->error ? -1 : 0; } -/** Start a batch of files. */ -static void bftw_batch_start(struct bftw_state *state) { - if (state->strategy == BFTW_DFS) { - state->queue.target = &state->queue.head; - } - state->batch = state->queue.target; -} - /** Finish adding a batch of files. */ static void bftw_batch_finish(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - state->queue.target = bftw_sort_files(state->batch, state->queue.target); + bftw_queue_sort(&state->batch); + } + + if (state->strategy == BFTW_DFS) { + bftw_queue_extend(&state->batch, &state->queue); } + bftw_queue_extend(&state->queue, &state->batch); } /** @@ -1156,7 +1127,6 @@ static int bftw_stream(const struct bftw_args *args) { assert(!(state.flags & (BFTW_SORT | BFTW_BUFFER))); - bftw_batch_start(&state); for (size_t i = 0; i < args->npaths; ++i) { const char *path = args->paths[i]; @@ -1178,7 +1148,6 @@ static int bftw_stream(const struct bftw_args *args) { while (bftw_pop(&state) > 0) { bftw_opendir(&state); - bftw_batch_start(&state); while (bftw_readdir(&state) > 0) { const char *name = state.de->name; @@ -1218,7 +1187,6 @@ static int bftw_batch(const struct bftw_args *args) { return -1; } - bftw_batch_start(&state); for (size_t i = 0; i < args->npaths; ++i) { if (bftw_push(&state, args->paths[i], false) != 0) { goto done; @@ -1241,7 +1209,6 @@ static int bftw_batch(const struct bftw_args *args) { bftw_opendir(&state); - bftw_batch_start(&state); while (bftw_readdir(&state) > 0) { if (bftw_push(&state, state.de->name, false) != 0) { goto done; -- cgit v1.2.3 From 135b98c26456adbfbc72fb12e4753ee0716b1f92 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 28 Mar 2023 14:18:08 -0400 Subject: list: New generic linked list API --- Makefile | 1 + src/config.h | 11 ++++ src/list.c | 169 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/list.h | 170 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 351 insertions(+) create mode 100644 src/list.c create mode 100644 src/list.h diff --git a/Makefile b/Makefile index b509065..428a0f7 100644 --- a/Makefile +++ b/Makefile @@ -223,6 +223,7 @@ LIBBFS := \ $(OBJ)/src/eval.o \ $(OBJ)/src/exec.o \ $(OBJ)/src/fsade.o \ + $(OBJ)/src/list.o \ $(OBJ)/src/mtab.o \ $(OBJ)/src/opt.o \ $(OBJ)/src/parse.o \ diff --git a/src/config.h b/src/config.h index 34ae11d..cee8511 100644 --- a/src/config.h +++ b/src/config.h @@ -147,6 +147,17 @@ */ #define BFS_COUNTOF(array) (sizeof(array) / sizeof(0[array])) +// BFS_CONTAINER_OF() helper +static inline char *bfs_container_offset(char *ptr, ptrdiff_t offset, size_t unused) { + return ptr ? ptr - offset : NULL; +} + +/** + * Move a pointer from a field to its outer struct. + */ +#define BFS_CONTAINER_OF(ptr, type, member) \ + ((type *)bfs_container_offset((char *)(ptr), offsetof(type, member), sizeof((ptr) - &((type *)NULL)->member))) + // Lower bound on BFS_FLEX_SIZEOF() #define BFS_FLEX_LB(type, member, length) (offsetof(type, member) + sizeof(((type *)NULL)->member[0]) * (length)) diff --git a/src/list.c b/src/list.c new file mode 100644 index 0000000..dffee19 --- /dev/null +++ b/src/list.c @@ -0,0 +1,169 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "list.h" +#include +#include + +void slink_init(struct slink *link) { + link->next = NULL; +} + +void slist_init(struct slist *list) { + list->head = NULL; + list->tail = &list->head; +} + +bool slist_is_empty(const struct slist *list) { + return !list->head; +} + +void slist_append(struct slist *list, struct slink *link) { + assert(!link->next); + *list->tail = link; + list->tail = &link->next; +} + +void slist_prepend(struct slist *list, struct slink *link) { + assert(!link->next); + if (!list->head) { + list->tail = &link->next; + } + link->next = list->head; + list->head = link; +} + +void slist_extend(struct slist *dest, struct slist *src) { + if (src->head) { + *dest->tail = src->head; + dest->tail = src->tail; + slist_init(src); + } +} + +struct slink *slist_pop(struct slist *list) { + struct slink *head = list->head; + if (!head) { + return NULL; + } + + list->head = head->next; + if (!list->head) { + list->tail = &list->head; + } + + head->next = NULL; + return head; +} + +void slist_sort(struct slist *list, slist_cmp_fn *cmp_fn, const void *ptr) { + if (!list->head || !list->head->next) { + return; + } + + struct slist left, right; + slist_init(&left); + slist_init(&right); + + // Split + for (struct slink *hare = list->head; hare && (hare = hare->next); hare = hare->next) { + slist_append(&left, slist_pop(list)); + } + slist_extend(&right, list); + + // Recurse + slist_sort(&left, cmp_fn, ptr); + slist_sort(&right, cmp_fn, ptr); + + // Merge + while (left.head && right.head) { + if (cmp_fn(left.head, right.head, ptr)) { + slist_append(list, slist_pop(&left)); + } else { + slist_append(list, slist_pop(&right)); + } + } + slist_extend(list, &left); + slist_extend(list, &right); +} + +void link_init(struct link *link) { + link->prev = NULL; + link->next = NULL; +} + +void list_init(struct list *list) { + list->head = NULL; + list->tail = NULL; +} + +bool list_is_empty(const struct list *list) { + return !list->head; +} + +void list_append(struct list *list, struct link *link) { + list_insert_after(list, list->tail, link); +} + +void list_prepend(struct list *list, struct link *link) { + list_insert_after(list, NULL, link); +} + +void list_insert_after(struct list *list, struct link *target, struct link *link) { + assert(!list_attached(list, link)); + + if (target) { + link->prev = target; + link->next = target->next; + } else { + link->next = list->head; + } + + if (link->prev) { + link->prev->next = link; + } else { + list->head = link; + } + + if (link->next) { + link->next->prev = link; + } else { + list->tail = link; + } +} + +void list_remove(struct list *list, struct link *link) { + if (link->prev) { + assert(list->head != link); + link->prev->next = link->next; + } else { + assert(list->head == link); + list->head = link->next; + } + + if (link->next) { + assert(list->tail != link); + link->next->prev = link->prev; + } else { + assert(list->tail == link); + list->tail = link->prev; + } + + link->prev = NULL; + link->next = NULL; +} + +struct link *list_pop(struct list *list) { + struct link *head = list->head; + if (!head) { + return NULL; + } + + list_remove(list, head); + return head; +} + +bool list_attached(const struct list *list, const struct link *link) { + return link->prev || list->head == link + || link->next || list->tail == link; +} diff --git a/src/list.h b/src/list.h new file mode 100644 index 0000000..1985413 --- /dev/null +++ b/src/list.h @@ -0,0 +1,170 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Intrusive linked lists. + */ + +#ifndef BFS_LIST_H +#define BFS_LIST_H + +#include "config.h" +#include + +/** + * A singly-linked list entry. + */ +struct slink { + struct slink *next; +}; + +/** Initialize a list entry. */ +void slink_init(struct slink *link); + +/** + * A singly-linked list. + */ +struct slist { + struct slink *head; + struct slink **tail; +}; + +/** Initialize an empty list. */ +void slist_init(struct slist *list); + +/** Check if a list is empty. */ +bool slist_is_empty(const struct slist *list); + +/** Add an entry at the tail of the list. */ +void slist_append(struct slist *list, struct slink *link); + +/** Add an entry at the head of the list. */ +void slist_prepend(struct slist *list, struct slink *link); + +/** Add an entire list at the tail of the list. */ +void slist_extend(struct slist *dest, struct slist *src); + +/** Remove the head of the list. */ +struct slink *slist_pop(struct slist *list); + +/** + * Comparison function type for slist_sort(). + * + * @param left + * The left-hand side of the comparison. + * @param right + * The right-hand side of the comparison. + * @param ptr + * An arbitrary pointer passed to slist_sort(). + * @return + * Whether left <= right. + */ +typedef bool slist_cmp_fn(struct slink *left, struct slink *right, const void *ptr); + +/** Sort a list. */ +void slist_sort(struct slist *list, slist_cmp_fn *cmp_fn, const void *ptr); + +/** + * A doubly-linked list entry. + */ +struct link { + struct link *prev; + struct link *next; +}; + +/** Initialize a list entry. */ +void link_init(struct link *link); + +/** + * A doubly-linked list. + */ +struct list { + struct link *head; + struct link *tail; +}; + +/** Initialize an empty list. */ +void list_init(struct list *list); + +/** Check if a list is empty. */ +bool list_is_empty(const struct list *list); + +/** Add an entry at the tail of the list. */ +void list_append(struct list *list, struct link *link); + +/** Add an entry at the head of the list. */ +void list_prepend(struct list *list, struct link *link); + +/** Insert an entry after the target entry. */ +void list_insert_after(struct list *list, struct link *target, struct link *link); + +/** Remove an entry from a list. */ +void list_remove(struct list *list, struct link *link); + +/** Remove the head of the list. */ +struct link *list_pop(struct list *list); + +/** Check if a link is attached to a list. */ +bool list_attached(const struct list *list, const struct link *link); + +// Helper for LIST_FOR_EACH_*() +#define LIST_FOR_EACH_IMPL(entry, type, i, member, ...) \ + for (type *_next, *i = BFS_CONTAINER_OF(entry, type, member); \ + i && (_next = BFS_CONTAINER_OF(i->member.next, type, member), true); \ + i = _next) + +/** + * Iterate over a list from the given entry. + * + * @param entry + * The entry to start from. + * @param type + * The type of the list entries. + * @param i + * The name of the loop variable, declared as type *i. + * @param member + * The name of the list link field (default: link). + */ +#define LIST_FOR_EACH_FROM(...) \ + LIST_FOR_EACH_IMPL(__VA_ARGS__, link,) + +/** + * Iterate over a list. + * + * @param list + * The list to iterate over. + * @param type + * The type of the list entries. + * @param i + * The name of the loop variable, declared as type *i. + * @param member + * The name of the list link field (default: link). + */ +#define LIST_FOR_EACH(list, ...) \ + LIST_FOR_EACH_FROM((list)->head, __VA_ARGS__) + +// Pop from a list or slist +#define LIST_POP(l) _Generic((l), \ + struct list *: list_pop((struct list *)l), \ + struct slist *: slist_pop((struct slist *)l)) + +// Helper for LIST_DRAIN() +#define LIST_DRAIN_IMPL(list, type, i, member, ...) \ + for (type *i; (i = BFS_CONTAINER_OF(LIST_POP(list), type, member));) + +/** + * Drain the entries from a list. + * + * @param list + * The list to drain. + * @param type + * The type of the list entries. + * @param i + * The name of the loop variable, declared as type *i. + * @param member + * The name of the list link field (default: link). + */ +#define LIST_DRAIN(...) \ + LIST_DRAIN_IMPL(__VA_ARGS__, link,) + +#endif // BFS_LIST_H -- cgit v1.2.3 From 7888fbababd22190e9f919fc272957426a27969e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 28 Mar 2023 14:18:36 -0400 Subject: bftw: Use list.h for the queue and LRU lists --- src/bftw.c | 311 ++++++++++++++++++------------------------------------------- 1 file changed, 93 insertions(+), 218 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index fdc6be6..9ff526d 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -10,8 +10,6 @@ * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors, * used for openat() to minimize the amount of path re-traversals. * - * - struct bftw_queue: A linked list of bftw_file's left to explore. - * * - struct bftw_state: Represents the current state of the traversal, allowing * various helper functions to take fewer parameters. */ @@ -21,6 +19,7 @@ #include "config.h" #include "dir.h" #include "dstring.h" +#include "list.h" #include "mtab.h" #include "stat.h" #include "trie.h" @@ -40,13 +39,11 @@ struct bftw_file { struct bftw_file *parent; /** The root under which this file was found. */ struct bftw_file *root; - /** The next file in the queue, if any. */ - struct bftw_file *next; - /** The previous file in the LRU list. */ - struct bftw_file *lru_prev; - /** The next file in the LRU list. */ - struct bftw_file *lru_next; + /** Queue link. */ + struct slink link; + /** LRU link. */ + struct link lru; /** This file's depth in the walk. */ size_t depth; @@ -71,143 +68,89 @@ struct bftw_file { char name[]; }; +/** Move from a list entry to a bftw_file. */ +static struct bftw_file *bftw_file_entry(struct slink *link) { + return BFS_CONTAINER_OF(link, struct bftw_file, link); +} + +/** Move from an LRU entry to a bftw_file. */ +static struct bftw_file *bftw_lru_file(struct link *link) { + return BFS_CONTAINER_OF(link, struct bftw_file, lru); +} + /** * A cache of open directories. */ struct bftw_cache { - /** The head of the LRU list. */ - struct bftw_file *head; + /** The LRU list. */ + struct list list; /** The insertion target for the LRU list. */ - struct bftw_file *target; - /** The tail of the LRU list. */ - struct bftw_file *tail; + struct link *target; /** The remaining capacity of the LRU list. */ size_t capacity; }; /** Initialize a cache. */ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { - cache->head = NULL; + list_init(&cache->list); cache->target = NULL; - cache->tail = NULL; cache->capacity = capacity; } -/** Destroy a cache. */ -static void bftw_cache_destroy(struct bftw_cache *cache) { - assert(!cache->tail); - assert(!cache->target); - assert(!cache->head); -} - -/** Add a bftw_file to the cache. */ -static void bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { - assert(cache->capacity > 0); - assert(file->fd >= 0); - assert(!file->lru_prev); - assert(!file->lru_next); - - if (cache->target) { - file->lru_prev = cache->target; - file->lru_next = cache->target->lru_next; - } else { - file->lru_next = cache->head; - } - - if (file->lru_prev) { - file->lru_prev->lru_next = file; - } else { - cache->head = file; - } - - if (file->lru_next) { - file->lru_next->lru_prev = file; - } else { - cache->tail = file; - } - - // Prefer to keep the root paths open by keeping them at the head of the list - if (file->depth == 0) { - cache->target = file; - } - - --cache->capacity; -} - /** Remove a bftw_file from the cache. */ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { - if (cache->target == file) { - cache->target = file->lru_prev; + if (cache->target == &file->lru) { + cache->target = cache->target->prev; } - if (file->lru_prev) { - assert(cache->head != file); - file->lru_prev->lru_next = file->lru_next; - } else { - assert(cache->head == file); - cache->head = file->lru_next; - } - - if (file->lru_next) { - assert(cache->tail != file); - file->lru_next->lru_prev = file->lru_prev; - } else { - assert(cache->tail == file); - cache->tail = file->lru_prev; - } + list_remove(&cache->list, &file->lru); - file->lru_prev = NULL; - file->lru_next = NULL; ++cache->capacity; } -/** Mark a cache entry as recently used. */ -static void bftw_cache_use(struct bftw_cache *cache, struct bftw_file *file) { - bftw_cache_remove(cache, file); - bftw_cache_add(cache, file); -} - /** Close a bftw_file. */ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { assert(file->fd >= 0); - bftw_cache_remove(cache, file); + if (list_attached(&cache->list, &file->lru)) { + bftw_cache_remove(cache, file); + } xclose(file->fd); file->fd = -1; } -/** Pop a directory from the cache. */ -static void bftw_cache_pop(struct bftw_cache *cache) { - assert(cache->tail); - bftw_file_close(cache, cache->tail); +/** Pop the least recently used directory from the cache. */ +static int bftw_cache_pop(struct bftw_cache *cache) { + if (list_is_empty(&cache->list)) { + return -1; + } + + struct bftw_file *file = bftw_lru_file(cache->list.tail); + bftw_file_close(cache, file); + return 0; } -/** - * Shrink the cache, to recover from EMFILE. - * - * @param cache - * The cache in question. - * @param saved - * A bftw_file that must be preserved. - * @return - * 0 if successfully shrunk, otherwise -1. - */ -static int bftw_cache_shrink(struct bftw_cache *cache, const struct bftw_file *saved) { - struct bftw_file *file = cache->tail; - if (!file) { +/** Add a bftw_file to the cache. */ +static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { + assert(file->fd >= 0); + + if (cache->capacity == 0 && bftw_cache_pop(cache) != 0) { + bftw_file_close(cache, file); + errno = EMFILE; return -1; } - if (file == saved) { - file = file->lru_prev; - if (!file) { - return -1; - } + assert(cache->capacity > 0); + --cache->capacity; + + list_insert_after(&cache->list, cache->target, &file->lru); + + // Prefer to keep the root paths open by keeping them at the head of the list + if (file->depth == 0) { + cache->target = &file->lru; } - bftw_file_close(cache, file); - cache->capacity = 0; return 0; } @@ -220,6 +163,12 @@ static size_t bftw_child_nameoff(const struct bftw_file *parent) { return ret; } +/** Destroy a cache. */ +static void bftw_cache_destroy(struct bftw_cache *cache) { + assert(!cache->target); + assert(list_is_empty(&cache->list)); +} + /** Create a new bftw_file. */ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); @@ -243,10 +192,8 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam file->nameoff = 0; } - file->next = NULL; - - file->lru_prev = NULL; - file->lru_next = NULL; + slink_init(&file->link); + link_init(&file->lru); file->refcount = 1; file->fd = -1; @@ -282,7 +229,8 @@ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, st int at_fd = AT_FDCWD; if (base) { - bftw_cache_use(cache, base); + // Remove base from the cache temporarily so it stays open + bftw_cache_remove(cache, base); at_fd = base->fd; } @@ -290,21 +238,22 @@ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, st int fd = openat(at_fd, at_path, flags); if (fd < 0 && errno == EMFILE) { - if (bftw_cache_shrink(cache, base) == 0) { + if (bftw_cache_pop(cache) == 0) { fd = openat(at_fd, at_path, flags); } + cache->capacity = 1; } - if (fd >= 0) { - if (cache->capacity == 0) { - bftw_cache_pop(cache); - } + if (base) { + bftw_cache_add(cache, base); + } + if (fd >= 0) { file->fd = fd; bftw_cache_add(cache, file); } - return fd; + return file->fd; } /** @@ -337,28 +286,19 @@ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, cons } // Handle ENAMETOOLONG by manually traversing the path component-by-component - - // Use the ->next linked list to temporarily hold the reversed parent - // chain between base and file - struct bftw_file *cur; - for (cur = file; cur->parent != base; cur = cur->parent) { - cur->parent->next = cur; + struct slist parents; + slist_init(&parents); + for (struct bftw_file *cur = file; cur != base; cur = cur->parent) { + slist_prepend(&parents, &cur->link); } - // Open the files in the chain one by one - for (base = cur; base; base = base->next) { - fd = bftw_file_openat(cache, base, base->parent, base->name); - if (fd < 0 || base == file) { - break; + LIST_DRAIN(&parents, struct bftw_file, cur) { + if (!cur->parent || cur->parent->fd >= 0) { + bftw_file_openat(cache, cur, cur->parent, cur->name); } } - // Clear out the linked list - for (struct bftw_file *next = cur->next; cur != file; cur = next, next = next->next) { - cur->next = NULL; - } - - return fd; + return file->fd; } /** @@ -393,79 +333,6 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { free(file); } -/** - * A queue of bftw_file's. - */ -struct bftw_queue { - struct bftw_file *head; - struct bftw_file **tail; -}; - -/** Initialize a bftw_queue. */ -static void bftw_queue_init(struct bftw_queue *queue) { - queue->head = NULL; - queue->tail = &queue->head; -} - -/** Add a file to a bftw_queue. */ -static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { - assert(file->next == NULL); - *queue->tail = file; - queue->tail = &file->next; -} - -/** Append a whole queue to the tail of another. */ -static void bftw_queue_extend(struct bftw_queue *dest, struct bftw_queue *src) { - if (src->head) { - *dest->tail = src->head; - dest->tail = src->tail; - bftw_queue_init(src); - } -} - -/** Pop the next file from the head of the queue. */ -static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { - struct bftw_file *file = queue->head; - queue->head = file->next; - file->next = NULL; - if (!queue->head) { - queue->tail = &queue->head; - } - return file; -} - -/** Sort a queue by filename. */ -static void bftw_queue_sort(struct bftw_queue *queue) { - if (!queue->head || !queue->head->next) { - return; - } - - struct bftw_queue left, right; - bftw_queue_init(&left); - bftw_queue_init(&right); - - // Split - for (struct bftw_file *hare = queue->head; hare && (hare = hare->next); hare = hare->next) { - bftw_queue_push(&left, bftw_queue_pop(queue)); - } - bftw_queue_extend(&right, queue); - - // Recurse - bftw_queue_sort(&left); - bftw_queue_sort(&right); - - // Merge - while (left.head && right.head) { - if (strcoll(left.head->name, right.head->name) <= 0) { - bftw_queue_push(queue, bftw_queue_pop(&left)); - } else { - bftw_queue_push(queue, bftw_queue_pop(&right)); - } - } - bftw_queue_extend(queue, &left); - bftw_queue_extend(queue, &right); -} - /** * Holds the current state of the bftw() traversal. */ @@ -487,9 +354,9 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; /** The queue of files left to explore. */ - struct bftw_queue queue; + struct slist queue; /** A batch of files to enqueue. */ - struct bftw_queue batch; + struct slist batch; /** The current path. */ char *path; @@ -534,8 +401,9 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } bftw_cache_init(&state->cache, args->nopenfd); - bftw_queue_init(&state->queue); - bftw_queue_init(&state->batch); + + slist_init(&state->queue); + slist_init(&state->batch); state->file = NULL; state->previous = NULL; @@ -895,7 +763,7 @@ static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { bftw_fill_id(file, &state->ftwbuf); } - bftw_queue_push(&state->batch, file); + slist_append(&state->batch, &file->link); return 0; } @@ -942,7 +810,7 @@ static int bftw_pop(struct bftw_state *state) { return 0; } - state->file = bftw_queue_pop(&state->queue); + state->file = bftw_file_entry(slist_pop(&state->queue)); if (bftw_build_path(state) != 0) { return -1; @@ -1078,8 +946,8 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag * Drain all the files from the queue. */ static void bftw_drain_queue(struct bftw_state *state) { - while (state->queue.head) { - state->file = bftw_queue_pop(&state->queue); + LIST_DRAIN(&state->queue, struct bftw_file, file) { + state->file = file; bftw_gc_file(state, BFTW_VISIT_NONE); } } @@ -1104,16 +972,23 @@ static int bftw_state_destroy(struct bftw_state *state) { return state->error ? -1 : 0; } +/** Comparison function for BFTW_SORT. */ +static bool bftw_file_cmp(struct slink *left, struct slink *right, const void *ptr) { + struct bftw_file *lfile = bftw_file_entry(left); + struct bftw_file *rfile = bftw_file_entry(right); + return strcoll(lfile->name, rfile->name) <= 0; +} + /** Finish adding a batch of files. */ static void bftw_batch_finish(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - bftw_queue_sort(&state->batch); + slist_sort(&state->batch, bftw_file_cmp, NULL); } if (state->strategy == BFTW_DFS) { - bftw_queue_extend(&state->batch, &state->queue); + slist_extend(&state->batch, &state->queue); } - bftw_queue_extend(&state->queue, &state->batch); + slist_extend(&state->queue, &state->batch); } /** -- cgit v1.2.3 From ba2d4ac206ff1321ea953d3305d8bda048922983 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 28 Mar 2023 15:34:43 -0400 Subject: trie: Use list.h for the list of leaves --- src/trie.c | 28 ++++------------------------ src/trie.h | 31 ++++++++++--------------------- tests/trie.c | 2 -- 3 files changed, 14 insertions(+), 47 deletions(-) diff --git a/src/trie.c b/src/trie.c index 77c43cc..7b00f4b 100644 --- a/src/trie.c +++ b/src/trie.c @@ -163,8 +163,7 @@ static uintptr_t trie_encode_node(const struct trie_node *node) { void trie_init(struct trie *trie) { trie->root = 0; - trie->head = NULL; - trie->tail = NULL; + list_init(&trie->leaves); } /** Check if a number is a power of two. */ @@ -341,16 +340,8 @@ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, siz return NULL; } - leaf->prev = trie->tail; - leaf->next = NULL; - - if (leaf->prev) { - leaf->prev->next = leaf; - } else { - trie->head = leaf; - } - - trie->tail = leaf; + link_init(&leaf->link); + list_append(&trie->leaves, &leaf->link); leaf->value = NULL; leaf->length = length; @@ -361,18 +352,7 @@ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, siz /** Free a leaf. */ static void trie_leaf_free(struct trie *trie, struct trie_leaf *leaf) { - if (leaf->prev) { - leaf->prev->next = leaf->next; - } else { - trie->head = leaf->next; - } - - if (leaf->next) { - leaf->next->prev = leaf->prev; - } else { - trie->tail = leaf->prev; - } - + list_remove(&trie->leaves, &leaf->link); free(leaf); } diff --git a/src/trie.h b/src/trie.h index 03ee64d..6e1e875 100644 --- a/src/trie.h +++ b/src/trie.h @@ -4,6 +4,8 @@ #ifndef BFS_TRIE_H #define BFS_TRIE_H +#include "config.h" +#include "list.h" #include #include #include @@ -12,24 +14,13 @@ * A leaf of a trie. */ struct trie_leaf { - /** - * Linked list of leaves, in insertion order. - */ - struct trie_leaf *prev, *next; - - /** - * An arbitrary value associated with this leaf. - */ + /** Linked list of leaves, in insertion order. */ + struct link link; + /** An arbitrary value associated with this leaf. */ void *value; - - /** - * The length of the key in bytes. - */ + /** The length of the key in bytes. */ size_t length; - - /** - * The key itself, stored inline. - */ + /** The key itself, stored inline. */ char key[]; }; @@ -40,7 +31,7 @@ struct trie { /** Pointer to the root node/leaf. */ uintptr_t root; /** Linked list of leaves. */ - struct trie_leaf *head, *tail; + struct list leaves; }; /** @@ -142,9 +133,7 @@ void trie_destroy(struct trie *trie); /** * Iterate over the leaves of a trie. */ -#define TRIE_FOR_EACH(trie, leaf) \ - for (struct trie_leaf *leaf = (trie)->head, *_next; \ - leaf && (_next = leaf->next, true); \ - leaf = _next) +#define TRIE_FOR_EACH(trie, leaf) \ + LIST_FOR_EACH(&(trie)->leaves, struct trie_leaf, leaf) #endif // BFS_TRIE_H diff --git a/tests/trie.c b/tests/trie.c index c2af18a..65660a9 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -74,8 +74,6 @@ int main(void) { size_t i = 0; TRIE_FOR_EACH(&trie, leaf) { assert(leaf == trie_find_str(&trie, keys[i])); - assert(!leaf->prev || leaf->prev->next == leaf); - assert(!leaf->next || leaf->next->prev == leaf); ++i; } assert(i == nkeys); -- cgit v1.2.3 From 116ab1ed2b4230aea1ab634618af3162cabbb37f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 29 Mar 2023 12:15:33 -0400 Subject: xspawn: Use list.h for the action list --- src/xspawn.c | 36 +++++++++++++++++------------------- src/xspawn.h | 4 ++-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/xspawn.c b/src/xspawn.c index a30c264..e6ce0de 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -4,6 +4,7 @@ #include "xspawn.h" #include "bfstd.h" #include "config.h" +#include "list.h" #include #include #include @@ -32,7 +33,7 @@ enum bfs_spawn_op { * A spawn action. */ struct bfs_spawn_action { - struct bfs_spawn_action *next; + struct slink link; enum bfs_spawn_op op; int in_fd; @@ -43,18 +44,15 @@ struct bfs_spawn_action { int bfs_spawn_init(struct bfs_spawn *ctx) { ctx->flags = 0; - ctx->actions = NULL; - ctx->tail = &ctx->actions; + slist_init(&ctx->actions); return 0; } int bfs_spawn_destroy(struct bfs_spawn *ctx) { - struct bfs_spawn_action *action = ctx->actions; - while (action) { - struct bfs_spawn_action *next = action->next; + LIST_DRAIN(&ctx->actions, struct bfs_spawn_action, action) { free(action); - action = next; } + return 0; } @@ -66,15 +64,16 @@ int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { /** Add a spawn action to the chain. */ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { struct bfs_spawn_action *action = malloc(sizeof(*action)); - if (action) { - action->next = NULL; - action->op = op; - action->in_fd = -1; - action->out_fd = -1; - - *ctx->tail = action; - ctx->tail = &action->next; + if (!action) { + return NULL; } + + slink_init(&action->link); + action->op = op; + action->in_fd = -1; + action->out_fd = -1; + + slist_append(&ctx->actions, &action->link); return action; } @@ -137,12 +136,10 @@ int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rli /** Actually exec() the new process. */ static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { - int error; - const struct bfs_spawn_action *actions = ctx ? ctx->actions : NULL; - xclose(pipefd[0]); - for (const struct bfs_spawn_action *action = actions; action; action = action->next) { + const struct slink *head = ctx ? ctx->actions.head : NULL; + LIST_FOR_EACH_FROM(head, struct bfs_spawn_action, action) { // Move the error-reporting pipe out of the way if necessary... if (action->out_fd == pipefd[1]) { int fd = dup_cloexec(pipefd[1]); @@ -185,6 +182,7 @@ static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char ** execve(exe, argv, envp); + int error; fail: error = errno; diff --git a/src/xspawn.h b/src/xspawn.h index 3dbf5d2..7e673f1 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -8,6 +8,7 @@ #ifndef BFS_XSPAWN_H #define BFS_XSPAWN_H +#include "list.h" #include #include @@ -24,8 +25,7 @@ enum bfs_spawn_flags { */ struct bfs_spawn { enum bfs_spawn_flags flags; - struct bfs_spawn_action *actions; - struct bfs_spawn_action **tail; + struct slist actions; }; /** -- cgit v1.2.3 From f75f3de63888702f29f48bcf2691291403720b9d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 29 Mar 2023 13:25:18 -0400 Subject: list: New helper macros for converting entries to items --- src/bftw.c | 18 +++++------------- src/list.h | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 65 insertions(+), 16 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 9ff526d..aa36b87 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -69,14 +69,8 @@ struct bftw_file { }; /** Move from a list entry to a bftw_file. */ -static struct bftw_file *bftw_file_entry(struct slink *link) { - return BFS_CONTAINER_OF(link, struct bftw_file, link); -} - -/** Move from an LRU entry to a bftw_file. */ -static struct bftw_file *bftw_lru_file(struct link *link) { - return BFS_CONTAINER_OF(link, struct bftw_file, lru); -} +#define BFTW_FILE(...) \ + LIST_ITEM(struct bftw_file, __VA_ARGS__) /** * A cache of open directories. @@ -126,7 +120,7 @@ static int bftw_cache_pop(struct bftw_cache *cache) { return -1; } - struct bftw_file *file = bftw_lru_file(cache->list.tail); + struct bftw_file *file = BFTW_FILE(cache->list.tail, lru); bftw_file_close(cache, file); return 0; } @@ -810,7 +804,7 @@ static int bftw_pop(struct bftw_state *state) { return 0; } - state->file = bftw_file_entry(slist_pop(&state->queue)); + state->file = BFTW_FILE(slist_pop(&state->queue)); if (bftw_build_path(state) != 0) { return -1; @@ -974,9 +968,7 @@ static int bftw_state_destroy(struct bftw_state *state) { /** Comparison function for BFTW_SORT. */ static bool bftw_file_cmp(struct slink *left, struct slink *right, const void *ptr) { - struct bftw_file *lfile = bftw_file_entry(left); - struct bftw_file *rfile = bftw_file_entry(right); - return strcoll(lfile->name, rfile->name) <= 0; + return strcoll(BFTW_FILE(left)->name, BFTW_FILE(right)->name) <= 0; } /** Finish adding a batch of files. */ diff --git a/src/list.h b/src/list.h index 1985413..b47bed7 100644 --- a/src/list.h +++ b/src/list.h @@ -107,10 +107,67 @@ struct link *list_pop(struct list *list); /** Check if a link is attached to a list. */ bool list_attached(const struct list *list, const struct link *link); +// LIST_ITEM() helper +#define LIST_ITEM_IMPL(type, entry, member, ...) \ + BFS_CONTAINER_OF(entry, type, member) + +/** + * Convert a list entry to its container. + * + * @param type + * The type of the list entries. + * @param entry + * The list entry to convert. + * @param member + * The name of the list link field (default: link). + * @return + * The item that contains the given entry. + */ +#define LIST_ITEM(...) \ + LIST_ITEM_IMPL(__VA_ARGS__, link,) + +// LIST_NEXT() helper +#define LIST_NEXT_IMPL(type, entry, member, ...) \ + LIST_ITEM(type, (entry)->member.next, member) + +/** + * Get the next item in a list. + * + * @param type + * The type of the list entries. + * @param entry + * The current entry. + * @param member + * The name of the list link field (default: link). + * @return + * The next item in the list. + */ +#define LIST_NEXT(...) \ + LIST_NEXT_IMPL(__VA_ARGS__, link,) + +// LIST_PREV() helper +#define LIST_PREV_IMPL(type, entry, member, ...) \ + LIST_ITEM(type, (entry)->member.prev, member) + +/** + * Get the previous entry in a list. + * + * @param type + * The type of the list entries. + * @param entry + * The current entry. + * @param member + * The name of the list link field (default: link). + * @return + * The previous item in the list. + */ +#define LIST_PREV(...) \ + LIST_PREV_IMPL(__VA_ARGS__, link,) + // Helper for LIST_FOR_EACH_*() #define LIST_FOR_EACH_IMPL(entry, type, i, member, ...) \ - for (type *_next, *i = BFS_CONTAINER_OF(entry, type, member); \ - i && (_next = BFS_CONTAINER_OF(i->member.next, type, member), true); \ + for (type *_next, *i = LIST_ITEM(type, entry, member); \ + i && (_next = LIST_NEXT(type, i, member), true); \ i = _next) /** @@ -150,7 +207,7 @@ bool list_attached(const struct list *list, const struct link *link); // Helper for LIST_DRAIN() #define LIST_DRAIN_IMPL(list, type, i, member, ...) \ - for (type *i; (i = BFS_CONTAINER_OF(LIST_POP(list), type, member));) + for (type *i; (i = LIST_ITEM(type, LIST_POP(list), member));) /** * Drain the entries from a list. -- cgit v1.2.3 From 86f4b4f7180bca73a734249e67dada708f8275ff Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 31 Mar 2023 16:19:45 -0400 Subject: list: Use macros instead of type-erased lists --- Makefile | 1 - src/bftw.c | 136 ++++++++++++------ src/config.h | 11 -- src/list.c | 169 ---------------------- src/list.h | 447 ++++++++++++++++++++++++++++++++++++++--------------------- src/trie.c | 8 +- src/trie.h | 9 +- src/xspawn.c | 15 +- src/xspawn.h | 4 +- tests/trie.c | 2 + 10 files changed, 403 insertions(+), 399 deletions(-) delete mode 100644 src/list.c diff --git a/Makefile b/Makefile index 428a0f7..b509065 100644 --- a/Makefile +++ b/Makefile @@ -223,7 +223,6 @@ LIBBFS := \ $(OBJ)/src/eval.o \ $(OBJ)/src/exec.o \ $(OBJ)/src/fsade.o \ - $(OBJ)/src/list.o \ $(OBJ)/src/mtab.o \ $(OBJ)/src/opt.o \ $(OBJ)/src/parse.o \ diff --git a/src/bftw.c b/src/bftw.c index aa36b87..7bc724a 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -7,6 +7,8 @@ * - struct bftw_file: A file that has been encountered during the traversal. * They have reference-counted links to their parents in the directory tree. * + * - struct bftw_list: A linked list of bftw_file's. + * * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors, * used for openat() to minimize the amount of path re-traversals. * @@ -39,11 +41,14 @@ struct bftw_file { struct bftw_file *parent; /** The root under which this file was found. */ struct bftw_file *root; + /** The next file in the queue, if any. */ + struct bftw_file *next; - /** Queue link. */ - struct slink link; - /** LRU link. */ - struct link lru; + /** LRU list links. */ + struct { + struct bftw_file *prev; + struct bftw_file *next; + } lru; /** This file's depth in the walk. */ size_t depth; @@ -68,36 +73,42 @@ struct bftw_file { char name[]; }; -/** Move from a list entry to a bftw_file. */ -#define BFTW_FILE(...) \ - LIST_ITEM(struct bftw_file, __VA_ARGS__) +/** + * A linked list of bftw_file's. + */ +struct bftw_list { + struct bftw_file *head; + struct bftw_file **tail; +}; /** * A cache of open directories. */ struct bftw_cache { - /** The LRU list. */ - struct list list; + /** The head of the LRU list. */ + struct bftw_file *head; + /** The tail of the LRU list. */ + struct bftw_file *tail; /** The insertion target for the LRU list. */ - struct link *target; + struct bftw_file *target; /** The remaining capacity of the LRU list. */ size_t capacity; }; /** Initialize a cache. */ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { - list_init(&cache->list); + LIST_INIT(cache); cache->target = NULL; cache->capacity = capacity; } /** Remove a bftw_file from the cache. */ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { - if (cache->target == &file->lru) { - cache->target = cache->target->prev; + if (cache->target == file) { + cache->target = file->lru.prev; } - list_remove(&cache->list, &file->lru); + LIST_REMOVE(cache, file, lru); ++cache->capacity; } @@ -106,7 +117,7 @@ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { assert(file->fd >= 0); - if (list_attached(&cache->list, &file->lru)) { + if (LIST_ATTACHED(cache, file, lru)) { bftw_cache_remove(cache, file); } @@ -116,11 +127,11 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { /** Pop the least recently used directory from the cache. */ static int bftw_cache_pop(struct bftw_cache *cache) { - if (list_is_empty(&cache->list)) { + struct bftw_file *file = cache->tail; + if (!file) { return -1; } - struct bftw_file *file = BFTW_FILE(cache->list.tail, lru); bftw_file_close(cache, file); return 0; } @@ -138,11 +149,11 @@ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { assert(cache->capacity > 0); --cache->capacity; - list_insert_after(&cache->list, cache->target, &file->lru); + LIST_INSERT(cache, cache->target, file, lru); // Prefer to keep the root paths open by keeping them at the head of the list if (file->depth == 0) { - cache->target = &file->lru; + cache->target = file; } return 0; @@ -159,8 +170,9 @@ static size_t bftw_child_nameoff(const struct bftw_file *parent) { /** Destroy a cache. */ static void bftw_cache_destroy(struct bftw_cache *cache) { + assert(!cache->head); + assert(!cache->tail); assert(!cache->target); - assert(list_is_empty(&cache->list)); } /** Create a new bftw_file. */ @@ -186,8 +198,8 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam file->nameoff = 0; } - slink_init(&file->link); - link_init(&file->lru); + file->next = NULL; + file->lru.prev = file->lru.next = NULL; file->refcount = 1; file->fd = -1; @@ -280,16 +292,19 @@ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, cons } // Handle ENAMETOOLONG by manually traversing the path component-by-component - struct slist parents; - slist_init(&parents); - for (struct bftw_file *cur = file; cur != base; cur = cur->parent) { - slist_prepend(&parents, &cur->link); + struct bftw_list parents; + SLIST_INIT(&parents); + + struct bftw_file *cur; + for (cur = file; cur != base; cur = cur->parent) { + SLIST_PREPEND(&parents, cur); } - LIST_DRAIN(&parents, struct bftw_file, cur) { + while ((cur = parents.head)) { if (!cur->parent || cur->parent->fd >= 0) { bftw_file_openat(cache, cur, cur->parent, cur->name); } + SLIST_POP(&parents); } return file->fd; @@ -348,9 +363,9 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; /** The queue of files left to explore. */ - struct slist queue; + struct bftw_list queue; /** A batch of files to enqueue. */ - struct slist batch; + struct bftw_list batch; /** The current path. */ char *path; @@ -396,8 +411,8 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg bftw_cache_init(&state->cache, args->nopenfd); - slist_init(&state->queue); - slist_init(&state->batch); + SLIST_INIT(&state->queue); + SLIST_INIT(&state->batch); state->file = NULL; state->previous = NULL; @@ -757,7 +772,7 @@ static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { bftw_fill_id(file, &state->ftwbuf); } - slist_append(&state->batch, &file->link); + SLIST_APPEND(&state->batch, file); return 0; } @@ -800,11 +815,12 @@ static int bftw_build_path(struct bftw_state *state) { * Pop the next file from the queue. */ static int bftw_pop(struct bftw_state *state) { - if (!state->queue.head) { + state->file = state->queue.head; + if (!state->file) { return 0; } - state->file = BFTW_FILE(slist_pop(&state->queue)); + SLIST_POP(&state->queue); if (bftw_build_path(state) != 0) { return -1; @@ -940,8 +956,10 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag * Drain all the files from the queue. */ static void bftw_drain_queue(struct bftw_state *state) { - LIST_DRAIN(&state->queue, struct bftw_file, file) { - state->file = file; + while (state->queue.head) { + state->file = state->queue.head; + SLIST_POP(&state->queue); + bftw_gc_file(state, BFTW_VISIT_NONE); } } @@ -966,21 +984,55 @@ static int bftw_state_destroy(struct bftw_state *state) { return state->error ? -1 : 0; } -/** Comparison function for BFTW_SORT. */ -static bool bftw_file_cmp(struct slink *left, struct slink *right, const void *ptr) { - return strcoll(BFTW_FILE(left)->name, BFTW_FILE(right)->name) <= 0; +/** Sort a bftw_list by filename. */ +static void bftw_list_sort(struct bftw_list *queue) { + if (!queue->head || !queue->head->next) { + return; + } + + struct bftw_list left, right; + SLIST_INIT(&left); + SLIST_INIT(&right); + + // Split + for (struct bftw_file *hare = queue->head; hare && (hare = hare->next); hare = hare->next) { + struct bftw_file *tortoise = queue->head; + SLIST_POP(queue); + SLIST_APPEND(&left, tortoise); + } + SLIST_EXTEND(&right, queue); + + // Recurse + bftw_list_sort(&left); + bftw_list_sort(&right); + + // Merge + while (left.head && right.head) { + struct bftw_file *lf = left.head; + struct bftw_file *rf = right.head; + + if (strcoll(lf->name, rf->name) <= 0) { + SLIST_POP(&left); + SLIST_APPEND(queue, lf); + } else { + SLIST_POP(&right); + SLIST_APPEND(queue, rf); + } + } + SLIST_EXTEND(queue, &left); + SLIST_EXTEND(queue, &right); } /** Finish adding a batch of files. */ static void bftw_batch_finish(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - slist_sort(&state->batch, bftw_file_cmp, NULL); + bftw_list_sort(&state->batch); } if (state->strategy == BFTW_DFS) { - slist_extend(&state->batch, &state->queue); + SLIST_EXTEND(&state->batch, &state->queue); } - slist_extend(&state->queue, &state->batch); + SLIST_EXTEND(&state->queue, &state->batch); } /** diff --git a/src/config.h b/src/config.h index cee8511..34ae11d 100644 --- a/src/config.h +++ b/src/config.h @@ -147,17 +147,6 @@ */ #define BFS_COUNTOF(array) (sizeof(array) / sizeof(0[array])) -// BFS_CONTAINER_OF() helper -static inline char *bfs_container_offset(char *ptr, ptrdiff_t offset, size_t unused) { - return ptr ? ptr - offset : NULL; -} - -/** - * Move a pointer from a field to its outer struct. - */ -#define BFS_CONTAINER_OF(ptr, type, member) \ - ((type *)bfs_container_offset((char *)(ptr), offsetof(type, member), sizeof((ptr) - &((type *)NULL)->member))) - // Lower bound on BFS_FLEX_SIZEOF() #define BFS_FLEX_LB(type, member, length) (offsetof(type, member) + sizeof(((type *)NULL)->member[0]) * (length)) diff --git a/src/list.c b/src/list.c deleted file mode 100644 index dffee19..0000000 --- a/src/list.c +++ /dev/null @@ -1,169 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include "list.h" -#include -#include - -void slink_init(struct slink *link) { - link->next = NULL; -} - -void slist_init(struct slist *list) { - list->head = NULL; - list->tail = &list->head; -} - -bool slist_is_empty(const struct slist *list) { - return !list->head; -} - -void slist_append(struct slist *list, struct slink *link) { - assert(!link->next); - *list->tail = link; - list->tail = &link->next; -} - -void slist_prepend(struct slist *list, struct slink *link) { - assert(!link->next); - if (!list->head) { - list->tail = &link->next; - } - link->next = list->head; - list->head = link; -} - -void slist_extend(struct slist *dest, struct slist *src) { - if (src->head) { - *dest->tail = src->head; - dest->tail = src->tail; - slist_init(src); - } -} - -struct slink *slist_pop(struct slist *list) { - struct slink *head = list->head; - if (!head) { - return NULL; - } - - list->head = head->next; - if (!list->head) { - list->tail = &list->head; - } - - head->next = NULL; - return head; -} - -void slist_sort(struct slist *list, slist_cmp_fn *cmp_fn, const void *ptr) { - if (!list->head || !list->head->next) { - return; - } - - struct slist left, right; - slist_init(&left); - slist_init(&right); - - // Split - for (struct slink *hare = list->head; hare && (hare = hare->next); hare = hare->next) { - slist_append(&left, slist_pop(list)); - } - slist_extend(&right, list); - - // Recurse - slist_sort(&left, cmp_fn, ptr); - slist_sort(&right, cmp_fn, ptr); - - // Merge - while (left.head && right.head) { - if (cmp_fn(left.head, right.head, ptr)) { - slist_append(list, slist_pop(&left)); - } else { - slist_append(list, slist_pop(&right)); - } - } - slist_extend(list, &left); - slist_extend(list, &right); -} - -void link_init(struct link *link) { - link->prev = NULL; - link->next = NULL; -} - -void list_init(struct list *list) { - list->head = NULL; - list->tail = NULL; -} - -bool list_is_empty(const struct list *list) { - return !list->head; -} - -void list_append(struct list *list, struct link *link) { - list_insert_after(list, list->tail, link); -} - -void list_prepend(struct list *list, struct link *link) { - list_insert_after(list, NULL, link); -} - -void list_insert_after(struct list *list, struct link *target, struct link *link) { - assert(!list_attached(list, link)); - - if (target) { - link->prev = target; - link->next = target->next; - } else { - link->next = list->head; - } - - if (link->prev) { - link->prev->next = link; - } else { - list->head = link; - } - - if (link->next) { - link->next->prev = link; - } else { - list->tail = link; - } -} - -void list_remove(struct list *list, struct link *link) { - if (link->prev) { - assert(list->head != link); - link->prev->next = link->next; - } else { - assert(list->head == link); - list->head = link->next; - } - - if (link->next) { - assert(list->tail != link); - link->next->prev = link->prev; - } else { - assert(list->tail == link); - list->tail = link->prev; - } - - link->prev = NULL; - link->next = NULL; -} - -struct link *list_pop(struct list *list) { - struct link *head = list->head; - if (!head) { - return NULL; - } - - list_remove(list, head); - return head; -} - -bool list_attached(const struct list *list, const struct link *link) { - return link->prev || list->head == link - || link->next || list->tail == link; -} diff --git a/src/list.h b/src/list.h index b47bed7..c43be68 100644 --- a/src/list.h +++ b/src/list.h @@ -3,225 +3,354 @@ /** * Intrusive linked lists. + * + * Singly-linked lists are declared like this: + * + * struct item { + * struct item *next; + * }; + * + * struct list { + * struct item *head; + * struct item **tail; + * }; + * + * The SLIST_*() macros manipulate singly-linked lists. + * + * struct list list; + * SLIST_INIT(&list); + * + * struct item item; + * SLIST_APPEND(&list, &item); + * + * Doubly linked lists are similar: + * + * struct item { + * struct item *next; + * struct item *prev; + * }; + * + * struct list { + * struct item *head; + * struct item *tail; + * }; + * + * struct list list; + * LIST_INIT(&list); + * + * struct item item; + * LIST_APPEND(&list, &item); + * + * Items can be on multiple lists at once: + * + * struct item { + * struct { + * struct item *next; + * } chain; + * + * struct { + * struct item *next; + * struct item *prev; + * } lru; + * }; + * + * struct items { + * struct { + * struct item *head; + * struct item **tail; + * } queue; + * + * struct { + * struct item *head; + * struct item *tail; + * } cache; + * }; + * + * struct items items; + * SLIST_INIT(&items.queue); + * LIST_INIT(&items.cache); + * + * struct item item; + * SLIST_APPEND(&items.queue, &item, chain); + * LIST_APPEND(&items.cache, &item, lru); */ #ifndef BFS_LIST_H #define BFS_LIST_H -#include "config.h" -#include +#include /** - * A singly-linked list entry. + * Initialize a singly-linked list. + * + * @param list + * The list to initialize. + * + * --- + * + * Like many macros in this file, this macro delegates the bulk of its work to + * some helper macros. We explicitly parenthesize (list) here so the helpers + * don't have to. */ -struct slink { - struct slink *next; -}; +#define SLIST_INIT(list) \ + LIST_BLOCK_(SLIST_INIT_((list))) -/** Initialize a list entry. */ -void slink_init(struct slink *link); +#define SLIST_INIT_(list) \ + (list)->head = NULL; \ + (list)->tail = &(list)->head; /** - * A singly-linked list. + * Wraps a group of statements in a block. */ -struct slist { - struct slink *head; - struct slink **tail; -}; - -/** Initialize an empty list. */ -void slist_init(struct slist *list); - -/** Check if a list is empty. */ -bool slist_is_empty(const struct slist *list); - -/** Add an entry at the tail of the list. */ -void slist_append(struct slist *list, struct slink *link); - -/** Add an entry at the head of the list. */ -void slist_prepend(struct slist *list, struct slink *link); - -/** Add an entire list at the tail of the list. */ -void slist_extend(struct slist *dest, struct slist *src); - -/** Remove the head of the list. */ -struct slink *slist_pop(struct slist *list); +#define LIST_BLOCK_(block) do { block } while (0) /** - * Comparison function type for slist_sort(). + * Add an item to the tail of a singly-linked list. * - * @param left - * The left-hand side of the comparison. - * @param right - * The right-hand side of the comparison. - * @param ptr - * An arbitrary pointer passed to slist_sort(). - * @return - * Whether left <= right. + * @param list + * The list to modify. + * @param item + * The item to append. + * @param link (optional) + * If specified, use item->link.next rather than item->next. + * + * --- + * + * We play some tricks with variadic macros to handle the optional parameter: + * + * SLIST_APPEND(list, item) => { + * *list->tail = item; + * list->tail = &item->next; + * } + * + * SLIST_APPEND(list, item, link) => { + * *list->tail = item; + * list->tail = &item->link.next; + * } + * + * The first trick is that + * + * #define SLIST_APPEND(list, 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_APPEND(list, item) => SLIST_APPEND_(list, item, ) + * SLIST_APPEND(list, item, link) => SLIST_APPEND_(list, item, link, ) */ -typedef bool slist_cmp_fn(struct slink *left, struct slink *right, const void *ptr); +#define SLIST_APPEND(list, ...) SLIST_APPEND_(list, __VA_ARGS__, ) -/** Sort a list. */ -void slist_sort(struct slist *list, slist_cmp_fn *cmp_fn, const void *ptr); +/** + * Now we need a way to generate either ->next or ->link.next depending on + * whether the link 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_LINK_(next, ) + * LIST_NEXT_(link, ) => LIST_LINK_(next, link, ) + */ +#define LIST_NEXT_(...) LIST_LINK_(next, __VA_ARGS__) /** - * A doubly-linked list entry. + * LIST_LINK_() dispatches to yet another macro: + * + * LIST_LINK_(next, ) => LIST_LINK__(next, , , . , , ) + * LIST_LINK_(next, link, ) => LIST_LINK__(next, link, , , . , , ) */ -struct link { - struct link *prev; - struct link *next; -}; +#define LIST_LINK_(dir, ...) LIST_LINK__(dir, __VA_ARGS__, , . , , ) -/** Initialize a list entry. */ -void link_init(struct link *link); +/** + * And finally, LIST_LINK__() adds the link and the dot if necessary. + * + * dir link blank ignored dot + * v v v v v + * LIST_LINK__(next, , , . , , ) => next + * LIST_LINK__(next, link, , , . , , ) => link . next + * ^ ^ ^ ^ ^ + * dir link blank ignored dot + */ +#define LIST_LINK__(dir, link, blank, ignored, dot, ...) link dot dir /** - * A doubly-linked list. + * SLIST_APPEND_() uses LIST_NEXT_() to generate the right name for the list + * link, and finally delegates to the actual implementation. + * + * SLIST_APPEND_(list, item, ) => SLIST_APPEND__((list), (item), next) + * SLIST_APPEND_(list, item, link, ) => SLIST_APPEND__((list), (item), link.next) */ -struct list { - struct link *head; - struct link *tail; -}; +#define SLIST_APPEND_(list, item, ...) \ + LIST_BLOCK_(SLIST_APPEND__((list), (item), LIST_NEXT_(__VA_ARGS__))) -/** Initialize an empty list. */ -void list_init(struct list *list); +#define SLIST_APPEND__(list, item, next) \ + *list->tail = item; \ + list->tail = &item->next; -/** Check if a list is empty. */ -bool list_is_empty(const struct list *list); +/** + * Add an item to the head of a singly-linked list. + * + * @param list + * The list to modify. + * @param item + * The item to prepend. + * @param link (optional) + * If specified, use item->link.next rather than item->next. + */ +#define SLIST_PREPEND(list, ...) SLIST_PREPEND_(list, __VA_ARGS__, ) -/** Add an entry at the tail of the list. */ -void list_append(struct list *list, struct link *link); +#define SLIST_PREPEND_(list, item, ...) \ + LIST_BLOCK_(SLIST_PREPEND__((list), (item), LIST_NEXT_(__VA_ARGS__))) -/** Add an entry at the head of the list. */ -void list_prepend(struct list *list, struct link *link); +#define SLIST_PREPEND__(list, item, next) \ + list->tail = list->head ? list->tail : &item->next; \ + item->next = list->head; \ + list->head = item; -/** Insert an entry after the target entry. */ -void list_insert_after(struct list *list, struct link *target, struct link *link); +/** + * Add an entire singly-linked list to the tail of another. + * + * @param dest + * The destination list. + * @param src + * The source list. + */ +#define SLIST_EXTEND(dest, src) \ + LIST_BLOCK_(SLIST_EXTEND_((dest), (src))) -/** Remove an entry from a list. */ -void list_remove(struct list *list, struct link *link); +#define SLIST_EXTEND_(dest, src) \ + if (src->head) { \ + *dest->tail = src->head; \ + dest->tail = src->tail; \ + SLIST_INIT(src); \ + } -/** Remove the head of the list. */ -struct link *list_pop(struct list *list); +/** + * Pop the head off a singly-linked list. + * + * @param list + * The list to pop from. + * @param link (optional) + * If specified, use head->link.next rather than head->next. + */ +#define SLIST_POP(...) SLIST_POP_(__VA_ARGS__, ) -/** Check if a link is attached to a list. */ -bool list_attached(const struct list *list, const struct link *link); +#define SLIST_POP_(list, ...) \ + LIST_BLOCK_(SLIST_POP__((list), LIST_NEXT_(__VA_ARGS__))) -// LIST_ITEM() helper -#define LIST_ITEM_IMPL(type, entry, member, ...) \ - BFS_CONTAINER_OF(entry, type, member) +#define SLIST_POP__(list, next) \ + void *_next = (void *)list->head->next; \ + list->head->next = NULL; \ + list->head = _next; \ + list->tail = list->head ? list->tail : &list->head; /** - * Convert a list entry to its container. + * Initialize a doubly-linked list. * - * @param type - * The type of the list entries. - * @param entry - * The list entry to convert. - * @param member - * The name of the list link field (default: link). - * @return - * The item that contains the given entry. + * @param list + * The list to initialize. */ -#define LIST_ITEM(...) \ - LIST_ITEM_IMPL(__VA_ARGS__, link,) +#define LIST_INIT(list) \ + LIST_BLOCK_(LIST_INIT_((list))) -// LIST_NEXT() helper -#define LIST_NEXT_IMPL(type, entry, member, ...) \ - LIST_ITEM(type, (entry)->member.next, member) +#define LIST_INIT_(list) \ + list->head = list->tail = NULL; /** - * Get the next item in a list. - * - * @param type - * The type of the list entries. - * @param entry - * The current entry. - * @param member - * The name of the list link field (default: link). - * @return - * The next item in the list. + * LIST_PREV_() => prev + * LIST_PREV_(link, ) => link.prev */ -#define LIST_NEXT(...) \ - LIST_NEXT_IMPL(__VA_ARGS__, link,) - -// LIST_PREV() helper -#define LIST_PREV_IMPL(type, entry, member, ...) \ - LIST_ITEM(type, (entry)->member.prev, member) +#define LIST_PREV_(...) LIST_LINK_(prev, __VA_ARGS__) /** - * Get the previous entry in a list. + * Add an item to the tail of a doubly-linked list. * - * @param type - * The type of the list entries. - * @param entry - * The current entry. - * @param member - * The name of the list link field (default: link). - * @return - * The previous item in the list. + * @param list + * The list to modify. + * @param item + * The item to append. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. */ -#define LIST_PREV(...) \ - LIST_PREV_IMPL(__VA_ARGS__, link,) +#define LIST_APPEND(list, ...) LIST_INSERT(list, (list)->tail, __VA_ARGS__) -// Helper for LIST_FOR_EACH_*() -#define LIST_FOR_EACH_IMPL(entry, type, i, member, ...) \ - for (type *_next, *i = LIST_ITEM(type, entry, member); \ - i && (_next = LIST_NEXT(type, i, member), true); \ - i = _next) +/** + * Add an item to the head of a doubly-linked list. + * + * @param list + * The list to modify. + * @param item + * The item to prepend. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. + */ +#define LIST_PREPEND(list, ...) LIST_INSERT(list, NULL, __VA_ARGS__) /** - * Iterate over a list from the given entry. + * Insert into a doubly-linked list after the given cursor. * - * @param entry - * The entry to start from. - * @param type - * The type of the list entries. - * @param i - * The name of the loop variable, declared as type *i. - * @param member - * The name of the list link field (default: link). + * @param list + * The list to initialize. + * @param cursor + * Insert after this element. + * @param item + * The item to insert. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. */ -#define LIST_FOR_EACH_FROM(...) \ - LIST_FOR_EACH_IMPL(__VA_ARGS__, link,) +#define LIST_INSERT(list, cursor, ...) LIST_INSERT_(list, cursor, __VA_ARGS__, ) + +#define LIST_INSERT_(list, cursor, item, ...) \ + LIST_BLOCK_(LIST_INSERT__((list), (cursor), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__))) + +#define LIST_INSERT__(list, cursor, item, prev, next) \ + item->prev = cursor; \ + item->next = cursor ? cursor->next : list->head; \ + *(item->prev ? &item->prev->next : &list->head) = item; \ + *(item->next ? &item->next->prev : &list->tail) = item; /** - * Iterate over a list. + * Remove an item from a doubly-linked list. * * @param list - * The list to iterate over. - * @param type - * The type of the list entries. - * @param i - * The name of the loop variable, declared as type *i. - * @param member - * The name of the list link field (default: link). + * The list to modify. + * @param item + * The item to remove. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. */ -#define LIST_FOR_EACH(list, ...) \ - LIST_FOR_EACH_FROM((list)->head, __VA_ARGS__) +#define LIST_REMOVE(list, ...) LIST_REMOVE_(list, __VA_ARGS__, ) -// Pop from a list or slist -#define LIST_POP(l) _Generic((l), \ - struct list *: list_pop((struct list *)l), \ - struct slist *: slist_pop((struct slist *)l)) +#define LIST_REMOVE_(list, item, ...) \ + LIST_BLOCK_(LIST_REMOVE__((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__))) -// Helper for LIST_DRAIN() -#define LIST_DRAIN_IMPL(list, type, i, member, ...) \ - for (type *i; (i = LIST_ITEM(type, LIST_POP(list), member));) +#define LIST_REMOVE__(list, item, prev, next) \ + *(item->prev ? &item->prev->next : &list->head) = item->next; \ + *(item->next ? &item->next->prev : &list->tail) = item->prev; \ + item->prev = item->next = NULL; /** - * Drain the entries from a list. + * Check if an item is attached to a doubly-linked list. * * @param list - * The list to drain. - * @param type - * The type of the list entries. - * @param i - * The name of the loop variable, declared as type *i. - * @param member - * The name of the list link field (default: link). - */ -#define LIST_DRAIN(...) \ - LIST_DRAIN_IMPL(__VA_ARGS__, link,) + * The list to check. + * @param item + * The item to check. + * @param link (optional) + * If specified, use item->link.{prev,next} rather than item->{prev,next}. + * @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, prev, next) \ + (item->prev || item->next || list->head == item || list->tail == item) #endif // BFS_LIST_H diff --git a/src/trie.c b/src/trie.c index 7b00f4b..43df9dc 100644 --- a/src/trie.c +++ b/src/trie.c @@ -83,6 +83,7 @@ #include "trie.h" #include "config.h" +#include "list.h" #include #include #include @@ -163,7 +164,7 @@ static uintptr_t trie_encode_node(const struct trie_node *node) { void trie_init(struct trie *trie) { trie->root = 0; - list_init(&trie->leaves); + LIST_INIT(trie); } /** Check if a number is a power of two. */ @@ -340,8 +341,7 @@ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, siz return NULL; } - link_init(&leaf->link); - list_append(&trie->leaves, &leaf->link); + LIST_APPEND(trie, leaf); leaf->value = NULL; leaf->length = length; @@ -352,7 +352,7 @@ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, siz /** Free a leaf. */ static void trie_leaf_free(struct trie *trie, struct trie_leaf *leaf) { - list_remove(&trie->leaves, &leaf->link); + LIST_REMOVE(trie, leaf); free(leaf); } diff --git a/src/trie.h b/src/trie.h index 6e1e875..58974aa 100644 --- a/src/trie.h +++ b/src/trie.h @@ -5,7 +5,6 @@ #define BFS_TRIE_H #include "config.h" -#include "list.h" #include #include #include @@ -15,7 +14,7 @@ */ struct trie_leaf { /** Linked list of leaves, in insertion order. */ - struct link link; + struct trie_leaf *prev, *next; /** An arbitrary value associated with this leaf. */ void *value; /** The length of the key in bytes. */ @@ -31,7 +30,7 @@ struct trie { /** Pointer to the root node/leaf. */ uintptr_t root; /** Linked list of leaves. */ - struct list leaves; + struct trie_leaf *head, *tail; }; /** @@ -134,6 +133,8 @@ void trie_destroy(struct trie *trie); * Iterate over the leaves of a trie. */ #define TRIE_FOR_EACH(trie, leaf) \ - LIST_FOR_EACH(&(trie)->leaves, struct trie_leaf, leaf) + for (struct trie_leaf *leaf = (trie)->head, *_next; \ + leaf && (_next = leaf->next, true); \ + leaf = _next) #endif // BFS_TRIE_H diff --git a/src/xspawn.c b/src/xspawn.c index e6ce0de..a185200 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -33,7 +33,7 @@ enum bfs_spawn_op { * A spawn action. */ struct bfs_spawn_action { - struct slink link; + struct bfs_spawn_action *next; enum bfs_spawn_op op; int in_fd; @@ -44,12 +44,14 @@ struct bfs_spawn_action { int bfs_spawn_init(struct bfs_spawn *ctx) { ctx->flags = 0; - slist_init(&ctx->actions); + SLIST_INIT(ctx); return 0; } int bfs_spawn_destroy(struct bfs_spawn *ctx) { - LIST_DRAIN(&ctx->actions, struct bfs_spawn_action, action) { + while (ctx->head) { + struct bfs_spawn_action *action = ctx->head; + SLIST_POP(ctx); free(action); } @@ -68,12 +70,12 @@ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_sp return NULL; } - slink_init(&action->link); + action->next = NULL; action->op = op; action->in_fd = -1; action->out_fd = -1; - slist_append(&ctx->actions, &action->link); + SLIST_APPEND(ctx, action); return action; } @@ -138,8 +140,7 @@ int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rli static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { xclose(pipefd[0]); - const struct slink *head = ctx ? ctx->actions.head : NULL; - LIST_FOR_EACH_FROM(head, struct bfs_spawn_action, action) { + for (const struct bfs_spawn_action *action = ctx ? ctx->head : NULL; action; action = action->next) { // Move the error-reporting pipe out of the way if necessary... if (action->out_fd == pipefd[1]) { int fd = dup_cloexec(pipefd[1]); diff --git a/src/xspawn.h b/src/xspawn.h index 7e673f1..d9b4a2e 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -8,7 +8,6 @@ #ifndef BFS_XSPAWN_H #define BFS_XSPAWN_H -#include "list.h" #include #include @@ -25,7 +24,8 @@ enum bfs_spawn_flags { */ struct bfs_spawn { enum bfs_spawn_flags flags; - struct slist actions; + struct bfs_spawn_action *head; + struct bfs_spawn_action **tail; }; /** diff --git a/tests/trie.c b/tests/trie.c index 65660a9..c2af18a 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -74,6 +74,8 @@ int main(void) { size_t i = 0; TRIE_FOR_EACH(&trie, leaf) { assert(leaf == trie_find_str(&trie, keys[i])); + assert(!leaf->prev || leaf->prev->next == leaf); + assert(!leaf->next || leaf->next->prev == leaf); ++i; } assert(i == nkeys); -- cgit v1.2.3 From 536c7e7af3777bc8b8d7f919402aaff51b7cda9d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 1 Apr 2023 14:25:00 -0400 Subject: list: Simplify some macros --- src/list.h | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/list.h b/src/list.h index c43be68..39510e0 100644 --- a/src/list.h +++ b/src/list.h @@ -96,8 +96,8 @@ LIST_BLOCK_(SLIST_INIT_((list))) #define SLIST_INIT_(list) \ - (list)->head = NULL; \ - (list)->tail = &(list)->head; + list->head = NULL; \ + list->tail = &list->head; /** * Wraps a group of statements in a block. @@ -160,22 +160,22 @@ /** * LIST_LINK_() dispatches to yet another macro: * - * LIST_LINK_(next, ) => LIST_LINK__(next, , , . , , ) - * LIST_LINK_(next, link, ) => LIST_LINK__(next, link, , , . , , ) + * LIST_LINK_(next, ) => LIST_LINK__(next, , . , , ) + * LIST_LINK_(next, link, ) => LIST_LINK__(next, link, , . , , ) */ -#define LIST_LINK_(dir, ...) LIST_LINK__(dir, __VA_ARGS__, , . , , ) +#define LIST_LINK_(dir, ...) LIST_LINK__(dir, __VA_ARGS__, . , , ) /** * And finally, LIST_LINK__() adds the link and the dot if necessary. * - * dir link blank ignored dot - * v v v v v - * LIST_LINK__(next, , , . , , ) => next - * LIST_LINK__(next, link, , , . , , ) => link . next - * ^ ^ ^ ^ ^ - * dir link blank ignored dot + * dir link ignored dot + * v v v v + * LIST_LINK__(next, , . , , ) => next + * LIST_LINK__(next, link, , . , , ) => link . next + * ^ ^ ^ ^ + * dir link ignored dot */ -#define LIST_LINK__(dir, link, blank, ignored, dot, ...) link dot dir +#define LIST_LINK__(dir, link, ignored, dot, ...) link dot dir /** * SLIST_APPEND_() uses LIST_NEXT_() to generate the right name for the list -- cgit v1.2.3 From 10e8327badb15bd8dd229a7a25bdcd28c5629e1c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 1 Apr 2023 14:25:06 -0400 Subject: list: Implement SLIST_REMOVE() --- src/list.h | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/src/list.h b/src/list.h index 39510e0..95917e7 100644 --- a/src/list.h +++ b/src/list.h @@ -229,6 +229,27 @@ SLIST_INIT(src); \ } +/** + * Remove an item from a singly-linked list. + * + * @param list + * The list to remove from. + * @param ptr + * A pointer to the item to remove, either &list->head or &prev->next. + * @param link (optional) + * If specified, use item->link.next rather than item->next. + */ +#define SLIST_REMOVE(list, ...) SLIST_REMOVE_(__VA_ARGS__, ) + +#define SLIST_REMOVE_(list, ptr, ...) \ + LIST_BLOCK_(SLIST_REMOVE__((list), (ptr), LIST_NEXT_(__VA_ARGS__))) + +#define SLIST_REMOVE__(list, ptr, next) \ + void *_next = (void *)(*ptr)->next; \ + (*ptr)->next = NULL; \ + *ptr = _next; \ + list->tail = list->head ? list->tail : &list->head; + /** * Pop the head off a singly-linked list. * @@ -239,14 +260,9 @@ */ #define SLIST_POP(...) SLIST_POP_(__VA_ARGS__, ) -#define SLIST_POP_(list, ...) \ - LIST_BLOCK_(SLIST_POP__((list), LIST_NEXT_(__VA_ARGS__))) +#define SLIST_POP_(list, ...) SLIST_POP__((list), LIST_NEXT_(__VA_ARGS__)) -#define SLIST_POP__(list, next) \ - void *_next = (void *)list->head->next; \ - list->head->next = NULL; \ - list->head = _next; \ - list->tail = list->head ? list->tail : &list->head; +#define SLIST_POP__(list, next) SLIST_REMOVE__(list, (&list->head), next) /** * Initialize a doubly-linked list. -- cgit v1.2.3 From ecbc4a7572cb67802b1cb99d8914c19f1fb4f2c4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 1 Apr 2023 14:45:15 -0400 Subject: list: Fix a typo in SLIST_REMOVE() --- src/list.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/list.h b/src/list.h index 95917e7..08f97ab 100644 --- a/src/list.h +++ b/src/list.h @@ -239,7 +239,7 @@ * @param link (optional) * If specified, use item->link.next rather than item->next. */ -#define SLIST_REMOVE(list, ...) SLIST_REMOVE_(__VA_ARGS__, ) +#define SLIST_REMOVE(list, ...) SLIST_REMOVE_(list, __VA_ARGS__, ) #define SLIST_REMOVE_(list, ptr, ...) \ LIST_BLOCK_(SLIST_REMOVE__((list), (ptr), LIST_NEXT_(__VA_ARGS__))) -- cgit v1.2.3 From c4a4d397ccc124f50b851cfa52b9937ba7485b2b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 1 Apr 2023 18:09:48 -0400 Subject: list: Fix SLIST_REMOVE() on the tail --- src/list.h | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/list.h b/src/list.h index 08f97ab..a674aaf 100644 --- a/src/list.h +++ b/src/list.h @@ -234,21 +234,21 @@ * * @param list * The list to remove from. - * @param ptr + * @param cursor * A pointer to the item to remove, either &list->head or &prev->next. * @param link (optional) * If specified, use item->link.next rather than item->next. */ #define SLIST_REMOVE(list, ...) SLIST_REMOVE_(list, __VA_ARGS__, ) -#define SLIST_REMOVE_(list, ptr, ...) \ - LIST_BLOCK_(SLIST_REMOVE__((list), (ptr), LIST_NEXT_(__VA_ARGS__))) +#define SLIST_REMOVE_(list, cursor, ...) \ + LIST_BLOCK_(SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__))) -#define SLIST_REMOVE__(list, ptr, next) \ - void *_next = (void *)(*ptr)->next; \ - (*ptr)->next = NULL; \ - *ptr = _next; \ - list->tail = list->head ? list->tail : &list->head; +#define SLIST_REMOVE__(list, cursor, next) \ + void *_next = (void *)(*cursor)->next; \ + (*cursor)->next = NULL; \ + *cursor = _next; \ + list->tail = _next ? list->tail : cursor; /** * Pop the head off a singly-linked list. -- cgit v1.2.3 From fd0f8a3d42a5b59f471ba77df31c4e45ab9c01f3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 7 Apr 2023 11:13:33 -0400 Subject: dir: Allow overriding BFS_GETDENTS --- src/dir.c | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/src/dir.c b/src/dir.c index b92b7c2..2719c15 100644 --- a/src/dir.c +++ b/src/dir.c @@ -4,6 +4,7 @@ #include "dir.h" #include "bfstd.h" #include "config.h" +#include #include #include #include @@ -14,22 +15,17 @@ #include #include -#if __has_feature(memory_sanitizer) -# include -#endif - -#if __linux__ -# include - -/** Directory entry type for bfs_getdents() */ -typedef struct dirent64 sys_dirent; -#else -typedef struct dirent sys_dirent; +#ifndef BFS_GETDENTS +# define BFS_GETDENTS (__linux__ || __FreeBSD__) #endif -#define BFS_GETDENTS (__linux__ || __FreeBSD__) - #if BFS_GETDENTS +# if __has_feature(memory_sanitizer) +# include +# endif +# if __linux__ +# include +# endif /** getdents() syscall wrapper. */ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { @@ -56,6 +52,13 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { #endif // BFS_GETDENTS +#if BFS_GETDENTS && __linux__ +/** Directory entry type for bfs_getdents() */ +typedef struct dirent64 sys_dirent; +#else +typedef struct dirent sys_dirent; +#endif + enum bfs_type bfs_mode_to_type(mode_t mode) { switch (mode & S_IFMT) { #ifdef S_IFBLK @@ -112,7 +115,6 @@ struct bfs_dir { // sys_dirent buf[]; #else DIR *dir; - struct dirent *de; #endif }; @@ -158,8 +160,6 @@ struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { free(dir); return NULL; } - - dir->de = NULL; #endif return dir; @@ -251,6 +251,7 @@ int bfs_closedir(struct bfs_dir *dir) { int ret = xclose(dir->fd); #else int ret = closedir(dir->dir); + assert(ret == 0 || errno != EBADF); #endif free(dir); return ret; @@ -260,10 +261,14 @@ int bfs_freedir(struct bfs_dir *dir) { #if BFS_GETDENTS int ret = dir->fd; free(dir); - return ret; +#elif __FreeBSD__ + int ret = fdclosedir(dir->dir); + free(dir); #else int ret = dup_cloexec(dirfd(dir->dir)); + int error = errno; bfs_closedir(dir); - return ret; + errno = error; #endif + return ret; } -- cgit v1.2.3 From 520c8302187538c3c9adab822d7268cbb22565e7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 7 Apr 2023 13:06:08 -0400 Subject: bfstd: Fix declaration order to match the right standard headers --- src/bfstd.c | 148 ++++++++++++++++++++++++++++++------------------------------ src/bfstd.h | 84 +++++++++++++++++----------------- 2 files changed, 116 insertions(+), 116 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 437e9c9..bc31288 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -82,20 +82,6 @@ size_t xbaseoff(const char *path) { return i; } -void close_quietly(int fd) { - int error = errno; - xclose(fd); - errno = error; -} - -int xclose(int fd) { - int ret = close(fd); - if (ret != 0) { - assert(errno != EBADF); - } - return ret; -} - FILE *xfopen(const char *path, int flags) { char mode[4]; @@ -157,52 +143,6 @@ char *xgetdelim(FILE *file, char delim) { } } -size_t xread(int fd, void *buf, size_t nbytes) { - size_t count = 0; - - while (count < nbytes) { - ssize_t ret = read(fd, (char *)buf + count, nbytes - count); - if (ret < 0) { - if (errno == EINTR) { - continue; - } else { - break; - } - } else if (ret == 0) { - // EOF - errno = 0; - break; - } else { - count += ret; - } - } - - return count; -} - -size_t xwrite(int fd, const void *buf, size_t nbytes) { - size_t count = 0; - - while (count < nbytes) { - ssize_t ret = write(fd, (const char *)buf + count, nbytes - count); - if (ret < 0) { - if (errno == EINTR) { - continue; - } else { - break; - } - } else if (ret == 0) { - // EOF? - errno = 0; - break; - } else { - count += ret; - } - } - - return count; -} - /** Compile and execute a regular expression for xrpmatch(). */ static int xrpregex(nl_item item, const char *response) { const char *pattern = nl_langinfo(item); @@ -398,6 +338,80 @@ int pipe_cloexec(int pipefd[2]) { #endif } +size_t xread(int fd, void *buf, size_t nbytes) { + size_t count = 0; + + while (count < nbytes) { + ssize_t ret = read(fd, (char *)buf + count, nbytes - count); + if (ret < 0) { + if (errno == EINTR) { + continue; + } else { + break; + } + } else if (ret == 0) { + // EOF + errno = 0; + break; + } else { + count += ret; + } + } + + return count; +} + +size_t xwrite(int fd, const void *buf, size_t nbytes) { + size_t count = 0; + + while (count < nbytes) { + ssize_t ret = write(fd, (const char *)buf + count, nbytes - count); + if (ret < 0) { + if (errno == EINTR) { + continue; + } else { + break; + } + } else if (ret == 0) { + // EOF? + errno = 0; + break; + } else { + count += ret; + } + } + + return count; +} + +void close_quietly(int fd) { + int error = errno; + xclose(fd); + errno = error; +} + +int xclose(int fd) { + int ret = close(fd); + if (ret != 0) { + assert(errno != EBADF); + } + return ret; +} + +int xfaccessat(int fd, const char *path, int amode) { + int ret = faccessat(fd, path, amode, 0); + +#ifdef AT_EACCESS + // Some platforms, like Hurd, only support AT_EACCESS. Other platforms, + // like Android, don't support AT_EACCESS at all. + if (ret != 0 && (errno == EINVAL || errno == ENOTSUP)) { + ret = faccessat(fd, path, amode, AT_EACCESS); + } +#endif + + return ret; +} + char *xconfstr(int name) { #if __ANDROID__ errno = ENOTSUP; @@ -422,20 +436,6 @@ char *xconfstr(int name) { #endif // !__ANDROID__ } -int xfaccessat(int fd, const char *path, int amode) { - int ret = faccessat(fd, path, amode, 0); - -#ifdef AT_EACCESS - // Some platforms, like Hurd, only support AT_EACCESS. Other platforms, - // like Android, don't support AT_EACCESS at all. - if (ret != 0 && (errno == EINVAL || errno == ENOTSUP)) { - ret = faccessat(fd, path, amode, AT_EACCESS); - } -#endif - - return ret; -} - char *xreadlinkat(int fd, const char *path, size_t size) { ssize_t len; char *name = NULL; diff --git a/src/bfstd.h b/src/bfstd.h index 0e11b66..f85b74f 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -80,24 +80,6 @@ size_t xbaseoff(const char *path); #include -/** - * close() variant that preserves errno. - * - * @param fd - * The file descriptor to close. - */ -void close_quietly(int fd); - -/** - * close() wrapper that asserts the file descriptor is valid. - * - * @param fd - * The file descriptor to close. - * @return - * 0 on success, or -1 on error. - */ -int xclose(int fd); - /** * fopen() variant that takes open() style flags. * @@ -121,25 +103,6 @@ FILE *xfopen(const char *path, int flags); */ char *xgetdelim(FILE *file, char delim); -/** - * A safe version of read() that handles interrupted system calls and partial - * reads. - * - * @return - * The number of bytes read. A value != nbytes indicates an error - * (errno != 0) or end of file (errno == 0). - */ -size_t xread(int fd, void *buf, size_t nbytes); - -/** - * A safe version of write() that handles interrupted system calls and partial - * writes. - * - * @return - The number of bytes written. A value != nbytes indicates an error. - */ -size_t xwrite(int fd, const void *buf, size_t nbytes); - // #include /** @@ -210,14 +173,41 @@ int dup_cloexec(int fd); int pipe_cloexec(int pipefd[2]); /** - * Wrapper for confstr() that allocates with malloc(). + * A safe version of read() that handles interrupted system calls and partial + * reads. * - * @param name - * The ID of the confstr to look up. * @return - * The value of the confstr, or NULL on failure. + * The number of bytes read. A value != nbytes indicates an error + * (errno != 0) or end of file (errno == 0). */ -char *xconfstr(int name); +size_t xread(int fd, void *buf, size_t nbytes); + +/** + * A safe version of write() that handles interrupted system calls and partial + * writes. + * + * @return + The number of bytes written. A value != nbytes indicates an error. + */ +size_t xwrite(int fd, const void *buf, size_t nbytes); + +/** + * close() variant that preserves errno. + * + * @param fd + * The file descriptor to close. + */ +void close_quietly(int fd); + +/** + * close() wrapper that asserts the file descriptor is valid. + * + * @param fd + * The file descriptor to close. + * @return + * 0 on success, or -1 on error. + */ +int xclose(int fd); /** * Wrapper for faccessat() that handles some portability issues. @@ -238,6 +228,16 @@ int xfaccessat(int fd, const char *path, int amode); */ char *xreadlinkat(int fd, const char *path, size_t size); +/** + * Wrapper for confstr() that allocates with malloc(). + * + * @param name + * The ID of the confstr to look up. + * @return + * The value of the confstr, or NULL on failure. + */ +char *xconfstr(int name); + /** * Portability wrapper for strtofflags(). * -- cgit v1.2.3 From f2ec5c1538c82f7007935647d5e5b59ea10a6f83 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 12 Apr 2023 10:01:44 -0400 Subject: bftw: Merge bftw_closedir() into bftw_gc() --- src/bftw.c | 106 +++++++++++++++++++++++++++---------------------------------- 1 file changed, 47 insertions(+), 59 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 7bc724a..5cbe0c2 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -705,6 +705,10 @@ static void bftw_fill_id(struct bftw_file *file, const struct BFTW *ftwbuf) { * Visit a path, invoking the callback. */ static enum bftw_action bftw_visit(struct bftw_state *state, const char *name, enum bftw_visit visit) { + if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { + return BFTW_PRUNE; + } + if (bftw_update_path(state, name) != 0) { state->error = errno; return BFTW_STOP; @@ -814,13 +818,23 @@ static int bftw_build_path(struct bftw_state *state) { /** * Pop the next file from the queue. */ -static int bftw_pop(struct bftw_state *state) { +static bool bftw_pop(struct bftw_state *state) { state->file = state->queue.head; - if (!state->file) { - return 0; + if (state->file) { + SLIST_POP(&state->queue); + return true; + } else { + return false; } +} - SLIST_POP(&state->queue); +/** + * Start processing the next file in the queue. + */ +static int bftw_next(struct bftw_state *state) { + if (!bftw_pop(state)) { + return 0; + } if (bftw_build_path(state) != 0) { return -1; @@ -871,23 +885,25 @@ static int bftw_readdir(struct bftw_state *state) { enum bftw_gc_flags { /** Don't visit anything. */ BFTW_VISIT_NONE = 0, + /** Report directory errors. */ + BFTW_VISIT_ERROR = 1 << 0, /** Visit the file itself. */ - BFTW_VISIT_FILE = 1 << 0, + BFTW_VISIT_FILE = 1 << 1, /** Visit the file's ancestors. */ - BFTW_VISIT_PARENTS = 1 << 1, + BFTW_VISIT_PARENTS = 1 << 2, /** Visit both the file and its ancestors. */ - BFTW_VISIT_ALL = BFTW_VISIT_FILE | BFTW_VISIT_PARENTS, + BFTW_VISIT_ALL = BFTW_VISIT_ERROR | BFTW_VISIT_FILE | BFTW_VISIT_PARENTS, }; /** - * Close the current directory. + * Garbage collect the current file and its parents. */ -static enum bftw_action bftw_closedir(struct bftw_state *state, enum bftw_gc_flags flags) { - struct bftw_file *file = state->file; +static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { enum bftw_action ret = BFTW_CONTINUE; if (state->dir) { - assert(file->fd >= 0); + struct bftw_file *file = state->file; + assert(file && file->fd >= 0); if (file->refcount > 1) { // Keep the fd around if any subdirectories exist @@ -902,32 +918,22 @@ static enum bftw_action bftw_closedir(struct bftw_state *state, enum bftw_gc_fla } } - state->de = NULL; state->dir = NULL; + state->de = NULL; if (state->direrror != 0) { - if (flags & BFTW_VISIT_FILE) { - ret = bftw_visit(state, NULL, BFTW_PRE); + if (flags & BFTW_VISIT_ERROR) { + if (bftw_visit(state, NULL, BFTW_PRE) == BFTW_STOP) { + ret = BFTW_STOP; + flags = 0; + } } else { state->error = state->direrror; } state->direrror = 0; } - return ret; -} - -/** - * Finalize and free a file we're done with. - */ -static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flags flags) { - enum bftw_action ret = BFTW_CONTINUE; - - if (!(state->flags & BFTW_POST_ORDER)) { - flags = 0; - } - bool visit = flags & BFTW_VISIT_FILE; - + enum bftw_gc_flags visit = BFTW_VISIT_FILE; while (state->file) { struct bftw_file *file = state->file; if (--file->refcount > 0) { @@ -935,11 +941,13 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag break; } - if (visit && bftw_visit(state, NULL, BFTW_POST) == BFTW_STOP) { - ret = BFTW_STOP; - flags &= ~BFTW_VISIT_PARENTS; + if (flags & visit) { + if (bftw_visit(state, NULL, BFTW_POST) == BFTW_STOP) { + ret = BFTW_STOP; + flags = 0; + } } - visit = flags & BFTW_VISIT_PARENTS; + visit = BFTW_VISIT_PARENTS; struct bftw_file *parent = file->parent; if (state->previous == file) { @@ -952,18 +960,6 @@ static enum bftw_action bftw_gc_file(struct bftw_state *state, enum bftw_gc_flag return ret; } -/** - * Drain all the files from the queue. - */ -static void bftw_drain_queue(struct bftw_state *state) { - while (state->queue.head) { - state->file = state->queue.head; - SLIST_POP(&state->queue); - - bftw_gc_file(state, BFTW_VISIT_NONE); - } -} - /** * Dispose of the bftw() state. * @@ -973,10 +969,9 @@ static void bftw_drain_queue(struct bftw_state *state) { static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); - bftw_closedir(state, BFTW_VISIT_NONE); - - bftw_gc_file(state, BFTW_VISIT_NONE); - bftw_drain_queue(state); + do { + bftw_gc(state, BFTW_VISIT_NONE); + } while (bftw_pop(state)); bftw_cache_destroy(&state->cache); @@ -1064,7 +1059,7 @@ static int bftw_stream(const struct bftw_args *args) { } bftw_batch_finish(&state); - while (bftw_pop(&state) > 0) { + while (bftw_next(&state) > 0) { bftw_opendir(&state); while (bftw_readdir(&state) > 0) { @@ -1085,10 +1080,7 @@ static int bftw_stream(const struct bftw_args *args) { } bftw_batch_finish(&state); - if (bftw_closedir(&state, BFTW_VISIT_ALL) == BFTW_STOP) { - goto done; - } - if (bftw_gc_file(&state, BFTW_VISIT_ALL) == BFTW_STOP) { + if (bftw_gc(&state, BFTW_VISIT_ALL) == BFTW_STOP) { goto done; } } @@ -1113,7 +1105,7 @@ static int bftw_batch(const struct bftw_args *args) { } bftw_batch_finish(&state); - while (bftw_pop(&state) > 0) { + while (bftw_next(&state) > 0) { enum bftw_gc_flags gcflags = BFTW_VISIT_ALL; switch (bftw_visit(&state, NULL, BFTW_PRE)) { @@ -1135,12 +1127,8 @@ static int bftw_batch(const struct bftw_args *args) { } bftw_batch_finish(&state); - if (bftw_closedir(&state, gcflags) == BFTW_STOP) { - goto done; - } - next: - if (bftw_gc_file(&state, gcflags) == BFTW_STOP) { + if (bftw_gc(&state, gcflags) == BFTW_STOP) { goto done; } } -- cgit v1.2.3 From 17f2d430b4927c5862afdd038a629c71fae8771d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 12 Apr 2023 10:03:46 -0400 Subject: list: s/link/node/ --- src/list.h | 80 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 40 insertions(+), 40 deletions(-) diff --git a/src/list.h b/src/list.h index a674aaf..55242a3 100644 --- a/src/list.h +++ b/src/list.h @@ -111,8 +111,8 @@ * The list to modify. * @param item * The item to append. - * @param link (optional) - * If specified, use item->link.next rather than item->next. + * @param node (optional) + * If specified, use item->node.next rather than item->next. * * --- * @@ -123,9 +123,9 @@ * list->tail = &item->next; * } * - * SLIST_APPEND(list, item, link) => { + * SLIST_APPEND(list, item, node) => { * *list->tail = item; - * list->tail = &item->link.next; + * list->tail = &item->node.next; * } * * The first trick is that @@ -136,13 +136,13 @@ * workaround, we dispatch to another macro and add a trailing comma. * * SLIST_APPEND(list, item) => SLIST_APPEND_(list, item, ) - * SLIST_APPEND(list, item, link) => SLIST_APPEND_(list, item, link, ) + * SLIST_APPEND(list, item, node) => SLIST_APPEND_(list, item, node, ) */ #define SLIST_APPEND(list, ...) SLIST_APPEND_(list, __VA_ARGS__, ) /** - * Now we need a way to generate either ->next or ->link.next depending on - * whether the link parameter was passed. The approach is based on + * 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 @@ -152,37 +152,37 @@ * * The LIST_NEXT_() macro uses this technique: * - * LIST_NEXT_() => LIST_LINK_(next, ) - * LIST_NEXT_(link, ) => LIST_LINK_(next, link, ) + * LIST_NEXT_() => LIST_NODE_(next, ) + * LIST_NEXT_(node, ) => LIST_NODE_(next, node, ) */ -#define LIST_NEXT_(...) LIST_LINK_(next, __VA_ARGS__) +#define LIST_NEXT_(...) LIST_NODE_(next, __VA_ARGS__) /** - * LIST_LINK_() dispatches to yet another macro: + * LIST_NODE_() dispatches to yet another macro: * - * LIST_LINK_(next, ) => LIST_LINK__(next, , . , , ) - * LIST_LINK_(next, link, ) => LIST_LINK__(next, link, , . , , ) + * LIST_NODE_(next, ) => LIST_NODE__(next, , . , , ) + * LIST_NODE_(next, node, ) => LIST_NODE__(next, node, , . , , ) */ -#define LIST_LINK_(dir, ...) LIST_LINK__(dir, __VA_ARGS__, . , , ) +#define LIST_NODE_(dir, ...) LIST_NODE__(dir, __VA_ARGS__, . , , ) /** - * And finally, LIST_LINK__() adds the link and the dot if necessary. + * And finally, LIST_NODE__() adds the node and the dot if necessary. * - * dir link ignored dot + * dir node ignored dot * v v v v - * LIST_LINK__(next, , . , , ) => next - * LIST_LINK__(next, link, , . , , ) => link . next + * LIST_NODE__(next, , . , , ) => next + * LIST_NODE__(next, node, , . , , ) => node . next * ^ ^ ^ ^ - * dir link ignored dot + * dir node ignored dot */ -#define LIST_LINK__(dir, link, ignored, dot, ...) link dot dir +#define LIST_NODE__(dir, node, ignored, dot, ...) node dot dir /** * SLIST_APPEND_() uses LIST_NEXT_() to generate the right name for the list - * link, and finally delegates to the actual implementation. + * node, and finally delegates to the actual implementation. * * SLIST_APPEND_(list, item, ) => SLIST_APPEND__((list), (item), next) - * SLIST_APPEND_(list, item, link, ) => SLIST_APPEND__((list), (item), link.next) + * SLIST_APPEND_(list, item, node, ) => SLIST_APPEND__((list), (item), node.next) */ #define SLIST_APPEND_(list, item, ...) \ LIST_BLOCK_(SLIST_APPEND__((list), (item), LIST_NEXT_(__VA_ARGS__))) @@ -198,8 +198,8 @@ * The list to modify. * @param item * The item to prepend. - * @param link (optional) - * If specified, use item->link.next rather than item->next. + * @param node (optional) + * If specified, use item->node.next rather than item->next. */ #define SLIST_PREPEND(list, ...) SLIST_PREPEND_(list, __VA_ARGS__, ) @@ -236,8 +236,8 @@ * The list to remove from. * @param cursor * A pointer to the item to remove, either &list->head or &prev->next. - * @param link (optional) - * If specified, use item->link.next rather than item->next. + * @param node (optional) + * If specified, use item->node.next rather than item->next. */ #define SLIST_REMOVE(list, ...) SLIST_REMOVE_(list, __VA_ARGS__, ) @@ -255,8 +255,8 @@ * * @param list * The list to pop from. - * @param link (optional) - * If specified, use head->link.next rather than head->next. + * @param node (optional) + * If specified, use head->node.next rather than head->next. */ #define SLIST_POP(...) SLIST_POP_(__VA_ARGS__, ) @@ -278,9 +278,9 @@ /** * LIST_PREV_() => prev - * LIST_PREV_(link, ) => link.prev + * LIST_PREV_(node, ) => node.prev */ -#define LIST_PREV_(...) LIST_LINK_(prev, __VA_ARGS__) +#define LIST_PREV_(...) LIST_NODE_(prev, __VA_ARGS__) /** * Add an item to the tail of a doubly-linked list. @@ -289,8 +289,8 @@ * The list to modify. * @param item * The item to append. - * @param link (optional) - * If specified, use item->link.{prev,next} rather than item->{prev,next}. + * @param 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__) @@ -301,8 +301,8 @@ * The list to modify. * @param item * The item to prepend. - * @param link (optional) - * If specified, use item->link.{prev,next} rather than item->{prev,next}. + * @param node (optional) + * If specified, use item->node.{prev,next} rather than item->{prev,next}. */ #define LIST_PREPEND(list, ...) LIST_INSERT(list, NULL, __VA_ARGS__) @@ -315,8 +315,8 @@ * Insert after this element. * @param item * The item to insert. - * @param link (optional) - * If specified, use item->link.{prev,next} rather than item->{prev,next}. + * @param 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__, ) @@ -336,8 +336,8 @@ * The list to modify. * @param item * The item to remove. - * @param link (optional) - * If specified, use item->link.{prev,next} rather than item->{prev,next}. + * @param node (optional) + * If specified, use item->node.{prev,next} rather than item->{prev,next}. */ #define LIST_REMOVE(list, ...) LIST_REMOVE_(list, __VA_ARGS__, ) @@ -356,8 +356,8 @@ * The list to check. * @param item * The item to check. - * @param link (optional) - * If specified, use item->link.{prev,next} rather than item->{prev,next}. + * @param node (optional) + * If specified, use item->node.{prev,next} rather than item->{prev,next}. * @return * Whether the item is attached to the list. */ -- cgit v1.2.3 From a812c5c8c9aabf6e0fd934bd55ec553c6dbb60c2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 12 Apr 2023 10:32:06 -0400 Subject: list: New SLIST_INSERT() macro --- src/list.h | 71 ++++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/src/list.h b/src/list.h index 55242a3..87e2bf2 100644 --- a/src/list.h +++ b/src/list.h @@ -105,12 +105,14 @@ #define LIST_BLOCK_(block) do { block } while (0) /** - * Add an item to the tail of a singly-linked list. + * Insert an item into a singly-linked list. * * @param list - * The list to modify. + * The list to remove from. + * @param cursor + * A pointer to the item to insert after, e.g. &list->head or list->tail. * @param item - * The item to append. + * The item to insert. * @param node (optional) * If specified, use item->node.next rather than item->next. * @@ -118,27 +120,29 @@ * * We play some tricks with variadic macros to handle the optional parameter: * - * SLIST_APPEND(list, item) => { - * *list->tail = item; - * list->tail = &item->next; + * SLIST_INSERT(list, cursor, item) => { + * item->next = *cursor; + * *cursor = item; + * list->tail = item->next ? list->tail : &item->next; * } * - * SLIST_APPEND(list, item, node) => { - * *list->tail = item; - * list->tail = &item->node.next; + * SLIST_INSERT(list, cursor, item, node) => { + * item->node.next = *cursor; + * *cursor = item; + * list->tail = item->node.next ? list->tail : &item->node.next; * } * * The first trick is that * - * #define SLIST_APPEND(list, item, ...) + * #define SLIST_INSERT(list, item, cursor, ...) * * 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_APPEND(list, item) => SLIST_APPEND_(list, item, ) - * SLIST_APPEND(list, item, node) => SLIST_APPEND_(list, item, node, ) + * SLIST_INSERT(list, cursor, item) => SLIST_INSERT_(list, cursor, item, ) + * SLIST_INSERT(list, cursor, item, node) => SLIST_INSERT_(list, cursor, item, node, ) */ -#define SLIST_APPEND(list, ...) SLIST_APPEND_(list, __VA_ARGS__, ) +#define SLIST_INSERT(list, cursor, ...) SLIST_INSERT_(list, cursor, __VA_ARGS__, ) /** * Now we need a way to generate either ->next or ->node.next depending on @@ -178,18 +182,31 @@ #define LIST_NODE__(dir, node, ignored, dot, ...) node dot dir /** - * SLIST_APPEND_() uses LIST_NEXT_() to generate the right name for the list + * SLIST_INSERT_() uses LIST_NEXT_() to generate the right name for the list * node, and finally delegates to the actual implementation. + */ +#define SLIST_INSERT_(list, cursor, item, ...) \ + LIST_BLOCK_(SLIST_INSERT__((list), (cursor), (item), LIST_NEXT_(__VA_ARGS__))) + +#define SLIST_INSERT__(list, cursor, item, next) \ + item->next = *cursor; \ + *cursor = item; \ + list->tail = item->next ? list->tail : &item->next; + +/** + * Add an item to the tail of a singly-linked list. * - * SLIST_APPEND_(list, item, ) => SLIST_APPEND__((list), (item), next) - * SLIST_APPEND_(list, item, node, ) => SLIST_APPEND__((list), (item), node.next) + * @param list + * The list to modify. + * @param item + * The item to append. + * @param node (optional) + * If specified, use item->node.next rather than item->next. */ -#define SLIST_APPEND_(list, item, ...) \ - LIST_BLOCK_(SLIST_APPEND__((list), (item), LIST_NEXT_(__VA_ARGS__))) +#define SLIST_APPEND(list, ...) SLIST_APPEND_(list, __VA_ARGS__, ) -#define SLIST_APPEND__(list, item, next) \ - *list->tail = item; \ - list->tail = &item->next; +#define SLIST_APPEND_(list, item, ...) \ + SLIST_INSERT_(list, (list)->tail, item, __VA_ARGS__) /** * Add an item to the head of a singly-linked list. @@ -204,12 +221,7 @@ #define SLIST_PREPEND(list, ...) SLIST_PREPEND_(list, __VA_ARGS__, ) #define SLIST_PREPEND_(list, item, ...) \ - LIST_BLOCK_(SLIST_PREPEND__((list), (item), LIST_NEXT_(__VA_ARGS__))) - -#define SLIST_PREPEND__(list, item, next) \ - list->tail = list->head ? list->tail : &item->next; \ - item->next = list->head; \ - list->head = item; + SLIST_INSERT_(list, &(list)->head, item, __VA_ARGS__) /** * Add an entire singly-linked list to the tail of another. @@ -260,9 +272,8 @@ */ #define SLIST_POP(...) SLIST_POP_(__VA_ARGS__, ) -#define SLIST_POP_(list, ...) SLIST_POP__((list), LIST_NEXT_(__VA_ARGS__)) - -#define SLIST_POP__(list, next) SLIST_REMOVE__(list, (&list->head), next) +#define SLIST_POP_(list, ...) \ + SLIST_REMOVE_(list, &(list)->head, __VA_ARGS__) /** * Initialize a doubly-linked list. -- cgit v1.2.3 From a3bed764555f76003bb03c023123846fdff76f1b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 6 Apr 2023 14:52:19 -0400 Subject: build: Fix tsan --- Makefile | 72 +++++++++++++++++++++++++++++++--------------------------- tests/tests.sh | 11 +++++---- 2 files changed, 45 insertions(+), 38 deletions(-) diff --git a/Makefile b/Makefile index b509065..57ff238 100644 --- a/Makefile +++ b/Makefile @@ -65,7 +65,42 @@ MSAN := $(filter msan,$(MAKECMDGOALS)) TSAN := $(filter tsan,$(MAKECMDGOALS)) UBSAN := $(filter ubsan,$(MAKECMDGOALS)) -ifndef MSAN +ifdef ASAN +LOCAL_CFLAGS += -fsanitize=address +SANITIZE := y +endif + +ifdef LSAN +LOCAL_CFLAGS += -fsanitize=leak +SANITIZE := y +endif + +ifdef MSAN +# msan needs all code instrumented +NOLIBS := y +LOCAL_CFLAGS += -fsanitize=memory -fsanitize-memory-track-origins +SANITIZE := y +endif + +ifdef TSAN +# tsan needs all code instrumented +NOLIBS := y +# https://github.com/google/sanitizers/issues/342 +LOCAL_CPPFLAGS += -DBFS_TARGET_CLONES=false +LOCAL_CFLAGS += -fsanitize=thread +SANITIZE := y +endif + +ifdef UBSAN +LOCAL_CFLAGS += -fsanitize=undefined +SANITIZE := y +endif + +ifdef SANITIZE +LOCAL_CFLAGS += -fno-sanitize-recover=all +endif + +ifndef NOLIBS WITH_ONIGURUMA := y endif @@ -82,10 +117,10 @@ endif LOCAL_CFLAGS += $(ONIG_CFLAGS) LOCAL_LDLIBS += $(ONIG_LDLIBS) -endif +endif # WITH_ONIGURUMA ifeq ($(OS),Linux) -ifndef MSAN # These libraries are not built with msan +ifndef NOLIBS WITH_ACL := y WITH_ATTR := y WITH_LIBCAP := y @@ -111,41 +146,12 @@ endif LOCAL_LDFLAGS += -Wl,--as-needed LOCAL_LDLIBS += -lrt -endif +endif # Linux ifeq ($(OS),NetBSD) LOCAL_LDLIBS += -lutil endif -ifdef ASAN -LOCAL_CFLAGS += -fsanitize=address -SANITIZE := y -endif - -ifdef LSAN -LOCAL_CFLAGS += -fsanitize=leak -SANITIZE := y -endif - -ifdef MSAN -LOCAL_CFLAGS += -fsanitize=memory -fsanitize-memory-track-origins -SANITIZE := y -endif - -ifdef TSAN -LOCAL_CFLAGS += -fsanitize=thread -SANITIZE := y -endif - -ifdef UBSAN -LOCAL_CFLAGS += -fsanitize=undefined -SANITIZE := y -endif - -ifdef SANITIZE -LOCAL_CFLAGS += -fno-sanitize-recover=all -endif - ifneq ($(filter gcov,$(MAKECMDGOALS)),) LOCAL_CFLAGS += --coverage # gcov only intercepts fork()/exec() with -std=gnu* diff --git a/tests/tests.sh b/tests/tests.sh index 98d332c..9265481 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -9,11 +9,12 @@ umask 022 export LC_ALL=C export TZ=UTC0 -export ASAN_OPTIONS="abort_on_error=1:log_to_syslog=0" -export LSAN_OPTIONS="abort_on_error=1:log_to_syslog=0" -export MSAN_OPTIONS="abort_on_error=1:log_to_syslog=0" -export TSAN_OPTIONS="abort_on_error=1:log_to_syslog=0" -export UBSAN_OPTIONS="abort_on_error=1:log_to_syslog=0" +SAN_OPTIONS="halt_on_error=1:log_to_syslog=0" +export ASAN_OPTIONS="$SAN_OPTIONS" +export LSAN_OPTIONS="$SAN_OPTIONS" +export MSAN_OPTIONS="$SAN_OPTIONS" +export TSAN_OPTIONS="$SAN_OPTIONS" +export UBSAN_OPTIONS="$SAN_OPTIONS" export LS_COLORS="" unset BFS_COLORS -- cgit v1.2.3 From 6d59961d6d5ce91529a17bdad380ee78fa866564 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 May 2023 09:11:47 -0400 Subject: Let musl builds use getdents64() Glibc exposes a different struct dirent and dirent64, while on musl they are the same. But musl needs _LARGEFILE64_SOURCE to expose the *64() aliases. --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 57ff238..7113bc4 100644 --- a/Makefile +++ b/Makefile @@ -50,9 +50,10 @@ LOCAL_CPPFLAGS := \ -D_BSD_SOURCE \ -D_DARWIN_C_SOURCE \ -D_DEFAULT_SOURCE \ + -D_GNU_SOURCE \ + -D_LARGEFILE64_SOURCE \ -D_FILE_OFFSET_BITS=64 \ -D_TIME_BITS=64 \ - -D_GNU_SOURCE \ -DBFS_VERSION=\"$(VERSION)\" LOCAL_CFLAGS := -std=c11 -- cgit v1.2.3 From 7e5357a1cf40ebaa7ffaeebfd3a88c3ba93eb1a7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 May 2023 09:29:19 -0400 Subject: style: Don't use tabs to indent preprocessor directives --- src/bfstd.c | 6 ++--- src/bfstd.h | 32 +++++++++++------------ src/config.h | 84 ++++++++++++++++++++++++++++++------------------------------ src/dir.c | 20 +++++++-------- src/eval.c | 22 ++++++++-------- src/fsade.c | 8 +++--- src/fsade.h | 8 +++--- src/mtab.c | 20 +++++++-------- src/printf.c | 8 +++--- src/stat.c | 10 ++++---- src/stat.h | 8 +++--- src/trie.c | 30 +++++++++++----------- src/xregex.c | 28 ++++++++++---------- src/xspawn.c | 2 +- 14 files changed, 143 insertions(+), 143 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index bc31288..1dc322b 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -19,13 +19,13 @@ #include #if BFS_USE_SYS_SYSMACROS_H -# include +# include #elif BFS_USE_SYS_MKDEV_H -# include +# include #endif #if BFS_USE_UTIL_H -# include +# include #endif bool is_nonexistence_error(int error) { diff --git a/src/bfstd.h b/src/bfstd.h index f85b74f..028e4e6 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -21,29 +21,29 @@ bool is_nonexistence_error(int error); #include #ifndef O_EXEC -# ifdef O_PATH -# define O_EXEC O_PATH -# else -# define O_EXEC O_RDONLY -# endif +# ifdef O_PATH +# define O_EXEC O_PATH +# else +# define O_EXEC O_RDONLY +# endif #endif #ifndef O_SEARCH -# ifdef O_PATH -# define O_SEARCH O_PATH -# else -# define O_SEARCH O_RDONLY -# endif +# ifdef O_PATH +# define O_SEARCH O_PATH +# else +# define O_SEARCH O_RDONLY +# endif #endif #ifndef O_DIRECTORY -# define O_DIRECTORY 0 +# define O_DIRECTORY 0 #endif #include #if !defined(FNM_CASEFOLD) && defined(FNM_IGNORECASE) -# define FNM_CASEFOLD FNM_IGNORECASE +# define FNM_CASEFOLD FNM_IGNORECASE #endif // #include @@ -144,10 +144,10 @@ int xminor(dev_t dev); // #include #if __APPLE__ -# define st_atim st_atimespec -# define st_ctim st_ctimespec -# define st_mtim st_mtimespec -# define st_birthtim st_birthtimespec +# define st_atim st_atimespec +# define st_ctim st_ctimespec +# define st_mtim st_mtimespec +# define st_birthtim st_birthtimespec #endif // #include diff --git a/src/config.h b/src/config.h index 34ae11d..5f54250 100644 --- a/src/config.h +++ b/src/config.h @@ -14,13 +14,13 @@ // bfs packaging configuration #ifndef BFS_COMMAND -# define BFS_COMMAND "bfs" +# define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "2.6.3" +# define BFS_VERSION "2.6.3" #endif #ifndef BFS_HOMEPAGE -# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" +# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" #endif // Check for system headers @@ -28,34 +28,34 @@ #ifdef __has_include #if __has_include() -# define BFS_HAS_MNTENT_H true +# define BFS_HAS_MNTENT_H true #endif #if __has_include() -# define BFS_HAS_PATHS_H true +# define BFS_HAS_PATHS_H true #endif #if __has_include() -# define BFS_HAS_SYS_ACL_H true +# define BFS_HAS_SYS_ACL_H true #endif #if __has_include() -# define BFS_HAS_SYS_CAPABILITY_H true +# define BFS_HAS_SYS_CAPABILITY_H true #endif #if __has_include() -# define BFS_HAS_SYS_EXTATTR_H true +# define BFS_HAS_SYS_EXTATTR_H true #endif #if __has_include() -# define BFS_HAS_SYS_MKDEV_H true +# define BFS_HAS_SYS_MKDEV_H true #endif #if __has_include() -# define BFS_HAS_SYS_PARAM_H true +# define BFS_HAS_SYS_PARAM_H true #endif #if __has_include() -# define BFS_HAS_SYS_SYSMACROS_H true +# define BFS_HAS_SYS_SYSMACROS_H true #endif #if __has_include() -# define BFS_HAS_SYS_XATTR_H true +# define BFS_HAS_SYS_XATTR_H true #endif #if __has_include() -# define BFS_HAS_UTIL_H true +# define BFS_HAS_UTIL_H true #endif #else // !__has_include @@ -74,59 +74,59 @@ #endif // !__has_include #ifndef BFS_USE_MNTENT_H -# define BFS_USE_MNTENT_H BFS_HAS_MNTENT_H +# define BFS_USE_MNTENT_H BFS_HAS_MNTENT_H #endif #ifndef BFS_USE_PATHS_H -# define BFS_USE_PATHS_H BFS_HAS_PATHS_H +# define BFS_USE_PATHS_H BFS_HAS_PATHS_H #endif #ifndef BFS_USE_SYS_ACL_H -# define BFS_USE_SYS_ACL_H BFS_HAS_SYS_ACL_H +# define BFS_USE_SYS_ACL_H BFS_HAS_SYS_ACL_H #endif #ifndef BFS_USE_SYS_CAPABILITY_H -# define BFS_USE_SYS_CAPABILITY_H BFS_HAS_SYS_CAPABILITY_H +# define BFS_USE_SYS_CAPABILITY_H BFS_HAS_SYS_CAPABILITY_H #endif #ifndef BFS_USE_SYS_EXTATTR_H -# define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H +# define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H #endif #ifndef BFS_USE_SYS_MKDEV_H -# define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H +# define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H #endif #ifndef BFS_USE_SYS_PARAM_H -# define BFS_USE_SYS_PARAM_H BFS_HAS_SYS_PARAM_H +# define BFS_USE_SYS_PARAM_H BFS_HAS_SYS_PARAM_H #endif #ifndef BFS_USE_SYS_SYSMACROS_H -# define BFS_USE_SYS_SYSMACROS_H BFS_HAS_SYS_SYSMACROS_H +# define BFS_USE_SYS_SYSMACROS_H BFS_HAS_SYS_SYSMACROS_H #endif #ifndef BFS_USE_SYS_XATTR_H -# define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H +# define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H #endif #ifndef BFS_USE_UTIL_H -# define BFS_USE_UTIL_H BFS_HAS_UTIL_H +# define BFS_USE_UTIL_H BFS_HAS_UTIL_H #endif // Stub out feature detection on old/incompatible compilers #ifndef __has_feature -# define __has_feature(feat) false +# define __has_feature(feat) false #endif #ifndef __has_c_attribute -# define __has_c_attribute(attr) false +# define __has_c_attribute(attr) false #endif #ifndef __has_attribute -# define __has_attribute(attr) false +# define __has_attribute(attr) false #endif // Platform detection // Get the definition of BSD if available #if BFS_USE_SYS_PARAM_H -# include +# include #endif #ifndef __GLIBC_PREREQ -# define __GLIBC_PREREQ(maj, min) false +# define __GLIBC_PREREQ(maj, min) false #endif // Wrappers for fundamental language features/extensions @@ -135,11 +135,11 @@ * Silence compiler warnings about switch/case fall-throughs. */ #if __has_c_attribute(fallthrough) -# define BFS_FALLTHROUGH [[fallthrough]] +# define BFS_FALLTHROUGH [[fallthrough]] #elif __has_attribute(fallthrough) -# define BFS_FALLTHROUGH __attribute__((fallthrough)) +# define BFS_FALLTHROUGH __attribute__((fallthrough)) #else -# define BFS_FALLTHROUGH ((void)0) +# define BFS_FALLTHROUGH ((void)0) #endif /** @@ -173,40 +173,40 @@ * Adds compiler warnings for bad printf()-style function calls, if supported. */ #if __has_attribute(format) -# define BFS_FORMATTER(fmt, args) __attribute__((format(printf, fmt, args))) +# define BFS_FORMATTER(fmt, args) __attribute__((format(printf, fmt, args))) #else -# define BFS_FORMATTER(fmt, args) +# define BFS_FORMATTER(fmt, args) #endif /** * Check if function multiversioning via GNU indirect functions (ifunc) is supported. */ #if !defined(BFS_TARGET_CLONES) && __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__ || __NetBSD__) -# define BFS_TARGET_CLONES true +# define BFS_TARGET_CLONES true #endif /** * Ignore a particular GCC warning for a region of code. */ #if __GNUC__ -# define BFS_PRAGMA_STRINGIFY(...) _Pragma(#__VA_ARGS__) -# define BFS_SUPPRESS(warning) \ - _Pragma("GCC diagnostic push"); \ +# define BFS_PRAGMA_STRINGIFY(...) _Pragma(#__VA_ARGS__) +# define BFS_SUPPRESS(warning) \ + _Pragma("GCC diagnostic push"); \ BFS_PRAGMA_STRINGIFY(GCC diagnostic ignored warning) -# define BFS_UNSUPPRESS() \ +# define BFS_UNSUPPRESS() \ _Pragma("GCC diagnostic pop") #else -# define BFS_SUPPRESS(warning) -# define BFS_UNSUPPRESS() +# define BFS_SUPPRESS(warning) +# define BFS_UNSUPPRESS() #endif /** * Initialize a variable, unless sanitizers would detect uninitialized uses. */ #if __has_feature(memory_sanitizer) -# define BFS_UNINIT(var, value) var = var +# define BFS_UNINIT(var, value) var = var #else -# define BFS_UNINIT(var, value) var = value +# define BFS_UNINIT(var, value) var = value #endif #endif // BFS_CONFIG_H diff --git a/src/dir.c b/src/dir.c index 2719c15..eb6e3e0 100644 --- a/src/dir.c +++ b/src/dir.c @@ -16,16 +16,16 @@ #include #ifndef BFS_GETDENTS -# define BFS_GETDENTS (__linux__ || __FreeBSD__) +# define BFS_GETDENTS (__linux__ || __FreeBSD__) #endif #if BFS_GETDENTS -# if __has_feature(memory_sanitizer) -# include -# endif -# if __linux__ -# include -# endif +# if __has_feature(memory_sanitizer) +# include +# endif +# if __linux__ +# include +# endif /** getdents() syscall wrapper. */ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { @@ -119,10 +119,10 @@ struct bfs_dir { }; #if BFS_GETDENTS -# define DIR_SIZE (64 << 10) -# define BUF_SIZE (DIR_SIZE - sizeof(struct bfs_dir)) +# define DIR_SIZE (64 << 10) +# define BUF_SIZE (DIR_SIZE - sizeof(struct bfs_dir)) #else -# define DIR_SIZE sizeof(struct bfs_dir) +# define DIR_SIZE sizeof(struct bfs_dir) #endif struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { diff --git a/src/eval.c b/src/eval.c index 53ce605..687ba32 100644 --- a/src/eval.c +++ b/src/eval.c @@ -943,9 +943,9 @@ bool eval_xtype(const struct bfs_expr *expr, struct bfs_eval *state) { } #if _POSIX_MONOTONIC_CLOCK > 0 -# define BFS_CLOCK CLOCK_MONOTONIC +# define BFS_CLOCK CLOCK_MONOTONIC #elif _POSIX_TIMERS > 0 -# define BFS_CLOCK CLOCK_REALTIME +# define BFS_CLOCK CLOCK_REALTIME #endif /** @@ -1194,15 +1194,15 @@ static bool eval_file_unique(struct bfs_eval *state, struct trie *seen) { } } -#define DEBUG_FLAG(flags, flag) \ - do { \ - if ((flags & flag) || flags == flag) { \ - fputs(#flag, stderr); \ - flags ^= flag; \ - if (flags) { \ - fputs(" | ", stderr); \ - } \ - } \ +#define DEBUG_FLAG(flags, flag) \ + do { \ + if ((flags & flag) || flags == flag) { \ + fputs(#flag, stderr); \ + flags ^= flag; \ + if (flags) { \ + fputs(" | ", stderr); \ + } \ + } \ } while (0) /** diff --git a/src/fsade.c b/src/fsade.c index a61a6b8..aefbb75 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -13,17 +13,17 @@ #include #if BFS_CAN_CHECK_ACL -# include +# include #endif #if BFS_CAN_CHECK_CAPABILITIES -# include +# include #endif #if BFS_USE_SYS_EXTATTR_H -# include +# include #elif BFS_USE_SYS_XATTR_H -# include +# include #endif #if BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS diff --git a/src/fsade.h b/src/fsade.h index 9bef892..557da26 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -15,10 +15,10 @@ #define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H #if !defined(BFS_CAN_CHECK_CAPABILITIES) && BFS_USE_SYS_CAPABILITY_H && !__FreeBSD__ -# include -# ifdef CAP_CHOWN -# define BFS_CAN_CHECK_CAPABILITIES true -# endif +# include +# ifdef CAP_CHOWN +# define BFS_CAN_CHECK_CAPABILITIES true +# endif #endif #define BFS_CAN_CHECK_XATTRS (BFS_USE_SYS_EXTATTR_H || BFS_USE_SYS_XATTR_H) diff --git a/src/mtab.c b/src/mtab.c index 0e377a1..27f1743 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -15,23 +15,23 @@ #include #if BFS_USE_MNTENT_H -# define BFS_MNTENT 1 +# define BFS_MNTENT 1 #elif BSD -# define BFS_MNTINFO 1 +# define BFS_MNTINFO 1 #elif __SVR4 -# define BFS_MNTTAB 1 +# define BFS_MNTTAB 1 #endif #if BFS_MNTENT -# include -# include -# include +# include +# include +# include #elif BFS_MNTINFO -# include -# include +# include +# include #elif BFS_MNTTAB -# include -# include +# include +# include #endif /** diff --git a/src/printf.c b/src/printf.c index 1b4f2d4..726d54d 100644 --- a/src/printf.c +++ b/src/printf.c @@ -72,10 +72,10 @@ static bool should_color(CFILE *cfile, const struct bfs_printf *directive) { /** * Print a value to a temporary buffer before formatting it. */ -#define BFS_PRINTF_BUF(buf, format, ...) \ - char buf[256]; \ - int ret = snprintf(buf, sizeof(buf), format, __VA_ARGS__); \ - assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ +#define BFS_PRINTF_BUF(buf, format, ...) \ + char buf[256]; \ + int ret = snprintf(buf, sizeof(buf), format, __VA_ARGS__); \ + assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ (void)ret /** diff --git a/src/stat.c b/src/stat.c index aaa5eac..f3d9046 100644 --- a/src/stat.c +++ b/src/stat.c @@ -13,15 +13,15 @@ #include #if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30) -# define BFS_LIBC_STATX true +# define BFS_LIBC_STATX true #elif __linux__ -# include -# include -# include +# include +# include +# include #endif #if BFS_LIBC_STATX || defined(__NR_statx) -# define BFS_STATX true +# define BFS_STATX true #endif const char *bfs_stat_field_name(enum bfs_stat_field field) { diff --git a/src/stat.h b/src/stat.h index 7a70146..e08dd4d 100644 --- a/src/stat.h +++ b/src/stat.h @@ -17,7 +17,7 @@ #include #if BFS_USE_SYS_PARAM_H -# include +# include #endif /** @@ -61,11 +61,11 @@ enum bfs_stat_flags { }; #ifdef DEV_BSIZE -# define BFS_STAT_BLKSIZE DEV_BSIZE +# define BFS_STAT_BLKSIZE DEV_BSIZE #elif defined(S_BLKSIZE) -# define BFS_STAT_BLKSIZE S_BLKSIZE +# define BFS_STAT_BLKSIZE S_BLKSIZE #else -# define BFS_STAT_BLKSIZE 512 +# define BFS_STAT_BLKSIZE 512 #endif /** diff --git a/src/trie.c b/src/trie.c index 43df9dc..9f2e45f 100644 --- a/src/trie.c +++ b/src/trie.c @@ -94,9 +94,9 @@ static_assert(CHAR_BIT == 8, "This trie implementation assumes 8-bit bytes."); #if BFS_TARGET_CLONES && (__i386__ || __x86_64__) -# define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) +# define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) #else -# define TARGET_CLONES_POPCNT +# define TARGET_CLONES_POPCNT #endif /** Number of bits for the sparse array bitmap, aka the range of a nibble. */ @@ -367,23 +367,23 @@ static size_t trie_node_size(unsigned int size) { } #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -# define TRIE_BSWAP(n) (n) +# define TRIE_BSWAP(n) (n) #elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -# if __SIZEOF_SIZE_T__ == 8 -# define TRIE_BSWAP(n) __builtin_bswap64(n) -# elif __SIZEOF_SIZE_T__ == 4 -# define TRIE_BSWAP(n) __builtin_bswap32(n) -# endif +# if __SIZEOF_SIZE_T__ == 8 +# define TRIE_BSWAP(n) __builtin_bswap64(n) +# elif __SIZEOF_SIZE_T__ == 4 +# define TRIE_BSWAP(n) __builtin_bswap32(n) +# endif #endif #ifdef TRIE_BSWAP -# if __SIZEOF_SIZE_T__ == __SIZEOF_LONG_LONG__ -# define TRIE_CTZ(n) __builtin_ctzll(n) -# elif __SIZEOF_SIZE_T__ == __SIZEOF_LONG__ -# define TRIE_CTZ(n) __builtin_ctzl(n) -# elif __SIZEOF_SIZE_T__ == __SIZEOF_INT__ -# define TRIE_CTZ(n) __builtin_ctz(n) -# endif +# if __SIZEOF_SIZE_T__ == __SIZEOF_LONG_LONG__ +# define TRIE_CTZ(n) __builtin_ctzll(n) +# elif __SIZEOF_SIZE_T__ == __SIZEOF_LONG__ +# define TRIE_CTZ(n) __builtin_ctzl(n) +# elif __SIZEOF_SIZE_T__ == __SIZEOF_INT__ +# define TRIE_CTZ(n) __builtin_ctz(n) +# endif #endif /** Find the offset of the first nibble that differs between two keys. */ diff --git a/src/xregex.c b/src/xregex.c index 5f5480f..a7153b7 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -9,10 +9,10 @@ #include #if BFS_WITH_ONIGURUMA -# include -# include +# include +# include #else -# include +# include #endif struct bfs_regex { @@ -43,20 +43,20 @@ static int bfs_onig_encoding(OnigEncoding *penc) { // from the current locale. const char *charmap = nl_langinfo(CODESET); if (charmap) { -#define BFS_MAP_ENCODING(name, value) \ - do { \ - if (strcmp(charmap, name) == 0) { \ - enc = value; \ - } \ +#define BFS_MAP_ENCODING(name, value) \ + do { \ + if (strcmp(charmap, name) == 0) { \ + enc = value; \ + } \ } while (0) -#define BFS_MAP_ENCODING2(name1, name2, value) \ - do { \ - BFS_MAP_ENCODING(name1, value); \ - BFS_MAP_ENCODING(name2, value); \ +#define BFS_MAP_ENCODING2(name1, name2, value) \ + do { \ + BFS_MAP_ENCODING(name1, value); \ + BFS_MAP_ENCODING(name2, value); \ } while (0) // These names were found with locale -m on Linux and FreeBSD -#define BFS_MAP_ISO_8859(n) \ +#define BFS_MAP_ISO_8859(n) \ BFS_MAP_ENCODING2("ISO-8859-" #n, "ISO8859-" #n, ONIG_ENCODING_ISO_8859_ ## n) BFS_MAP_ISO_8859(1); @@ -78,7 +78,7 @@ static int bfs_onig_encoding(OnigEncoding *penc) { BFS_MAP_ENCODING("UTF-8", ONIG_ENCODING_UTF8); -#define BFS_MAP_EUC(name) \ +#define BFS_MAP_EUC(name) \ BFS_MAP_ENCODING2("EUC-" #name, "euc" #name, ONIG_ENCODING_EUC_ ## name) BFS_MAP_EUC(JP); diff --git a/src/xspawn.c b/src/xspawn.c index a185200..a6d18a3 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -16,7 +16,7 @@ #include #if BFS_USE_PATHS_H -# include +# include #endif /** -- cgit v1.2.3 From c1b16b49988ecff17ae30978ea14798d95b80018 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 5 May 2023 17:49:44 -0400 Subject: bftw: Use separate dir/file queues --- src/bftw.c | 394 +++++++++++++++++++++++++++---------------------------------- 1 file changed, 176 insertions(+), 218 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 5cbe0c2..e4dc411 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -362,8 +362,10 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; - /** The queue of files left to explore. */ - struct bftw_list queue; + /** The queue of directories to read. */ + struct bftw_list dirs; + /** The queue of files to visit. */ + struct bftw_list files; /** A batch of files to enqueue. */ struct bftw_list batch; @@ -397,6 +399,10 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->strategy = args->strategy; state->mtab = args->mtab; + if ((state->flags & BFTW_SORT) || state->strategy == BFTW_DFS) { + state->flags |= BFTW_BUFFER; + } + state->error = 0; if (args->nopenfd < 1) { @@ -411,7 +417,8 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg bftw_cache_init(&state->cache, args->nopenfd); - SLIST_INIT(&state->queue); + SLIST_INIT(&state->dirs); + SLIST_INIT(&state->files); SLIST_INIT(&state->batch); state->file = NULL; @@ -497,22 +504,47 @@ enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { } /** - * Update the path for the current file. + * Build the path to the current file. */ -static int bftw_update_path(struct bftw_state *state, const char *name) { +static int bftw_build_path(struct bftw_state *state, const char *name) { const struct bftw_file *file = state->file; - size_t length = file ? file->nameoff + file->namelen : 0; - assert(dstrlen(state->path) >= length); - dstresize(&state->path, length); + size_t pathlen = file ? file->nameoff + file->namelen : 0; + if (dstresize(&state->path, pathlen) != 0) { + state->error = errno; + return -1; + } + + // Try to find a common ancestor with the existing path + const struct bftw_file *ancestor = state->previous; + while (ancestor && ancestor->depth > file->depth) { + ancestor = ancestor->parent; + } + + // Build the path backwards + while (file && file != ancestor) { + if (file->nameoff > 0) { + state->path[file->nameoff - 1] = '/'; + } + memcpy(state->path + file->nameoff, file->name, file->namelen); + + if (ancestor && ancestor->depth == file->depth) { + ancestor = ancestor->parent; + } + file = file->parent; + } + + state->previous = state->file; if (name) { - if (length > 0 && state->path[length - 1] != '/') { + if (pathlen > 0 && state->path[pathlen - 1] != '/') { if (dstrapp(&state->path, '/') != 0) { + state->error = errno; return -1; } } if (dstrcat(&state->path, name) != 0) { + state->error = errno; return -1; } } @@ -689,28 +721,15 @@ static bool bftw_is_mount(struct bftw_state *state, const char *name) { return statbuf && statbuf->dev != parent->dev; } -/** Fill file identity information from an ftwbuf. */ -static void bftw_fill_id(struct bftw_file *file, const struct BFTW *ftwbuf) { - const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; - if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - statbuf = ftwbuf->lstat_cache.buf; - } - if (statbuf) { - file->dev = statbuf->dev; - file->ino = statbuf->ino; - } -} - /** - * Visit a path, invoking the callback. + * Invoke the callback. */ -static enum bftw_action bftw_visit(struct bftw_state *state, const char *name, enum bftw_visit visit) { +static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) { if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { return BFTW_PRUNE; } - if (bftw_update_path(state, name) != 0) { - state->error = errno; + if (bftw_build_path(state, name) != 0) { return BFTW_STOP; } @@ -730,132 +749,74 @@ static enum bftw_action bftw_visit(struct bftw_state *state, const char *name, e enum bftw_action ret = state->callback(ftwbuf, state->ptr); switch (ret) { case BFTW_CONTINUE: - break; + if (visit != BFTW_PRE) { + return BFTW_PRUNE; + } + if (ftwbuf->type != BFS_DIR) { + return BFTW_PRUNE; + } + if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { + return BFTW_PRUNE; + } + BFS_FALLTHROUGH; case BFTW_PRUNE: case BFTW_STOP: - goto done; + return ret; + default: state->error = EINVAL; return BFTW_STOP; } - - if (visit != BFTW_PRE || ftwbuf->type != BFS_DIR) { - ret = BFTW_PRUNE; - goto done; - } - - if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { - ret = BFTW_PRUNE; - goto done; - } - -done: - if (state->file && !name) { - bftw_fill_id(state->file, ftwbuf); - } - - return ret; } -/** - * Push a new file onto the queue. - */ -static int bftw_push(struct bftw_state *state, const char *name, bool fill_id) { - struct bftw_file *parent = state->file; - struct bftw_file *file = bftw_file_new(parent, name); - if (!file) { - state->error = errno; - return -1; - } +/** Pop a directory to read from the queue. */ +static bool bftw_pop_dir(struct bftw_state *state) { + assert(!state->file); - if (state->de) { - file->type = state->de->type; + if (!state->dirs.head) { + return false; } - if (fill_id) { - bftw_fill_id(file, &state->ftwbuf); + if (state->files.head && state->strategy == BFTW_BFS) { + return false; } - SLIST_APPEND(&state->batch, file); - return 0; + state->file = state->dirs.head; + SLIST_POP(&state->dirs); + return true; } -/** - * Build the path to the current file. - */ -static int bftw_build_path(struct bftw_state *state) { - const struct bftw_file *file = state->file; - - size_t pathlen = file->nameoff + file->namelen; - if (dstresize(&state->path, pathlen) != 0) { - state->error = errno; - return -1; - } - - // Try to find a common ancestor with the existing path - const struct bftw_file *ancestor = state->previous; - while (ancestor && ancestor->depth > file->depth) { - ancestor = ancestor->parent; - } - - // Build the path backwards - while (file && file != ancestor) { - if (file->nameoff > 0) { - state->path[file->nameoff - 1] = '/'; - } - memcpy(state->path + file->nameoff, file->name, file->namelen); - - if (ancestor && ancestor->depth == file->depth) { - ancestor = ancestor->parent; - } - file = file->parent; - } - - state->previous = state->file; - return 0; -} +/** Pop a file to visit from the queue. */ +static bool bftw_pop_file(struct bftw_state *state) { + assert(!state->file); -/** - * Pop the next file from the queue. - */ -static bool bftw_pop(struct bftw_state *state) { - state->file = state->queue.head; + state->file = state->files.head; if (state->file) { - SLIST_POP(&state->queue); + SLIST_POP(&state->files); return true; } else { return false; } } -/** - * Start processing the next file in the queue. - */ -static int bftw_next(struct bftw_state *state) { - if (!bftw_pop(state)) { - return 0; - } - - if (bftw_build_path(state) != 0) { - return -1; - } - - return 1; -} - /** * Open the current directory. */ -static void bftw_opendir(struct bftw_state *state) { +static int bftw_opendir(struct bftw_state *state) { assert(!state->dir); assert(!state->de); state->direrror = 0; + if (bftw_build_path(state, NULL) != 0) { + return -1; + } + state->dir = bftw_file_opendir(&state->cache, state->file, state->path); if (!state->dir) { state->direrror = errno; } + return 0; } /** @@ -898,8 +859,8 @@ enum bftw_gc_flags { /** * Garbage collect the current file and its parents. */ -static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { - enum bftw_action ret = BFTW_CONTINUE; +static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { + int ret = 0; if (state->dir) { struct bftw_file *file = state->file; @@ -923,8 +884,8 @@ static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags fla if (state->direrror != 0) { if (flags & BFTW_VISIT_ERROR) { - if (bftw_visit(state, NULL, BFTW_PRE) == BFTW_STOP) { - ret = BFTW_STOP; + if (bftw_call_back(state, NULL, BFTW_PRE) == BFTW_STOP) { + ret = -1; flags = 0; } } else { @@ -942,8 +903,8 @@ static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags fla } if (flags & visit) { - if (bftw_visit(state, NULL, BFTW_POST) == BFTW_STOP) { - ret = BFTW_STOP; + if (bftw_call_back(state, NULL, BFTW_POST) == BFTW_STOP) { + ret = -1; flags = 0; } } @@ -969,9 +930,10 @@ static enum bftw_action bftw_gc(struct bftw_state *state, enum bftw_gc_flags fla static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); + SLIST_EXTEND(&state->files, &state->batch); do { bftw_gc(state, BFTW_VISIT_NONE); - } while (bftw_pop(state)); + } while (bftw_pop_dir(state) || bftw_pop_file(state)); bftw_cache_destroy(&state->cache); @@ -980,8 +942,8 @@ static int bftw_state_destroy(struct bftw_state *state) { } /** Sort a bftw_list by filename. */ -static void bftw_list_sort(struct bftw_list *queue) { - if (!queue->head || !queue->head->next) { +static void bftw_list_sort(struct bftw_list *list) { + if (!list->head || !list->head->next) { return; } @@ -990,12 +952,12 @@ static void bftw_list_sort(struct bftw_list *queue) { SLIST_INIT(&right); // Split - for (struct bftw_file *hare = queue->head; hare && (hare = hare->next); hare = hare->next) { - struct bftw_file *tortoise = queue->head; - SLIST_POP(queue); + for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) { + struct bftw_file *tortoise = list->head; + SLIST_POP(list); SLIST_APPEND(&left, tortoise); } - SLIST_EXTEND(&right, queue); + SLIST_EXTEND(&right, list); // Recurse bftw_list_sort(&left); @@ -1008,14 +970,14 @@ static void bftw_list_sort(struct bftw_list *queue) { if (strcoll(lf->name, rf->name) <= 0) { SLIST_POP(&left); - SLIST_APPEND(queue, lf); + SLIST_APPEND(list, lf); } else { SLIST_POP(&right); - SLIST_APPEND(queue, rf); + SLIST_APPEND(list, rf); } } - SLIST_EXTEND(queue, &left); - SLIST_EXTEND(queue, &right); + SLIST_EXTEND(list, &left); + SLIST_EXTEND(list, &right); } /** Finish adding a batch of files. */ @@ -1024,112 +986,119 @@ static void bftw_batch_finish(struct bftw_state *state) { bftw_list_sort(&state->batch); } - if (state->strategy == BFTW_DFS) { - SLIST_EXTEND(&state->batch, &state->queue); + if (state->strategy != BFTW_BFS) { + SLIST_EXTEND(&state->batch, &state->files); } - SLIST_EXTEND(&state->queue, &state->batch); + SLIST_EXTEND(&state->files, &state->batch); } -/** - * Streaming mode: visit files as they are encountered. - */ -static int bftw_stream(const struct bftw_args *args) { - struct bftw_state state; - if (bftw_state_init(&state, args) != 0) { +/** Close the current directory. */ +static int bftw_closedir(struct bftw_state *state) { + if (bftw_gc(state, BFTW_VISIT_ALL) != 0) { return -1; } - assert(!(state.flags & (BFTW_SORT | BFTW_BUFFER))); + bftw_batch_finish(state); + return 0; +} - for (size_t i = 0; i < args->npaths; ++i) { - const char *path = args->paths[i]; +/** Fill file identity information from an ftwbuf. */ +static void bftw_save_ftwbuf(struct bftw_file *file, const struct BFTW *ftwbuf) { + file->type = ftwbuf->type; - switch (bftw_visit(&state, path, BFTW_PRE)) { - case BFTW_CONTINUE: - break; - case BFTW_PRUNE: - continue; - case BFTW_STOP: - goto done; + const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; + if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { + statbuf = ftwbuf->lstat_cache.buf; + } + if (statbuf) { + file->dev = statbuf->dev; + file->ino = statbuf->ino; + } +} + +/** Visit and/or enqueue the current file. */ +static int bftw_visit(struct bftw_state *state, const char *name) { + struct bftw_file *file = state->file; + + if (name && (state->flags & BFTW_BUFFER)) { + file = bftw_file_new(file, name); + if (!file) { + state->error = errno; + return -1; } - if (bftw_push(&state, path, true) != 0) { - goto done; + if (state->de) { + file->type = state->de->type; } - } - bftw_batch_finish(&state); - while (bftw_next(&state) > 0) { - bftw_opendir(&state); + SLIST_APPEND(&state->batch, file); + return 0; + } - while (bftw_readdir(&state) > 0) { - const char *name = state.de->name; + switch (bftw_call_back(state, name, BFTW_PRE)) { + case BFTW_CONTINUE: + if (name) { + file = bftw_file_new(state->file, name); + } else { + state->file = NULL; + } + if (!file) { + state->error = errno; + return -1; + } - switch (bftw_visit(&state, name, BFTW_PRE)) { - case BFTW_CONTINUE: - break; - case BFTW_PRUNE: - continue; - case BFTW_STOP: - goto done; - } + bftw_save_ftwbuf(file, &state->ftwbuf); + SLIST_APPEND(&state->dirs, file); + return 0; - if (bftw_push(&state, name, true) != 0) { - goto done; - } + case BFTW_PRUNE: + if (file && !name) { + return bftw_gc(state, BFTW_VISIT_PARENTS); + } else { + return 0; } - bftw_batch_finish(&state); - if (bftw_gc(&state, BFTW_VISIT_ALL) == BFTW_STOP) { - goto done; - } + default: + return -1; } - -done: - return bftw_state_destroy(&state); } /** - * Batching mode: queue up all children before visiting them. + * bftw() implementation for simple breadth-/depth-first search. */ -static int bftw_batch(const struct bftw_args *args) { +static int bftw_impl(const struct bftw_args *args) { struct bftw_state state; if (bftw_state_init(&state, args) != 0) { return -1; } for (size_t i = 0; i < args->npaths; ++i) { - if (bftw_push(&state, args->paths[i], false) != 0) { + if (bftw_visit(&state, args->paths[i]) != 0) { goto done; } } bftw_batch_finish(&state); - while (bftw_next(&state) > 0) { - enum bftw_gc_flags gcflags = BFTW_VISIT_ALL; - - switch (bftw_visit(&state, NULL, BFTW_PRE)) { - case BFTW_CONTINUE: - break; - case BFTW_PRUNE: - gcflags &= ~BFTW_VISIT_FILE; - goto next; - case BFTW_STOP: - goto done; - } - - bftw_opendir(&state); - - while (bftw_readdir(&state) > 0) { - if (bftw_push(&state, state.de->name, false) != 0) { + while (true) { + while (bftw_pop_dir(&state)) { + if (bftw_opendir(&state) != 0) { + goto done; + } + while (bftw_readdir(&state) > 0) { + if (bftw_visit(&state, state.de->name) != 0) { + goto done; + } + } + if (bftw_closedir(&state) != 0) { goto done; } } - bftw_batch_finish(&state); - next: - if (bftw_gc(&state, gcflags) == BFTW_STOP) { - goto done; + if (!bftw_pop_file(&state)) { + break; + } + if (bftw_visit(&state, NULL) != 0) { + break; } } @@ -1137,15 +1106,6 @@ done: return bftw_state_destroy(&state); } -/** Select bftw_stream() or bftw_batch() appropriately. */ -static int bftw_auto(const struct bftw_args *args) { - if (args->flags & (BFTW_SORT | BFTW_BUFFER)) { - return bftw_batch(args); - } else { - return bftw_stream(args); - } -} - /** * Iterative deepening search state. */ @@ -1247,7 +1207,6 @@ static void bftw_ids_init(const struct bftw_args *args, struct bftw_ids_state *s ids_args->callback = bftw_ids_callback; ids_args->ptr = state; ids_args->flags &= ~BFTW_POST_ORDER; - ids_args->strategy = BFTW_DFS; } /** Finish an iterative deepening search. */ @@ -1277,7 +1236,7 @@ static int bftw_ids(const struct bftw_args *args) { while (!state.quit && !state.bottom) { state.bottom = true; - if (bftw_auto(&ids_args) != 0) { + if (bftw_impl(&ids_args) != 0) { state.error = errno; state.quit = true; } @@ -1294,7 +1253,7 @@ static int bftw_ids(const struct bftw_args *args) { --state.max_depth; --state.min_depth; - if (bftw_auto(&ids_args) != 0) { + if (bftw_impl(&ids_args) != 0) { state.error = errno; state.quit = true; } @@ -1315,7 +1274,7 @@ static int bftw_eds(const struct bftw_args *args) { while (!state.quit && !state.bottom) { state.bottom = true; - if (bftw_auto(&ids_args) != 0) { + if (bftw_impl(&ids_args) != 0) { state.error = errno; state.quit = true; } @@ -1329,7 +1288,7 @@ static int bftw_eds(const struct bftw_args *args) { state.min_depth = 0; ids_args.flags |= BFTW_POST_ORDER; - if (bftw_auto(&ids_args) != 0) { + if (bftw_impl(&ids_args) != 0) { state.error = errno; } } @@ -1340,9 +1299,8 @@ static int bftw_eds(const struct bftw_args *args) { int bftw(const struct bftw_args *args) { switch (args->strategy) { case BFTW_BFS: - return bftw_auto(args); case BFTW_DFS: - return bftw_batch(args); + return bftw_impl(args); case BFTW_IDS: return bftw_ids(args); case BFTW_EDS: -- cgit v1.2.3 From 3f1822caed3dca731997c8739c19ee32e5caadc1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 8 May 2023 14:18:37 -0400 Subject: config: Hoist the assignment outside of BFS_UNINIT() --- src/config.h | 4 ++-- src/parse.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config.h b/src/config.h index 5f54250..ed3f6ab 100644 --- a/src/config.h +++ b/src/config.h @@ -204,9 +204,9 @@ * Initialize a variable, unless sanitizers would detect uninitialized uses. */ #if __has_feature(memory_sanitizer) -# define BFS_UNINIT(var, value) var = var +# define BFS_UNINIT(var, value) var #else -# define BFS_UNINIT(var, value) var = value +# define BFS_UNINIT(var, value) value #endif #endif // BFS_CONFIG_H diff --git a/src/parse.c b/src/parse.c index 30bf56d..9540e83 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1962,11 +1962,11 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct MODE_PLUS, MODE_MINUS, MODE_EQUALS, - } BFS_UNINIT(op, MODE_EQUALS); + } op = BFS_UNINIT(op, MODE_EQUALS); - mode_t BFS_UNINIT(who, 0); - mode_t BFS_UNINIT(file_change, 0); - mode_t BFS_UNINIT(dir_change, 0); + mode_t who = BFS_UNINIT(who, 0); + mode_t file_change = BFS_UNINIT(file_change, 0); + mode_t dir_change = BFS_UNINIT(dir_change, 0); const char *i = mode; while (true) { -- cgit v1.2.3 From f3ecfc1ee49e1c3ddd3d19ca6166424d72a08a5c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 May 2023 15:42:09 -0400 Subject: config: Group attribute wrappers together --- src/config.h | 44 +++++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/src/config.h b/src/config.h index ed3f6ab..e17f90b 100644 --- a/src/config.h +++ b/src/config.h @@ -129,18 +129,7 @@ # define __GLIBC_PREREQ(maj, min) false #endif -// Wrappers for fundamental language features/extensions - -/** - * Silence compiler warnings about switch/case fall-throughs. - */ -#if __has_c_attribute(fallthrough) -# define BFS_FALLTHROUGH [[fallthrough]] -#elif __has_attribute(fallthrough) -# define BFS_FALLTHROUGH __attribute__((fallthrough)) -#else -# define BFS_FALLTHROUGH ((void)0) -#endif +// Fundamental utilities /** * Get the length of an array. @@ -169,6 +158,28 @@ ? BFS_FLEX_LB(type, member, length) \ : BFS_FLEX_MAX(sizeof(type), BFS_FLEX_LB(type, member, length))) +/** + * Initialize a variable, unless sanitizers would detect uninitialized uses. + */ +#if __has_feature(memory_sanitizer) +# define BFS_UNINIT(var, value) var +#else +# define BFS_UNINIT(var, value) value +#endif + +// Wrappers for attributes + +/** + * Silence compiler warnings about switch/case fall-throughs. + */ +#if __has_c_attribute(fallthrough) +# define BFS_FALLTHROUGH [[fallthrough]] +#elif __has_attribute(fallthrough) +# define BFS_FALLTHROUGH __attribute__((fallthrough)) +#else +# define BFS_FALLTHROUGH ((void)0) +#endif + /** * Adds compiler warnings for bad printf()-style function calls, if supported. */ @@ -200,13 +211,4 @@ # define BFS_UNSUPPRESS() #endif -/** - * Initialize a variable, unless sanitizers would detect uninitialized uses. - */ -#if __has_feature(memory_sanitizer) -# define BFS_UNINIT(var, value) var -#else -# define BFS_UNINIT(var, value) value -#endif - #endif // BFS_CONFIG_H -- cgit v1.2.3 From 8368e139c176dcde3b125f4a180ff868729b1862 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 May 2023 15:42:54 -0400 Subject: config: s/BFS_COUNTOF/countof/ --- src/config.h | 2 +- src/fsade.c | 2 +- src/opt.c | 16 ++++++++-------- tests/trie.c | 2 +- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/config.h b/src/config.h index e17f90b..60bc930 100644 --- a/src/config.h +++ b/src/config.h @@ -134,7 +134,7 @@ /** * Get the length of an array. */ -#define BFS_COUNTOF(array) (sizeof(array) / sizeof(0[array])) +#define countof(array) (sizeof(array) / sizeof(0[array])) // Lower bound on BFS_FLEX_SIZEOF() #define BFS_FLEX_LB(type, member, length) (offsetof(type, member) + sizeof(((type *)NULL)->member[0]) * (length)) diff --git a/src/fsade.c b/src/fsade.c index aefbb75..4d67940 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -201,7 +201,7 @@ int bfs_check_acl(const struct BFTW *ftwbuf) { const char *path = fake_at(ftwbuf); int ret = -1, error = 0; - for (size_t i = 0; i < BFS_COUNTOF(acl_types) && ret <= 0; ++i) { + for (size_t i = 0; i < countof(acl_types) && ret <= 0; ++i) { acl_type_t type = acl_types[i]; if (type == ACL_TYPE_DEFAULT && ftwbuf->type != BFS_DIR) { diff --git a/src/opt.c b/src/opt.c index 5505b7b..731dd10 100644 --- a/src/opt.c +++ b/src/opt.c @@ -1154,21 +1154,21 @@ static const struct { * Look up the appropriate optimizer for an expression and call it. */ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs_expr *expr) { - for (size_t i = 0; i < BFS_COUNTOF(opt_pure); ++i) { + for (size_t i = 0; i < countof(opt_pure); ++i) { if (opt_pure[i] == expr->eval_fn) { expr->pure = true; break; } } - for (size_t i = 0; i < BFS_COUNTOF(opt_always_true); ++i) { + for (size_t i = 0; i < countof(opt_always_true); ++i) { if (opt_always_true[i] == expr->eval_fn) { expr->always_true = true; break; } } - for (size_t i = 0; i < BFS_COUNTOF(opt_always_false); ++i) { + for (size_t i = 0; i < countof(opt_always_false); ++i) { if (opt_always_false[i] == expr->eval_fn) { expr->always_false = true; break; @@ -1176,35 +1176,35 @@ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs } expr->cost = FAST_COST; - for (size_t i = 0; i < BFS_COUNTOF(opt_costs); ++i) { + for (size_t i = 0; i < countof(opt_costs); ++i) { if (opt_costs[i].eval_fn == expr->eval_fn) { expr->cost = opt_costs[i].cost; break; } } - for (size_t i = 0; i < BFS_COUNTOF(opt_probs); ++i) { + for (size_t i = 0; i < countof(opt_probs); ++i) { if (opt_probs[i].eval_fn == expr->eval_fn) { expr->probability = opt_probs[i].probability; break; } } - for (size_t i = 0; i < BFS_COUNTOF(opt_preds); ++i) { + for (size_t i = 0; i < countof(opt_preds); ++i) { if (opt_preds[i].eval_fn == expr->eval_fn) { infer_pred_facts(state, opt_preds[i].pred); break; } } - for (size_t i = 0; i < BFS_COUNTOF(opt_ranges); ++i) { + for (size_t i = 0; i < countof(opt_ranges); ++i) { if (opt_ranges[i].eval_fn == expr->eval_fn) { infer_icmp_facts(state, expr, opt_ranges[i].range); break; } } - for (size_t i = 0; i < BFS_COUNTOF(opt_fns); ++i) { + for (size_t i = 0; i < countof(opt_fns); ++i) { if (opt_fns[i].eval_fn == expr->eval_fn) { return opt_fns[i].opt_fn(state, expr); } diff --git a/tests/trie.c b/tests/trie.c index c2af18a..ced14d4 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -38,7 +38,7 @@ const char *keys[] = { ">>>", }; -const size_t nkeys = BFS_COUNTOF(keys); +const size_t nkeys = countof(keys); int main(void) { struct trie trie; -- cgit v1.2.3 From 3929ddedca049ac6f9dcbe88f85233b6ec1a415a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 May 2023 15:43:31 -0400 Subject: config: s/BFS_FLEX_SIZEOF/flex_sizeof/ --- src/bftw.c | 2 +- src/config.h | 4 ++-- src/dstring.c | 2 +- src/trie.c | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index e4dc411..d2f1e36 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -178,7 +178,7 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { /** Create a new bftw_file. */ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); - size_t size = BFS_FLEX_SIZEOF(struct bftw_file, name, namelen + 1); + size_t size = flex_sizeof(struct bftw_file, name, namelen + 1); struct bftw_file *file = malloc(size); if (!file) { diff --git a/src/config.h b/src/config.h index 60bc930..aa54491 100644 --- a/src/config.h +++ b/src/config.h @@ -136,7 +136,7 @@ */ #define countof(array) (sizeof(array) / sizeof(0[array])) -// Lower bound on BFS_FLEX_SIZEOF() +// Lower bound on flex_sizeof() #define BFS_FLEX_LB(type, member, length) (offsetof(type, member) + sizeof(((type *)NULL)->member[0]) * (length)) // Maximum macro for BFS_FLEX_SIZE() @@ -153,7 +153,7 @@ * @param length * The length of the flexible array. */ -#define BFS_FLEX_SIZEOF(type, member, length) \ +#define flex_sizeof(type, member, length) \ (sizeof(type) <= BFS_FLEX_LB(type, member, 0) \ ? BFS_FLEX_LB(type, member, length) \ : BFS_FLEX_MAX(sizeof(type), BFS_FLEX_LB(type, member, length))) diff --git a/src/dstring.c b/src/dstring.c index 9112e54..05efb10 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -24,7 +24,7 @@ static struct dstring *dstrheader(const char *dstr) { /** Get the correct size for a dstring with the given capacity. */ static size_t dstrsize(size_t capacity) { - return BFS_FLEX_SIZEOF(struct dstring, data, capacity + 1); + return flex_sizeof(struct dstring, data, capacity + 1); } /** Allocate a dstring with the given contents. */ diff --git a/src/trie.c b/src/trie.c index 9f2e45f..bfe97e6 100644 --- a/src/trie.c +++ b/src/trie.c @@ -336,7 +336,7 @@ struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key) { /** Create a new leaf, holding a copy of the given key. */ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, size_t length) { - struct trie_leaf *leaf = malloc(BFS_FLEX_SIZEOF(struct trie_leaf, key, length)); + struct trie_leaf *leaf = malloc(flex_sizeof(struct trie_leaf, key, length)); if (!leaf) { return NULL; } @@ -363,7 +363,7 @@ static size_t trie_node_size(unsigned int size) { // Node size must be a power of two assert(is_power_of_two(size)); - return BFS_FLEX_SIZEOF(struct trie_node, children, size); + return flex_sizeof(struct trie_node, children, size); } #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ -- cgit v1.2.3 From 2d6d2f2cf838dc459bc1ef27476a26af5d82a53f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 May 2023 15:43:57 -0400 Subject: config: s/BFS_UNINIT/uninit/ --- src/config.h | 4 ++-- src/parse.c | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/config.h b/src/config.h index aa54491..b9f2638 100644 --- a/src/config.h +++ b/src/config.h @@ -162,9 +162,9 @@ * Initialize a variable, unless sanitizers would detect uninitialized uses. */ #if __has_feature(memory_sanitizer) -# define BFS_UNINIT(var, value) var +# define uninit(var, value) var #else -# define BFS_UNINIT(var, value) value +# define uninit(var, value) value #endif // Wrappers for attributes diff --git a/src/parse.c b/src/parse.c index 9540e83..7dd851a 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1962,11 +1962,11 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct MODE_PLUS, MODE_MINUS, MODE_EQUALS, - } op = BFS_UNINIT(op, MODE_EQUALS); + } op = uninit(op, MODE_EQUALS); - mode_t who = BFS_UNINIT(who, 0); - mode_t file_change = BFS_UNINIT(file_change, 0); - mode_t dir_change = BFS_UNINIT(dir_change, 0); + mode_t who = uninit(who, 0); + mode_t file_change = uninit(file_change, 0); + mode_t dir_change = uninit(dir_change, 0); const char *i = mode; while (true) { -- cgit v1.2.3 From 87d8d5928325f951f40b5f87292a17586b85943b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 May 2023 15:44:59 -0400 Subject: config: s/BFS_FALLTHROUGH/fallthru/ --- src/bftw.c | 2 +- src/config.h | 6 +++--- src/eval.c | 4 ++-- src/parse.c | 18 +++++++++--------- src/printf.c | 2 +- 5 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index d2f1e36..56701a7 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -758,7 +758,7 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { return BFTW_PRUNE; } - BFS_FALLTHROUGH; + fallthru; case BFTW_PRUNE: case BFTW_STOP: return ret; diff --git a/src/config.h b/src/config.h index b9f2638..229bafb 100644 --- a/src/config.h +++ b/src/config.h @@ -173,11 +173,11 @@ * Silence compiler warnings about switch/case fall-throughs. */ #if __has_c_attribute(fallthrough) -# define BFS_FALLTHROUGH [[fallthrough]] +# define fallthru [[fallthrough]] #elif __has_attribute(fallthrough) -# define BFS_FALLTHROUGH __attribute__((fallthrough)) +# define fallthru __attribute__((fallthrough)) #else -# define BFS_FALLTHROUGH ((void)0) +# define fallthru ((void)0) #endif /** diff --git a/src/eval.c b/src/eval.c index 687ba32..7444ec9 100644 --- a/src/eval.c +++ b/src/eval.c @@ -241,10 +241,10 @@ bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) { switch (expr->time_unit) { case BFS_DAYS: diff /= 60*24; - BFS_FALLTHROUGH; + fallthru; case BFS_MINUTES: diff /= 60; - BFS_FALLTHROUGH; + fallthru; case BFS_SECONDS: break; } diff --git a/src/parse.c b/src/parse.c index 7dd851a..15f38a4 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1082,16 +1082,16 @@ static struct bfs_expr *parse_time(struct parser_state *state, int field, int ar switch (*tail) { case 'w': time *= 7; - BFS_FALLTHROUGH; + fallthru; case 'd': time *= 24; - BFS_FALLTHROUGH; + fallthru; case 'h': time *= 60; - BFS_FALLTHROUGH; + fallthru; case 'm': time *= 60; - BFS_FALLTHROUGH; + fallthru; case 's': break; default: @@ -1974,7 +1974,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct case MODE_CLAUSE: who = 0; mstate = MODE_WHO; - BFS_FALLTHROUGH; + fallthru; case MODE_WHO: switch (*i) { @@ -2001,7 +2001,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct case MODE_EQUALS: expr->file_mode &= ~who; expr->dir_mode &= ~who; - BFS_FALLTHROUGH; + fallthru; case MODE_PLUS: expr->file_mode |= file_change; expr->dir_mode |= dir_change; @@ -2011,7 +2011,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct expr->dir_mode &= ~dir_change; break; } - BFS_FALLTHROUGH; + fallthru; case MODE_ACTION: if (who == 0) { @@ -2093,7 +2093,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct break; case 'x': file_change |= who & 0111; - BFS_FALLTHROUGH; + fallthru; case 'X': dir_change |= who & 0111; break; @@ -2156,7 +2156,7 @@ static struct bfs_expr *parse_perm(struct parser_state *state, int field, int ar ++mode; break; } - BFS_FALLTHROUGH; + fallthru; default: expr->mode_cmp = BFS_MODE_EQUAL; break; diff --git a/src/printf.c b/src/printf.c index 726d54d..454fbee 100644 --- a/src/printf.c +++ b/src/printf.c @@ -680,7 +680,7 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha case '0': case '+': must_be_numeric = true; - BFS_FALLTHROUGH; + fallthru; case ' ': case '-': if (strchr(directive.str, c)) { -- cgit v1.2.3 From 59f87eed2b930af2f31fd1d1fb2589f80f426ee0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 May 2023 10:12:35 -0400 Subject: config: Provide and In anticipation of C23, since those headers won't be necessary any more. --- src/bfstd.c | 1 - src/bfstd.h | 2 +- src/bftw.c | 1 - src/color.c | 1 - src/color.h | 1 - src/config.h | 6 +++++- src/ctx.h | 2 +- src/diag.h | 1 - src/dir.c | 2 -- src/eval.h | 2 +- src/exec.c | 1 - src/expr.h | 2 +- src/fsade.h | 1 - src/main.c | 2 +- src/mtab.c | 1 - src/mtab.h | 2 +- src/opt.c | 1 - src/parse.c | 1 - src/printf.c | 1 - src/pwcache.c | 2 +- src/stat.c | 1 - src/trie.c | 1 - src/trie.h | 1 - src/xspawn.c | 1 - src/xtime.c | 2 +- tests/xtimegm.c | 2 +- tests/xtouch.c | 2 +- 27 files changed, 15 insertions(+), 28 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 1dc322b..932f2c4 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include diff --git a/src/bfstd.h b/src/bfstd.h index 028e4e6..e4fd1f1 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -8,7 +8,7 @@ #ifndef BFS_BFSTD_H #define BFS_BFSTD_H -#include +#include "config.h" #include // #include diff --git a/src/bftw.c b/src/bftw.c index 56701a7..14805de 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -28,7 +28,6 @@ #include #include #include -#include #include #include #include diff --git a/src/color.c b/src/color.c index 589e631..eeadf98 100644 --- a/src/color.c +++ b/src/color.c @@ -15,7 +15,6 @@ #include #include #include -#include #include #include #include diff --git a/src/color.h b/src/color.h index 737c34b..3db2b07 100644 --- a/src/color.h +++ b/src/color.h @@ -10,7 +10,6 @@ #include "config.h" #include -#include #include /** diff --git a/src/config.h b/src/config.h index 229bafb..4408feb 100644 --- a/src/config.h +++ b/src/config.h @@ -8,9 +8,13 @@ #ifndef BFS_CONFIG_H #define BFS_CONFIG_H -#include #include +#if __STDC_VERSION__ < 202311L +# include +# include +#endif + // bfs packaging configuration #ifndef BFS_COMMAND diff --git a/src/ctx.h b/src/ctx.h index 4c748b7..0dc9f08 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -9,8 +9,8 @@ #define BFS_CTX_H #include "bftw.h" +#include "config.h" #include "trie.h" -#include #include #include #include diff --git a/src/diag.h b/src/diag.h index 2952e30..987d4b4 100644 --- a/src/diag.h +++ b/src/diag.h @@ -11,7 +11,6 @@ #include "ctx.h" #include "config.h" #include -#include struct bfs_expr; diff --git a/src/dir.c b/src/dir.c index eb6e3e0..30db5df 100644 --- a/src/dir.c +++ b/src/dir.c @@ -8,8 +8,6 @@ #include #include #include -#include -#include #include #include #include diff --git a/src/eval.h b/src/eval.h index 3d70319..bdb9440 100644 --- a/src/eval.h +++ b/src/eval.h @@ -9,7 +9,7 @@ #ifndef BFS_EVAL_H #define BFS_EVAL_H -#include +#include "config.h" struct bfs_ctx; struct bfs_expr; diff --git a/src/exec.c b/src/exec.c index 6bde1c1..7f22d36 100644 --- a/src/exec.c +++ b/src/exec.c @@ -14,7 +14,6 @@ #include #include #include -#include #include #include #include diff --git a/src/expr.h b/src/expr.h index 1628cac..356689d 100644 --- a/src/expr.h +++ b/src/expr.h @@ -9,9 +9,9 @@ #define BFS_EXPR_H #include "color.h" +#include "config.h" #include "eval.h" #include "stat.h" -#include #include #include #include diff --git a/src/fsade.h b/src/fsade.h index 557da26..0d9ecaf 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -10,7 +10,6 @@ #define BFS_FSADE_H #include "config.h" -#include #define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H diff --git a/src/main.c b/src/main.c index 4f99580..24a5035 100644 --- a/src/main.c +++ b/src/main.c @@ -40,13 +40,13 @@ */ #include "bfstd.h" +#include "config.h" #include "ctx.h" #include "eval.h" #include "parse.h" #include #include #include -#include #include #include #include diff --git a/src/mtab.c b/src/mtab.c index 27f1743..1d1ad94 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -9,7 +9,6 @@ #include "trie.h" #include #include -#include #include #include #include diff --git a/src/mtab.h b/src/mtab.h index 5dfdf6c..ca4372c 100644 --- a/src/mtab.h +++ b/src/mtab.h @@ -8,7 +8,7 @@ #ifndef BFS_MTAB_H #define BFS_MTAB_H -#include +#include "config.h" struct bfs_stat; diff --git a/src/opt.c b/src/opt.c index 731dd10..4ce9425 100644 --- a/src/opt.c +++ b/src/opt.c @@ -39,7 +39,6 @@ #include #include #include -#include #include #include #include diff --git a/src/parse.c b/src/parse.c index 15f38a4..55f1e74 100644 --- a/src/parse.c +++ b/src/parse.c @@ -37,7 +37,6 @@ #include #include #include -#include #include #include #include diff --git a/src/printf.c b/src/printf.c index 454fbee..9ccc216 100644 --- a/src/printf.c +++ b/src/printf.c @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include diff --git a/src/pwcache.c b/src/pwcache.c index 5026dee..f52e4e1 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -2,12 +2,12 @@ // SPDX-License-Identifier: 0BSD #include "pwcache.h" +#include "config.h" #include "darray.h" #include "trie.h" #include #include #include -#include #include #include #include diff --git a/src/stat.c b/src/stat.c index f3d9046..7973d71 100644 --- a/src/stat.c +++ b/src/stat.c @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include diff --git a/src/trie.c b/src/trie.c index bfe97e6..e0e00ff 100644 --- a/src/trie.c +++ b/src/trie.c @@ -86,7 +86,6 @@ #include "list.h" #include #include -#include #include #include #include diff --git a/src/trie.h b/src/trie.h index 58974aa..6bd211e 100644 --- a/src/trie.h +++ b/src/trie.h @@ -5,7 +5,6 @@ #define BFS_TRIE_H #include "config.h" -#include #include #include diff --git a/src/xspawn.c b/src/xspawn.c index a6d18a3..00fb76e 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -7,7 +7,6 @@ #include "list.h" #include #include -#include #include #include #include diff --git a/src/xtime.c b/src/xtime.c index 82690d0..406d694 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -2,9 +2,9 @@ // SPDX-License-Identifier: 0BSD #include "xtime.h" +#include "config.h" #include #include -#include #include #include #include diff --git a/tests/xtimegm.c b/tests/xtimegm.c index bab64ba..b2479b7 100644 --- a/tests/xtimegm.c +++ b/tests/xtimegm.c @@ -2,7 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "../src/xtime.h" -#include +#include "../src/config.h" #include #include #include diff --git a/tests/xtouch.c b/tests/xtouch.c index 7e29547..50416ba 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -2,10 +2,10 @@ // SPDX-License-Identifier: 0BSD #include "../src/bfstd.h" +#include "../src/config.h" #include "../src/xtime.h" #include #include -#include #include #include #include -- cgit v1.2.3 From 7e26443627926bb3bcc88bd790190d5e4e0eda98 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 May 2023 10:38:23 -0400 Subject: config: Properly align flex_sizeof() --- src/config.h | 31 +++++++++++++++++++++---------- tests/bfstd.c | 13 +++++++++++++ 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/src/config.h b/src/config.h index 4408feb..c6dcc1e 100644 --- a/src/config.h +++ b/src/config.h @@ -140,11 +140,12 @@ */ #define countof(array) (sizeof(array) / sizeof(0[array])) -// Lower bound on flex_sizeof() -#define BFS_FLEX_LB(type, member, length) (offsetof(type, member) + sizeof(((type *)NULL)->member[0]) * (length)) - -// Maximum macro for BFS_FLEX_SIZE() -#define BFS_FLEX_MAX(a, b) ((a) > (b) ? (a) : (b)) +/** + * Round up to a multiple of an alignment. + */ +static inline size_t align_ceil(size_t align, size_t size) { + return (size + align - 1) & ~(align - 1); +} /** * Computes the size of a struct containing a flexible array member of the given @@ -154,13 +155,23 @@ * The type of the struct containing the flexible array. * @param member * The name of the flexible array member. - * @param length + * @param count * The length of the flexible array. */ -#define flex_sizeof(type, member, length) \ - (sizeof(type) <= BFS_FLEX_LB(type, member, 0) \ - ? BFS_FLEX_LB(type, member, length) \ - : BFS_FLEX_MAX(sizeof(type), BFS_FLEX_LB(type, member, length))) +#define flex_sizeof(type, member, count) \ + flex_sizeof_impl(alignof(type), sizeof(type), offsetof(type, member), sizeof(((type *)NULL)->member[0]), count) + +static inline size_t flex_sizeof_impl(size_t align, size_t min, size_t offset, size_t size, size_t count) { + size_t ret = align_ceil(align, offset + size * count); + + // Make sure flex_sizeof(type, member, 0) >= sizeof(type), even if the + // type has more padding than necessary for alignment + if (min > align_ceil(align, offset) && ret < min) { + ret = min; + } + + return ret; +} /** * Initialize a variable, unless sanitizers would detect uninitialized uses. diff --git a/tests/bfstd.c b/tests/bfstd.c index 8c61072..c6a9e9f 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,8 +1,12 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#undef NDEBUG #include "../src/bfstd.h" +#include "../src/config.h" +#include #include +#include #include #include @@ -30,6 +34,15 @@ static void check_base_dir(const char *path, const char *dir, const char *base) } int main(void) { + // Check flex_sizeof() + struct flexible { + alignas(64) int foo; + int bar[]; + }; + assert(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); + assert(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); + assert(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); + // From man 3p basename check_base_dir("usr", ".", "usr"); check_base_dir("usr/", ".", "usr"); -- cgit v1.2.3 From a7932050f65844fb1f1145fee87c72aadaf4f995 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 May 2023 13:08:43 -0400 Subject: config: Saturate on overflow in flex_sizeof() --- src/config.h | 12 +++++++++++- tests/bfstd.c | 1 + 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index c6dcc1e..b2c58be 100644 --- a/src/config.h +++ b/src/config.h @@ -162,7 +162,17 @@ static inline size_t align_ceil(size_t align, size_t size) { flex_sizeof_impl(alignof(type), sizeof(type), offsetof(type, member), sizeof(((type *)NULL)->member[0]), count) static inline size_t flex_sizeof_impl(size_t align, size_t min, size_t offset, size_t size, size_t count) { - size_t ret = align_ceil(align, offset + size * count); + size_t ret = size * count; + size_t overflow = ret / size != count; + + ret += offset; + overflow |= ret < offset; + + size_t mask = align - 1; + ret += mask; + overflow |= ret < mask; + ret &= ~mask; + ret |= -overflow; // Make sure flex_sizeof(type, member, 0) >= sizeof(type), even if the // type has more padding than necessary for alignment diff --git a/tests/bfstd.c b/tests/bfstd.c index c6a9e9f..1917a53 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -41,6 +41,7 @@ int main(void) { }; assert(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); assert(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); + assert(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) == SIZE_MAX); assert(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); // From man 3p basename -- cgit v1.2.3 From 72cd1c2a584d89e76a5e9e7deaf07ddc4196d1aa Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 May 2023 14:07:54 -0400 Subject: diag: New bfs_static_assert() macro --- src/diag.h | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/diag.h b/src/diag.h index 987d4b4..a086942 100644 --- a/src/diag.h +++ b/src/diag.h @@ -2,7 +2,7 @@ // SPDX-License-Identifier: 0BSD /** - * Formatters for diagnostic messages. + * Diagnostic messages. */ #ifndef BFS_DIAG_H @@ -12,6 +12,16 @@ #include "config.h" #include +/** + * static_assert() with an optional second argument. + */ +#if __STDC_VERSION__ >= 202311L +# define bfs_static_assert static_assert +#else +# define bfs_static_assert(...) BFS_STATIC_ASSERT(__VA_ARGS__, #__VA_ARGS__, ) +# define BFS_STATIC_ASSERT(expr, msg, ...) _Static_assert(expr, msg) +#endif + struct bfs_expr; /** -- cgit v1.2.3 From e49bf693efbe7ea54ac2c812c574cae1a09bbebf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 11:29:34 -0400 Subject: trie: Use bfs_static_assert() --- src/trie.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/trie.c b/src/trie.c index e0e00ff..5427d79 100644 --- a/src/trie.c +++ b/src/trie.c @@ -83,6 +83,7 @@ #include "trie.h" #include "config.h" +#include "diag.h" #include "list.h" #include #include @@ -90,7 +91,7 @@ #include #include -static_assert(CHAR_BIT == 8, "This trie implementation assumes 8-bit bytes."); +bfs_static_assert(CHAR_BIT == 8); #if BFS_TARGET_CLONES && (__i386__ || __x86_64__) # define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) -- cgit v1.2.3 From c13171fd7177c843e2e08417297babf99a365f1c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 10:23:20 -0400 Subject: build: Make the tests a little less repetitive --- Makefile | 34 ++++++++++++++++------------------ 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/Makefile b/Makefile index 7113bc4..f0e4e1e 100644 --- a/Makefile +++ b/Makefile @@ -189,13 +189,7 @@ $(FLAG_GOALS): $(FLAG_PREREQS) @: .PHONY: $(FLAG_GOALS) -all: \ - $(BIN)/bfs \ - $(BIN)/tests/bfstd \ - $(BIN)/tests/mksock \ - $(BIN)/tests/trie \ - $(BIN)/tests/xtimegm \ - $(BIN)/tests/xtouch +all: bfs tests .PHONY: all $(BIN)/%: @@ -245,31 +239,35 @@ LIBBFS := \ # The main executable $(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) -# Standalone binary tests -STANDALONE_CHECKS := check-bfstd check-trie check-xtimegm +# Standalone unit tests +UNITS := bfstd trie xtimegm +UNIT_TESTS := $(UNITS:%=$(BIN)/tests/%) +UNIT_CHECKS := $(UNITS:%=check-%) + +# Testing utilities +TEST_UTILS := $(BIN)/tests/mksock $(BIN)/tests/xtouch + +tests: $(UNIT_TESTS) $(TEST_UTILS) +.PHONY: tests + +$(UNIT_TESTS): $(BIN)/tests/%: $(OBJ)/tests/%.o $(LIBBFS) # The different search strategies that we test STRATEGIES := bfs dfs ids eds STRATEGY_CHECKS := $(STRATEGIES:%=check-%) # All the different checks we run -CHECKS := $(STANDALONE_CHECKS) $(STRATEGY_CHECKS) +CHECKS := $(UNIT_CHECKS) $(STRATEGY_CHECKS) check: $(CHECKS) .PHONY: check $(CHECKS) -$(STANDALONE_CHECKS): check-%: $(BIN)/tests/% +$(UNIT_CHECKS): check-%: $(BIN)/tests/% $< -$(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(BIN)/tests/mksock $(BIN)/tests/xtouch +$(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(TEST_UTILS) ./tests/tests.sh --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) -$(BIN)/tests/bfstd: $(OBJ)/tests/bfstd.o $(LIBBFS) -$(BIN)/tests/mksock: $(OBJ)/tests/mksock.o $(LIBBFS) -$(BIN)/tests/trie: $(OBJ)/tests/trie.o $(LIBBFS) -$(BIN)/tests/xtimegm: $(OBJ)/tests/xtimegm.o $(LIBBFS) -$(BIN)/tests/xtouch: $(OBJ)/tests/xtouch.o $(LIBBFS) - # Custom test flags for distcheck DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" -- cgit v1.2.3 From 6b0209f04a7a2fd3f8aec78808225177647c3aec Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 10:24:29 -0400 Subject: int: Backport C23's _WIDTH macros --- Makefile | 2 +- src/int.h | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/int.c | 54 ++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 1 deletion(-) create mode 100644 src/int.h create mode 100644 tests/int.c diff --git a/Makefile b/Makefile index f0e4e1e..40f6b17 100644 --- a/Makefile +++ b/Makefile @@ -240,7 +240,7 @@ LIBBFS := \ $(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) # Standalone unit tests -UNITS := bfstd trie xtimegm +UNITS := bfstd int trie xtimegm UNIT_TESTS := $(UNITS:%=$(BIN)/tests/%) UNIT_CHECKS := $(UNITS:%=check-%) diff --git a/src/int.h b/src/int.h new file mode 100644 index 0000000..56fabee --- /dev/null +++ b/src/int.h @@ -0,0 +1,100 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Bits & bytes. + */ + +#ifndef BFS_INT_H +#define BFS_INT_H + +#include +#include + +// C23 polyfill: _WIDTH macros + +// The U*_MAX macros are of the form 2**n - 1, and we want to extract the n. +// One way would be *_WIDTH = popcount(*_MAX). Alternatively, we can use +// Hallvard B. Furuseth's technique from [1], which is shorter. +// +// [1]: https://groups.google.com/g/comp.lang.c/c/NfedEFBFJ0k + +// Let mask be of the form 2**m - 1, e.g. 0b111, and let n range over +// [0b0, 0b1, 0b11, 0b111, 0b1111, ...]. Then we have +// +// n % 0b111 +// == [0b0, 0b1, 0b11, 0b0, 0b1, 0b11, ...] +// n / (n % 0b111 + 1) +// == [0b0 (x3), 0b111 (x3), 0b111111 (x3), ...] +// n / (n % 0b111 + 1) / 0b111 +// == [0b0 (x3), 0b1 (x3), 0b1001 (x3), 0b1001001 (x3), ...] +// n / (n % 0b111 + 1) / 0b111 % 0b111 +// == [0 (x3), 1 (x3), 2 (x3), ...] +// == UMAX_CHUNK(n, 0b111) +#define UMAX_CHUNK(n, mask) (n / (n % mask + 1) / mask % mask) + +// 8 * UMAX_CHUNK(n, 255) gives [0 (x8), 8 (x8), 16 (x8), ...]. To that we add +// [0, 1, 2, ..., 6, 7, 0, 1, ...], which we get from a linear interpolation on +// n % 255: +// +// n % 255 +// == [0, 1, 3, 7, 15, 31, 63, 127, 0, ...] +// 86 / (n % 255 + 12) +// == [7, 6, 5, 4, 3, 2, 1, 0, 7, ...] +#define UMAX_INTERP(n) (7 - 86 / (n % 255 + 12)) + +#define UMAX_WIDTH(n) (8 * UMAX_CHUNK(n, 255) + UMAX_INTERP(n)) + +#ifndef CHAR_WIDTH +# define CHAR_WIDTH CHAR_BIT +#endif +#ifndef UCHAR_WIDTH +# define UCHAR_WIDTH CHAR_WIDTH +#endif +#ifndef SCHAR_WIDTH +# define SCHAR_WIDTH CHAR_WIDTH +#endif +#ifndef USHRT_WIDTH +# define USHRT_WIDTH UMAX_WIDTH(USHRT_MAX) +#endif +#ifndef SHRT_WIDTH +# define SHRT_WIDTH USHRT_WIDTH +#endif +#ifndef UINT_WIDTH +# define UINT_WIDTH UMAX_WIDTH(UINT_MAX) +#endif +#ifndef INT_WIDTH +# define INT_WIDTH UINT_WIDTH +#endif +#ifndef ULONG_WIDTH +# define ULONG_WIDTH UMAX_WIDTH(ULONG_MAX) +#endif +#ifndef LONG_WIDTH +# define LONG_WIDTH ULONG_WIDTH +#endif +#ifndef ULLONG_WIDTH +# define ULLONG_WIDTH UMAX_WIDTH(ULLONG_MAX) +#endif +#ifndef LLONG_WIDTH +# define LLONG_WIDTH ULLONG_WIDTH +#endif +#ifndef SIZE_WIDTH +# define SIZE_WIDTH UMAX_WIDTH(SIZE_MAX) +#endif +#ifndef PTRDIFF_WIDTH +# define PTRDIFF_WIDTH (UMAX_WIDTH(PTRDIFF_MAX) + 1) +#endif +#ifndef UINTPTR_WIDTH +# define UINTPTR_WIDTH UMAX_WIDTH(UINTPTR_MAX) +#endif +#ifndef INTPTR_WIDTH +# define INTPTR_WIDTH UINTPTR_WIDTH +#endif +#ifndef UINTMAX_WIDTH +# define UINTMAX_WIDTH UMAX_WIDTH(UINTMAX_MAX) +#endif +#ifndef INTMAX_WIDTH +# define INTMAX_WIDTH UINTMAX_WIDTH +#endif + +#endif // BFS_INT_H diff --git a/tests/int.c b/tests/int.c new file mode 100644 index 0000000..db59e90 --- /dev/null +++ b/tests/int.c @@ -0,0 +1,54 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "../src/int.h" +#include "../src/diag.h" +#include +#include + +bfs_static_assert(UMAX_WIDTH(0x1) == 1); +bfs_static_assert(UMAX_WIDTH(0x3) == 2); +bfs_static_assert(UMAX_WIDTH(0x7) == 3); +bfs_static_assert(UMAX_WIDTH(0xF) == 4); +bfs_static_assert(UMAX_WIDTH(0xFF) == 8); +bfs_static_assert(UMAX_WIDTH(0xFFF) == 12); +bfs_static_assert(UMAX_WIDTH(0xFFFF) == 16); + +#define UWIDTH_MAX(n) (2 * ((UINTMAX_C(1) << ((n) - 1)) - 1) + 1) +#define IWIDTH_MAX(n) UWIDTH_MAX((n) - 1) +#define IWIDTH_MIN(n) (-(intmax_t)IWIDTH_MAX(n) - 1) + +bfs_static_assert(UCHAR_MAX == UWIDTH_MAX(UCHAR_WIDTH)); +bfs_static_assert(SCHAR_MIN == IWIDTH_MIN(SCHAR_WIDTH)); +bfs_static_assert(SCHAR_MAX == IWIDTH_MAX(SCHAR_WIDTH)); + +bfs_static_assert(USHRT_MAX == UWIDTH_MAX(USHRT_WIDTH)); +bfs_static_assert(SHRT_MIN == IWIDTH_MIN(SHRT_WIDTH)); +bfs_static_assert(SHRT_MAX == IWIDTH_MAX(SHRT_WIDTH)); + +bfs_static_assert(UINT_MAX == UWIDTH_MAX(UINT_WIDTH)); +bfs_static_assert(INT_MIN == IWIDTH_MIN(INT_WIDTH)); +bfs_static_assert(INT_MAX == IWIDTH_MAX(INT_WIDTH)); + +bfs_static_assert(ULONG_MAX == UWIDTH_MAX(ULONG_WIDTH)); +bfs_static_assert(LONG_MIN == IWIDTH_MIN(LONG_WIDTH)); +bfs_static_assert(LONG_MAX == IWIDTH_MAX(LONG_WIDTH)); + +bfs_static_assert(ULLONG_MAX == UWIDTH_MAX(ULLONG_WIDTH)); +bfs_static_assert(LLONG_MIN == IWIDTH_MIN(LLONG_WIDTH)); +bfs_static_assert(LLONG_MAX == IWIDTH_MAX(LLONG_WIDTH)); + +bfs_static_assert(SIZE_MAX == UWIDTH_MAX(SIZE_WIDTH)); +bfs_static_assert(PTRDIFF_MIN == IWIDTH_MIN(PTRDIFF_WIDTH)); +bfs_static_assert(PTRDIFF_MAX == IWIDTH_MAX(PTRDIFF_WIDTH)); + +bfs_static_assert(UINTPTR_MAX == UWIDTH_MAX(UINTPTR_WIDTH)); +bfs_static_assert(INTPTR_MIN == IWIDTH_MIN(INTPTR_WIDTH)); +bfs_static_assert(INTPTR_MAX == IWIDTH_MAX(INTPTR_WIDTH)); + +bfs_static_assert(UINTMAX_MAX == UWIDTH_MAX(UINTMAX_WIDTH)); +bfs_static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH)); +bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); + +int main(void) { +} -- cgit v1.2.3 From 9ee1ca387d59a2d1281c310915a9853a57b11a1e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 10:26:35 -0400 Subject: trie: Use the _WIDTH macros --- src/trie.c | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/trie.c b/src/trie.c index 5427d79..becddbf 100644 --- a/src/trie.c +++ b/src/trie.c @@ -84,6 +84,7 @@ #include "trie.h" #include "config.h" #include "diag.h" +#include "int.h" #include "list.h" #include #include @@ -369,19 +370,19 @@ static size_t trie_node_size(unsigned int size) { #if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ # define TRIE_BSWAP(n) (n) #elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -# if __SIZEOF_SIZE_T__ == 8 +# if SIZE_WIDTH == 8 # define TRIE_BSWAP(n) __builtin_bswap64(n) -# elif __SIZEOF_SIZE_T__ == 4 +# elif SIZE_WIDTH == 4 # define TRIE_BSWAP(n) __builtin_bswap32(n) # endif #endif #ifdef TRIE_BSWAP -# if __SIZEOF_SIZE_T__ == __SIZEOF_LONG_LONG__ +# if SIZE_WIDTH == LLONG_WIDTH # define TRIE_CTZ(n) __builtin_ctzll(n) -# elif __SIZEOF_SIZE_T__ == __SIZEOF_LONG__ +# elif SiZE_WIDTH == LONG_WIDTH # define TRIE_CTZ(n) __builtin_ctzl(n) -# elif __SIZEOF_SIZE_T__ == __SIZEOF_INT__ +# elif SIZE_WIDTH == INT_WIDTH # define TRIE_CTZ(n) __builtin_ctz(n) # endif #endif -- cgit v1.2.3 From fe472f30e1b82f762993cbc5376ff9b25c605aa9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 10:48:50 -0400 Subject: int: Backport C23's endian utilities --- src/int.h | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/int.c | 10 +++++++++ 2 files changed, 78 insertions(+) diff --git a/src/int.h b/src/int.h index 56fabee..1cd455a 100644 --- a/src/int.h +++ b/src/int.h @@ -11,6 +11,10 @@ #include #include +#if __STDC_VERSION__ >= 202311L +# include +#endif + // C23 polyfill: _WIDTH macros // The U*_MAX macros are of the form 2**n - 1, and we want to extract the n. @@ -97,4 +101,68 @@ # define INTMAX_WIDTH UINTMAX_WIDTH #endif +// C23 polyfill: byte order + +#ifdef __STDC_ENDIAN_LITTLE__ +# define ENDIAN_LITTLE __STDC_ENDIAN_LITTLE__ +#elif defined(__ORDER_LITTLE_ENDIAN__) +# define ENDIAN_LITTLE __ORDER_LITTLE_ENDIAN__ +#else +# define ENDIAN_LITTLE 1234 +#endif + +#ifdef __STDC_ENDIAN_BIG__ +# define ENDIAN_BIG __STDC_ENDIAN_BIG__ +#elif defined(__ORDER_BIG_ENDIAN__) +# define ENDIAN_BIG __ORDER_BIG_ENDIAN__ +#else +# define ENDIAN_BIG 4321 +#endif + +#ifdef __STDC_ENDIAN_NATIVE__ +# define ENDIAN_NATIVE __STDC_ENDIAN_NATIVE__ +#elif defined(__ORDER_NATIVE_ENDIAN__) +# define ENDIAN_NATIVE __ORDER_NATIVE_ENDIAN__ +#else +# define ENDIAN_NATIVE 0 +#endif + +#if __STDC_VERSION__ >= 202311L +# define bswap16 stdc_memreverse8u16 +# define bswap32 stdc_memreverse8u32 +# define bswap64 stdc_memreverse8u64 +#elif __GNUC__ +# define bswap16 __builtin_bswap16 +# define bswap32 __builtin_bswap32 +# define bswap64 __builtin_bswap64 +#else + +static inline uint16_t bswap16(uint16_t n) { + return (n << 8) | (n >> 8); +} + +static inline uint32_t bswap32(uint32_t n) { + return ((uint32_t)bswap16(n) << 16) | bswap16(n >> 16); +} + +static inline uint64_t bswap64(uint64_t n) { + return ((uint64_t)bswap32(n) << 32) | bswap32(n >> 32); +} + +#endif + +static inline uint8_t bswap8(uint8_t n) { + return n; +} + +/** + * Reverse the byte order of an integer. + */ +#define bswap(n) \ + _Generic((n), \ + uint8_t: bswap8, \ + uint16_t: bswap16, \ + uint32_t: bswap32, \ + uint64_t: bswap64)(n) + #endif // BFS_INT_H diff --git a/tests/int.c b/tests/int.c index db59e90..0039862 100644 --- a/tests/int.c +++ b/tests/int.c @@ -1,10 +1,14 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#undef NDEBUG + #include "../src/int.h" #include "../src/diag.h" +#include #include #include +#include bfs_static_assert(UMAX_WIDTH(0x1) == 1); bfs_static_assert(UMAX_WIDTH(0x3) == 2); @@ -51,4 +55,10 @@ bfs_static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH)); bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); int main(void) { + assert(bswap((uint8_t)0x12) == 0x12); + assert(bswap((uint16_t)0x1234) == 0x3412); + assert(bswap((uint32_t)0x12345678) == 0x78563412); + assert(bswap((uint64_t)0x1234567812345678) == 0x7856341278563412); + + return EXIT_SUCCESS; } -- cgit v1.2.3 From 7cd9d40ee0666963334fca7ae44cae2f779cd4cc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 10:49:07 -0400 Subject: trie: Use ENDIAN_* and bswap() --- src/trie.c | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/src/trie.c b/src/trie.c index becddbf..02d1ae9 100644 --- a/src/trie.c +++ b/src/trie.c @@ -367,14 +367,10 @@ static size_t trie_node_size(unsigned int size) { return flex_sizeof(struct trie_node, children, size); } -#if defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ +#if ENDIAN_NATIVE == ENDIAN_LITTLE # define TRIE_BSWAP(n) (n) -#elif defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__ -# if SIZE_WIDTH == 8 -# define TRIE_BSWAP(n) __builtin_bswap64(n) -# elif SIZE_WIDTH == 4 -# define TRIE_BSWAP(n) __builtin_bswap32(n) -# endif +#elif ENDIAN_NATIVE == ENDIAN_BIG +# define TRIE_BSWAP(n) bswap(n) #endif #ifdef TRIE_BSWAP -- cgit v1.2.3 From c860ad15979ac0cc6060529aa2027b2724825ca0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 11:01:16 -0400 Subject: int: Backport C23's bit utilities --- src/int.h | 187 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/int.c | 57 ++++++++++++++++++ 2 files changed, 244 insertions(+) diff --git a/src/int.h b/src/int.h index 1cd455a..885933d 100644 --- a/src/int.h +++ b/src/int.h @@ -8,6 +8,7 @@ #ifndef BFS_INT_H #define BFS_INT_H +#include "config.h" #include #include @@ -165,4 +166,190 @@ static inline uint8_t bswap8(uint8_t n) { uint32_t: bswap32, \ uint64_t: bswap64)(n) +// Define an overload for each unsigned type +#define UINT_OVERLOADS(macro) \ + macro(unsigned char, _uc, UCHAR_WIDTH) \ + macro(unsigned short, _us, USHRT_WIDTH) \ + macro(unsigned int, _ui, UINT_WIDTH) \ + macro(unsigned long, _ul, ULONG_WIDTH) \ + macro(unsigned long long, _ull, ULLONG_WIDTH) + +// 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__ >= 202311L +# 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 +# define trailing_ones stdc_trailing_ones +# define first_leading_zero stdc_first_leading_zero +# define first_leading_one stdc_first_leading_one +# define first_trailing_zero stdc_first_trailing_zero +# define first_trailing_one stdc_first_trailing_one +# define has_single_bit stdc_has_single_bit +# define bit_width stdc_bit_width +# define bit_ceil stdc_bit_ceil +# define bit_floor stdc_bit_floor +#else + +#if __GNUC__ + +// GCC provides builtins for unsigned {int,long,long long}, so promote char/short +#define UINT_BUILTIN_uc(name) __builtin_##name +#define UINT_BUILTIN_us(name) __builtin_##name +#define UINT_BUILTIN_ui(name) __builtin_##name +#define UINT_BUILTIN_ul(name) __builtin_##name##l +#define UINT_BUILTIN_ull(name) __builtin_##name##ll +#define UINT_BUILTIN(name, suffix) UINT_BUILTIN##suffix(name) + +#define BUILTIN_WIDTH_uc UINT_WIDTH +#define BUILTIN_WIDTH_us UINT_WIDTH +#define BUILTIN_WIDTH_ui UINT_WIDTH +#define BUILTIN_WIDTH_ul ULONG_WIDTH +#define BUILTIN_WIDTH_ull ULLONG_WIDTH +#define BUILTIN_WIDTH(suffix) BUILTIN_WIDTH##suffix + +#define COUNT_ONES(type, suffix, width) \ + static inline 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) { \ + 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) { \ + return n ? UINT_BUILTIN(ctz, suffix)(n) : width; \ + } + +#define FIRST_TRAILING_ONE(type, suffix, width) \ + static inline 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) { \ + int ret; \ + for (ret = 0; n; ++ret) { \ + n &= n - 1; \ + } \ + return ret; \ + } + +#define LEADING_ZEROS(type, suffix, width) \ + static inline int leading_zeros##suffix(type n) { \ + type bit = (type)1 << (width - 1); \ + int ret; \ + for (ret = 0; bit && !(n & bit); ++ret, bit >>= 1); \ + return ret; \ + } + +#define TRAILING_ZEROS(type, suffix, width) \ + static inline int trailing_zeros##suffix(type n) { \ + type bit = 1; \ + int ret; \ + for (ret = 0; bit && !(n & bit); ++ret, bit <<= 1); \ + return ret; \ + } + +#define FIRST_TRAILING_ONE(type, suffix, width) \ + static inline int first_trailing_one##suffix(type n) { \ + return n ? trailing_zeros##suffix(n) + 1 : 0; \ + } + +#endif // !__GNUC__ + +UINT_OVERLOADS(COUNT_ONES) +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); \ + } + +#define HAS_SINGLE_BIT(type, suffix, width) \ + static inline bool has_single_bit##suffix(type n) { \ + return n && !(n & (n - 1)); \ + } + +UINT_OVERLOADS(ROTATE_LEFT) +UINT_OVERLOADS(ROTATE_RIGHT) +UINT_OVERLOADS(FIRST_LEADING_ONE) +UINT_OVERLOADS(HAS_SINGLE_BIT) + +#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)) + +#define trailing_zeros(n) UINT_SELECT(n, trailing_zeros)(n) +#define trailing_ones(n) UINT_SELECT(n, trailing_zeros)(~(n)) + +#define first_leading_one(n) UINT_SELECT(n, first_leading_one)(n) +#define first_leading_zero(n) UINT_SELECT(n, first_leading_one)(~(n)) + +#define first_trailing_one(n) UINT_SELECT(n, first_trailing_one)(n) +#define first_trailing_zero(n) UINT_SELECT(n, first_trailing_one)(~(n)) + +#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_CEIL(type, suffix, width) \ + static inline type bit_ceil##suffix(type n) { \ + return (type)1 << first_leading_one##suffix(n - !!n); \ + } + +UINT_OVERLOADS(BIT_FLOOR) +UINT_OVERLOADS(BIT_CEIL) + +#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) + +#endif // __STDC_VERSION__ < 202311L + #endif // BFS_INT_H diff --git a/tests/int.c b/tests/int.c index 0039862..e59efde 100644 --- a/tests/int.c +++ b/tests/int.c @@ -60,5 +60,62 @@ int main(void) { assert(bswap((uint32_t)0x12345678) == 0x78563412); assert(bswap((uint64_t)0x1234567812345678) == 0x7856341278563412); + assert(count_ones(0x0) == 0); + assert(count_ones(0x1) == 1); + assert(count_ones(0x2) == 1); + assert(count_ones(0x3) == 2); + assert(count_ones(0x137F) == 10); + + assert(count_zeros(0) == INT_WIDTH); + assert(count_zeros(0L) == LONG_WIDTH); + assert(count_zeros(0LL) == LLONG_WIDTH); + assert(count_zeros((uint8_t)0) == 8); + assert(count_zeros((uint16_t)0) == 16); + assert(count_zeros((uint32_t)0) == 32); + assert(count_zeros((uint64_t)0) == 64); + + assert(rotate_left((uint8_t)0xA1, 4) == 0x1A); + assert(rotate_left((uint16_t)0x1234, 12) == 0x4123); + assert(rotate_left((uint32_t)0x12345678, 20) == 0x67812345); + assert(rotate_left((uint32_t)0x12345678, 0) == 0x12345678); + + assert(rotate_right((uint8_t)0xA1, 4) == 0x1A); + assert(rotate_right((uint16_t)0x1234, 12) == 0x2341); + assert(rotate_right((uint32_t)0x12345678, 20) == 0x45678123); + assert(rotate_right((uint32_t)0x12345678, 0) == 0x12345678); + + for (int i = 0; i < 16; ++i) { + uint16_t n = (uint16_t)1 << i; + for (int j = i; j < 16; ++j) { + uint16_t m = (uint16_t)1 << j; + uint16_t nm = n | m; + assert(count_ones(nm) == 1 + (n != m)); + assert(count_zeros(nm) == 15 - (n != m)); + assert(leading_zeros(nm) == 15 - j); + assert(trailing_zeros(nm) == i); + assert(first_leading_one(nm) == j + 1); + assert(first_trailing_one(nm) == i + 1); + assert(bit_width(nm) == j + 1); + assert(bit_floor(nm) == m); + if (n == m) { + assert(bit_ceil(nm) == m); + assert(has_single_bit(nm)); + } else { + if (j < 15) { + assert(bit_ceil(nm) == (m << 1)); + } + assert(!has_single_bit(nm)); + } + } + } + + assert(leading_zeros((uint16_t)0) == 16); + assert(trailing_zeros((uint16_t)0) == 16); + assert(first_leading_one(0) == 0); + assert(first_trailing_one(0) == 0); + assert(bit_width(0) == 0); + assert(bit_floor(0) == 0); + assert(bit_ceil(0) == 1); + return EXIT_SUCCESS; } -- cgit v1.2.3 From 8302f1d0441b3105470426105b3031961e066535 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 11:01:25 -0400 Subject: trie: Use standard bit utilities --- src/trie.c | 59 +++++++++++++++-------------------------------------------- 1 file changed, 15 insertions(+), 44 deletions(-) diff --git a/src/trie.c b/src/trie.c index 02d1ae9..2226890 100644 --- a/src/trie.c +++ b/src/trie.c @@ -168,25 +168,6 @@ void trie_init(struct trie *trie) { LIST_INIT(trie); } -/** Check if a number is a power of two. */ -static bool is_power_of_two(size_t n) { - return (n & (n - 1)) == 0; -} - -/** Compute the popcount (Hamming weight) of a bitmap. */ -static unsigned int trie_popcount(unsigned int n) { -#if __GNUC__ - return __builtin_popcount(n); -#else - // See https://en.wikipedia.org/wiki/Hamming_weight#Efficient_implementation - n -= (n >> 1) & 0x5555; - n = (n & 0x3333) + ((n >> 2) & 0x3333); - n = (n + (n >> 4)) & 0x0F0F; - n = (n + (n >> 8)) & 0xFF; - return n; -#endif -} - /** Extract the nibble at a certain offset from a byte sequence. */ static unsigned char trie_key_nibble(const void *key, size_t offset) { const unsigned char *bytes = key; @@ -226,7 +207,7 @@ static struct trie_leaf *trie_representative(const struct trie *trie, const void unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { - index = trie_popcount(node->bitmap & (bit - 1)); + index = count_ones(node->bitmap & (bit - 1)); } } ptr = node->children[index]; @@ -316,7 +297,7 @@ static struct trie_leaf *trie_find_prefix_impl(const struct trie *trie, const ch unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { - unsigned int index = trie_popcount(node->bitmap & (bit - 1)); + unsigned int index = count_ones(node->bitmap & (bit - 1)); ptr = node->children[index]; } else { return best; @@ -362,7 +343,7 @@ static size_t trie_node_size(unsigned int size) { // Empty nodes aren't supported assert(size > 0); // Node size must be a power of two - assert(is_power_of_two(size)); + assert(has_single_bit(size)); return flex_sizeof(struct trie_node, children, size); } @@ -373,16 +354,6 @@ static size_t trie_node_size(unsigned int size) { # define TRIE_BSWAP(n) bswap(n) #endif -#ifdef TRIE_BSWAP -# if SIZE_WIDTH == LLONG_WIDTH -# define TRIE_CTZ(n) __builtin_ctzll(n) -# elif SiZE_WIDTH == LONG_WIDTH -# define TRIE_CTZ(n) __builtin_ctzl(n) -# elif SIZE_WIDTH == INT_WIDTH -# define TRIE_CTZ(n) __builtin_ctz(n) -# endif -#endif - /** Find the offset of the first nibble that differs between two keys. */ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t length) { if (!rep) { @@ -403,10 +374,10 @@ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t memcpy(&key_chunk, key_bytes + i, sizeof(key_chunk)); if (rep_chunk != key_chunk) { -#ifdef TRIE_CTZ +#ifdef TRIE_BSWAP size_t diff = TRIE_BSWAP(rep_chunk ^ key_chunk); i *= 2; - i += TRIE_CTZ(diff) / 4; + i += trailing_zeros(diff) / 4; return i; #else break; @@ -449,10 +420,10 @@ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t TARGET_CLONES_POPCNT 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_popcount(node->bitmap); + unsigned int size = count_ones(node->bitmap); // Double the capacity every power of two - if (is_power_of_two(size)) { + if (has_single_bit(size)) { node = realloc(node, trie_node_size(2 * size)); if (!node) { trie_leaf_free(trie, leaf); @@ -467,7 +438,7 @@ static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, str assert(!(node->bitmap & bit)); node->bitmap |= bit; - unsigned int target = trie_popcount(node->bitmap & (bit - 1)); + unsigned int target = count_ones(node->bitmap & (bit - 1)); for (size_t i = size; i > target; --i) { node->children[i] = node->children[i - 1]; } @@ -600,7 +571,7 @@ static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key unsigned int bit = 1U << nibble; if (node->bitmap & bit) { assert(offset < mismatch); - unsigned int index = trie_popcount(node->bitmap & (bit - 1)); + unsigned int index = count_ones(node->bitmap & (bit - 1)); ptr = &node->children[index]; } else { assert(offset == mismatch); @@ -629,7 +600,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 - assert(is_power_of_two(node->bitmap)); + assert(has_single_bit(node->bitmap)); ptr = node->children[0]; free(node); @@ -686,10 +657,10 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { unsigned int bit = 1U << nibble; unsigned int bitmap = node->bitmap; assert(bitmap & bit); - unsigned int index = trie_popcount(bitmap & (bit - 1)); + unsigned int index = count_ones(bitmap & (bit - 1)); // Advance the parent pointer, unless this node had only one child - if (!is_power_of_two(bitmap)) { + if (!has_single_bit(bitmap)) { parent = child; child_bit = bit; child_index = index; @@ -711,7 +682,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { trie_free_singletons(trie, *child); node->bitmap ^= child_bit; - unsigned int parent_size = trie_popcount(node->bitmap); + unsigned int parent_size = count_ones(node->bitmap); assert(parent_size > 0); if (parent_size == 1 && trie_collapse_node(parent, node, child_index) == 0) { return; @@ -721,7 +692,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { memmove(child, child + 1, (parent_size - child_index)*sizeof(*child)); } - if (is_power_of_two(parent_size)) { + if (has_single_bit(parent_size)) { node = realloc(node, trie_node_size(parent_size)); if (node) { *parent = trie_encode_node(node); @@ -740,7 +711,7 @@ static void free_trie_ptr(uintptr_t ptr) { free(trie_decode_leaf(ptr)); } else { struct trie_node *node = trie_decode_node(ptr); - size_t size = trie_popcount(node->bitmap); + size_t size = count_ones(node->bitmap); for (size_t i = 0; i < size; ++i) { free_trie_ptr(node->children[i]); } -- cgit v1.2.3 From 8706515d905038ee7d869ab5ace0471c781d2624 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 11:37:53 -0400 Subject: config: Align after saturating in flex_sizeof() This ensures that it's legal to call aligned_alloc() with the result, which requires a multiple of the alignment. --- src/config.h | 11 +++++++++-- tests/bfstd.c | 3 ++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/config.h b/src/config.h index b2c58be..47aa529 100644 --- a/src/config.h +++ b/src/config.h @@ -140,11 +140,18 @@ */ #define countof(array) (sizeof(array) / sizeof(0[array])) +/** + * Round down to a multiple of an alignment. + */ +static inline size_t align_floor(size_t align, size_t size) { + return size & ~(align - 1); +} + /** * Round up to a multiple of an alignment. */ static inline size_t align_ceil(size_t align, size_t size) { - return (size + align - 1) & ~(align - 1); + return align_floor(align, size + align - 1); } /** @@ -171,8 +178,8 @@ static inline size_t flex_sizeof_impl(size_t align, size_t min, size_t offset, s size_t mask = align - 1; ret += mask; overflow |= ret < mask; - ret &= ~mask; ret |= -overflow; + ret &= ~mask; // Make sure flex_sizeof(type, member, 0) >= sizeof(type), even if the // type has more padding than necessary for alignment diff --git a/tests/bfstd.c b/tests/bfstd.c index 1917a53..a986a23 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -41,7 +41,8 @@ int main(void) { }; assert(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); assert(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); - assert(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) == SIZE_MAX); + assert(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) + == align_floor(alignof(struct flexible), SIZE_MAX)); assert(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); // From man 3p basename -- cgit v1.2.3 From 7788deca5385c5856f12a93db84782880645c372 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 12:19:54 -0400 Subject: build: Fix test utility compilation --- Makefile | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 40f6b17..361f230 100644 --- a/Makefile +++ b/Makefile @@ -247,10 +247,12 @@ UNIT_CHECKS := $(UNITS:%=check-%) # Testing utilities TEST_UTILS := $(BIN)/tests/mksock $(BIN)/tests/xtouch -tests: $(UNIT_TESTS) $(TEST_UTILS) +TESTS := $(UNIT_TESTS) $(TEST_UTILS) + +tests: $(TESTS) .PHONY: tests -$(UNIT_TESTS): $(BIN)/tests/%: $(OBJ)/tests/%.o $(LIBBFS) +$(TESTS): $(BIN)/tests/%: $(OBJ)/tests/%.o $(LIBBFS) # The different search strategies that we test STRATEGIES := bfs dfs ids eds -- cgit v1.2.3 From ca8274a20d4c50706f2d40bfc58cba09a9a5c713 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 12:57:17 -0400 Subject: ci: Fix 32-bit Linux dependencies --- .github/workflows/ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9db363d..ae65157 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,6 +17,7 @@ jobs: sudo apt-get update -y sudo apt-get install -y \ gcc-multilib \ + libgcc-s1:i386 \ acl \ libacl1-dev \ libacl1:i386 \ -- cgit v1.2.3 From a1231458e25a6b1d6e6a600d59ca1b252d65db1d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 May 2023 16:52:20 -0400 Subject: Use SYS_* instead of __NR_* --- src/dir.c | 2 +- src/stat.c | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dir.c b/src/dir.c index 30db5df..6739f10 100644 --- a/src/dir.c +++ b/src/dir.c @@ -32,7 +32,7 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { #endif #if __linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30) - ssize_t ret = syscall(__NR_getdents64, fd, buf, size); + ssize_t ret = syscall(SYS_getdents64, fd, buf, size); #elif __linux__ ssize_t ret = getdents64(fd, buf, size); #else diff --git a/src/stat.c b/src/stat.c index 7973d71..50ea345 100644 --- a/src/stat.c +++ b/src/stat.c @@ -19,7 +19,7 @@ # include #endif -#if BFS_LIBC_STATX || defined(__NR_statx) +#if BFS_LIBC_STATX || defined(SYS_statx) # define BFS_STATX true #endif @@ -141,7 +141,7 @@ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int #if BFS_LIBC_STATX return statx(at_fd, at_path, at_flags, mask, buf); #else - return syscall(__NR_statx, at_fd, at_path, at_flags, mask, buf); + return syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf); #endif } -- cgit v1.2.3 From 4505819c576fc93f21d73e1bf85550250cdb72a2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 11:25:14 -0400 Subject: bit: Rename int.h to bit.h --- Makefile | 2 +- src/bit.h | 355 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/int.h | 355 ------------------------------------------------------------ src/trie.c | 2 +- tests/bit.c | 121 +++++++++++++++++++++ tests/int.c | 121 --------------------- 6 files changed, 478 insertions(+), 478 deletions(-) create mode 100644 src/bit.h delete mode 100644 src/int.h create mode 100644 tests/bit.c delete mode 100644 tests/int.c diff --git a/Makefile b/Makefile index 361f230..1d3bd20 100644 --- a/Makefile +++ b/Makefile @@ -240,7 +240,7 @@ LIBBFS := \ $(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) # Standalone unit tests -UNITS := bfstd int trie xtimegm +UNITS := bfstd bit trie xtimegm UNIT_TESTS := $(UNITS:%=$(BIN)/tests/%) UNIT_CHECKS := $(UNITS:%=check-%) diff --git a/src/bit.h b/src/bit.h new file mode 100644 index 0000000..efd7d27 --- /dev/null +++ b/src/bit.h @@ -0,0 +1,355 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Bits & bytes. + */ + +#ifndef BFS_BIT_H +#define BFS_BIT_H + +#include "config.h" +#include +#include + +#if __STDC_VERSION__ >= 202311L +# include +#endif + +// C23 polyfill: _WIDTH macros + +// The U*_MAX macros are of the form 2**n - 1, and we want to extract the n. +// One way would be *_WIDTH = popcount(*_MAX). Alternatively, we can use +// Hallvard B. Furuseth's technique from [1], which is shorter. +// +// [1]: https://groups.google.com/g/comp.lang.c/c/NfedEFBFJ0k + +// Let mask be of the form 2**m - 1, e.g. 0b111, and let n range over +// [0b0, 0b1, 0b11, 0b111, 0b1111, ...]. Then we have +// +// n % 0b111 +// == [0b0, 0b1, 0b11, 0b0, 0b1, 0b11, ...] +// n / (n % 0b111 + 1) +// == [0b0 (x3), 0b111 (x3), 0b111111 (x3), ...] +// n / (n % 0b111 + 1) / 0b111 +// == [0b0 (x3), 0b1 (x3), 0b1001 (x3), 0b1001001 (x3), ...] +// n / (n % 0b111 + 1) / 0b111 % 0b111 +// == [0 (x3), 1 (x3), 2 (x3), ...] +// == UMAX_CHUNK(n, 0b111) +#define UMAX_CHUNK(n, mask) (n / (n % mask + 1) / mask % mask) + +// 8 * UMAX_CHUNK(n, 255) gives [0 (x8), 8 (x8), 16 (x8), ...]. To that we add +// [0, 1, 2, ..., 6, 7, 0, 1, ...], which we get from a linear interpolation on +// n % 255: +// +// n % 255 +// == [0, 1, 3, 7, 15, 31, 63, 127, 0, ...] +// 86 / (n % 255 + 12) +// == [7, 6, 5, 4, 3, 2, 1, 0, 7, ...] +#define UMAX_INTERP(n) (7 - 86 / (n % 255 + 12)) + +#define UMAX_WIDTH(n) (8 * UMAX_CHUNK(n, 255) + UMAX_INTERP(n)) + +#ifndef CHAR_WIDTH +# define CHAR_WIDTH CHAR_BIT +#endif +#ifndef UCHAR_WIDTH +# define UCHAR_WIDTH CHAR_WIDTH +#endif +#ifndef SCHAR_WIDTH +# define SCHAR_WIDTH CHAR_WIDTH +#endif +#ifndef USHRT_WIDTH +# define USHRT_WIDTH UMAX_WIDTH(USHRT_MAX) +#endif +#ifndef SHRT_WIDTH +# define SHRT_WIDTH USHRT_WIDTH +#endif +#ifndef UINT_WIDTH +# define UINT_WIDTH UMAX_WIDTH(UINT_MAX) +#endif +#ifndef INT_WIDTH +# define INT_WIDTH UINT_WIDTH +#endif +#ifndef ULONG_WIDTH +# define ULONG_WIDTH UMAX_WIDTH(ULONG_MAX) +#endif +#ifndef LONG_WIDTH +# define LONG_WIDTH ULONG_WIDTH +#endif +#ifndef ULLONG_WIDTH +# define ULLONG_WIDTH UMAX_WIDTH(ULLONG_MAX) +#endif +#ifndef LLONG_WIDTH +# define LLONG_WIDTH ULLONG_WIDTH +#endif +#ifndef SIZE_WIDTH +# define SIZE_WIDTH UMAX_WIDTH(SIZE_MAX) +#endif +#ifndef PTRDIFF_WIDTH +# define PTRDIFF_WIDTH (UMAX_WIDTH(PTRDIFF_MAX) + 1) +#endif +#ifndef UINTPTR_WIDTH +# define UINTPTR_WIDTH UMAX_WIDTH(UINTPTR_MAX) +#endif +#ifndef INTPTR_WIDTH +# define INTPTR_WIDTH UINTPTR_WIDTH +#endif +#ifndef UINTMAX_WIDTH +# define UINTMAX_WIDTH UMAX_WIDTH(UINTMAX_MAX) +#endif +#ifndef INTMAX_WIDTH +# define INTMAX_WIDTH UINTMAX_WIDTH +#endif + +// C23 polyfill: byte order + +#ifdef __STDC_ENDIAN_LITTLE__ +# define ENDIAN_LITTLE __STDC_ENDIAN_LITTLE__ +#elif defined(__ORDER_LITTLE_ENDIAN__) +# define ENDIAN_LITTLE __ORDER_LITTLE_ENDIAN__ +#else +# define ENDIAN_LITTLE 1234 +#endif + +#ifdef __STDC_ENDIAN_BIG__ +# define ENDIAN_BIG __STDC_ENDIAN_BIG__ +#elif defined(__ORDER_BIG_ENDIAN__) +# define ENDIAN_BIG __ORDER_BIG_ENDIAN__ +#else +# define ENDIAN_BIG 4321 +#endif + +#ifdef __STDC_ENDIAN_NATIVE__ +# define ENDIAN_NATIVE __STDC_ENDIAN_NATIVE__ +#elif defined(__ORDER_NATIVE_ENDIAN__) +# define ENDIAN_NATIVE __ORDER_NATIVE_ENDIAN__ +#else +# define ENDIAN_NATIVE 0 +#endif + +#if __STDC_VERSION__ >= 202311L +# define bswap16 stdc_memreverse8u16 +# define bswap32 stdc_memreverse8u32 +# define bswap64 stdc_memreverse8u64 +#elif __GNUC__ +# define bswap16 __builtin_bswap16 +# define bswap32 __builtin_bswap32 +# define bswap64 __builtin_bswap64 +#else + +static inline uint16_t bswap16(uint16_t n) { + return (n << 8) | (n >> 8); +} + +static inline uint32_t bswap32(uint32_t n) { + return ((uint32_t)bswap16(n) << 16) | bswap16(n >> 16); +} + +static inline uint64_t bswap64(uint64_t n) { + return ((uint64_t)bswap32(n) << 32) | bswap32(n >> 32); +} + +#endif + +static inline uint8_t bswap8(uint8_t n) { + return n; +} + +/** + * Reverse the byte order of an integer. + */ +#define bswap(n) \ + _Generic((n), \ + uint8_t: bswap8, \ + uint16_t: bswap16, \ + uint32_t: bswap32, \ + uint64_t: bswap64)(n) + +// Define an overload for each unsigned type +#define UINT_OVERLOADS(macro) \ + macro(unsigned char, _uc, UCHAR_WIDTH) \ + macro(unsigned short, _us, USHRT_WIDTH) \ + macro(unsigned int, _ui, UINT_WIDTH) \ + macro(unsigned long, _ul, ULONG_WIDTH) \ + macro(unsigned long long, _ull, ULLONG_WIDTH) + +// 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__ >= 202311L +# 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 +# define trailing_ones stdc_trailing_ones +# define first_leading_zero stdc_first_leading_zero +# define first_leading_one stdc_first_leading_one +# define first_trailing_zero stdc_first_trailing_zero +# define first_trailing_one stdc_first_trailing_one +# define has_single_bit stdc_has_single_bit +# define bit_width stdc_bit_width +# define bit_ceil stdc_bit_ceil +# define bit_floor stdc_bit_floor +#else + +#if __GNUC__ + +// GCC provides builtins for unsigned {int,long,long long}, so promote char/short +#define UINT_BUILTIN_uc(name) __builtin_##name +#define UINT_BUILTIN_us(name) __builtin_##name +#define UINT_BUILTIN_ui(name) __builtin_##name +#define UINT_BUILTIN_ul(name) __builtin_##name##l +#define UINT_BUILTIN_ull(name) __builtin_##name##ll +#define UINT_BUILTIN(name, suffix) UINT_BUILTIN##suffix(name) + +#define BUILTIN_WIDTH_uc UINT_WIDTH +#define BUILTIN_WIDTH_us UINT_WIDTH +#define BUILTIN_WIDTH_ui UINT_WIDTH +#define BUILTIN_WIDTH_ul ULONG_WIDTH +#define BUILTIN_WIDTH_ull ULLONG_WIDTH +#define BUILTIN_WIDTH(suffix) BUILTIN_WIDTH##suffix + +#define COUNT_ONES(type, suffix, width) \ + static inline 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) { \ + 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) { \ + return n ? UINT_BUILTIN(ctz, suffix)(n) : width; \ + } + +#define FIRST_TRAILING_ONE(type, suffix, width) \ + static inline 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) { \ + int ret; \ + for (ret = 0; n; ++ret) { \ + n &= n - 1; \ + } \ + return ret; \ + } + +#define LEADING_ZEROS(type, suffix, width) \ + static inline int leading_zeros##suffix(type n) { \ + type bit = (type)1 << (width - 1); \ + int ret; \ + for (ret = 0; bit && !(n & bit); ++ret, bit >>= 1); \ + return ret; \ + } + +#define TRAILING_ZEROS(type, suffix, width) \ + static inline int trailing_zeros##suffix(type n) { \ + type bit = 1; \ + int ret; \ + for (ret = 0; bit && !(n & bit); ++ret, bit <<= 1); \ + return ret; \ + } + +#define FIRST_TRAILING_ONE(type, suffix, width) \ + static inline int first_trailing_one##suffix(type n) { \ + return n ? trailing_zeros##suffix(n) + 1 : 0; \ + } + +#endif // !__GNUC__ + +UINT_OVERLOADS(COUNT_ONES) +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); \ + } + +#define HAS_SINGLE_BIT(type, suffix, width) \ + static inline bool has_single_bit##suffix(type n) { \ + return n && !(n & (n - 1)); \ + } + +UINT_OVERLOADS(ROTATE_LEFT) +UINT_OVERLOADS(ROTATE_RIGHT) +UINT_OVERLOADS(FIRST_LEADING_ONE) +UINT_OVERLOADS(HAS_SINGLE_BIT) + +#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)) + +#define trailing_zeros(n) UINT_SELECT(n, trailing_zeros)(n) +#define trailing_ones(n) UINT_SELECT(n, trailing_zeros)(~(n)) + +#define first_leading_one(n) UINT_SELECT(n, first_leading_one)(n) +#define first_leading_zero(n) UINT_SELECT(n, first_leading_one)(~(n)) + +#define first_trailing_one(n) UINT_SELECT(n, first_trailing_one)(n) +#define first_trailing_zero(n) UINT_SELECT(n, first_trailing_one)(~(n)) + +#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_CEIL(type, suffix, width) \ + static inline type bit_ceil##suffix(type n) { \ + return (type)1 << first_leading_one##suffix(n - !!n); \ + } + +UINT_OVERLOADS(BIT_FLOOR) +UINT_OVERLOADS(BIT_CEIL) + +#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) + +#endif // __STDC_VERSION__ < 202311L + +#endif // BFS_BIT_H diff --git a/src/int.h b/src/int.h deleted file mode 100644 index 885933d..0000000 --- a/src/int.h +++ /dev/null @@ -1,355 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -/** - * Bits & bytes. - */ - -#ifndef BFS_INT_H -#define BFS_INT_H - -#include "config.h" -#include -#include - -#if __STDC_VERSION__ >= 202311L -# include -#endif - -// C23 polyfill: _WIDTH macros - -// The U*_MAX macros are of the form 2**n - 1, and we want to extract the n. -// One way would be *_WIDTH = popcount(*_MAX). Alternatively, we can use -// Hallvard B. Furuseth's technique from [1], which is shorter. -// -// [1]: https://groups.google.com/g/comp.lang.c/c/NfedEFBFJ0k - -// Let mask be of the form 2**m - 1, e.g. 0b111, and let n range over -// [0b0, 0b1, 0b11, 0b111, 0b1111, ...]. Then we have -// -// n % 0b111 -// == [0b0, 0b1, 0b11, 0b0, 0b1, 0b11, ...] -// n / (n % 0b111 + 1) -// == [0b0 (x3), 0b111 (x3), 0b111111 (x3), ...] -// n / (n % 0b111 + 1) / 0b111 -// == [0b0 (x3), 0b1 (x3), 0b1001 (x3), 0b1001001 (x3), ...] -// n / (n % 0b111 + 1) / 0b111 % 0b111 -// == [0 (x3), 1 (x3), 2 (x3), ...] -// == UMAX_CHUNK(n, 0b111) -#define UMAX_CHUNK(n, mask) (n / (n % mask + 1) / mask % mask) - -// 8 * UMAX_CHUNK(n, 255) gives [0 (x8), 8 (x8), 16 (x8), ...]. To that we add -// [0, 1, 2, ..., 6, 7, 0, 1, ...], which we get from a linear interpolation on -// n % 255: -// -// n % 255 -// == [0, 1, 3, 7, 15, 31, 63, 127, 0, ...] -// 86 / (n % 255 + 12) -// == [7, 6, 5, 4, 3, 2, 1, 0, 7, ...] -#define UMAX_INTERP(n) (7 - 86 / (n % 255 + 12)) - -#define UMAX_WIDTH(n) (8 * UMAX_CHUNK(n, 255) + UMAX_INTERP(n)) - -#ifndef CHAR_WIDTH -# define CHAR_WIDTH CHAR_BIT -#endif -#ifndef UCHAR_WIDTH -# define UCHAR_WIDTH CHAR_WIDTH -#endif -#ifndef SCHAR_WIDTH -# define SCHAR_WIDTH CHAR_WIDTH -#endif -#ifndef USHRT_WIDTH -# define USHRT_WIDTH UMAX_WIDTH(USHRT_MAX) -#endif -#ifndef SHRT_WIDTH -# define SHRT_WIDTH USHRT_WIDTH -#endif -#ifndef UINT_WIDTH -# define UINT_WIDTH UMAX_WIDTH(UINT_MAX) -#endif -#ifndef INT_WIDTH -# define INT_WIDTH UINT_WIDTH -#endif -#ifndef ULONG_WIDTH -# define ULONG_WIDTH UMAX_WIDTH(ULONG_MAX) -#endif -#ifndef LONG_WIDTH -# define LONG_WIDTH ULONG_WIDTH -#endif -#ifndef ULLONG_WIDTH -# define ULLONG_WIDTH UMAX_WIDTH(ULLONG_MAX) -#endif -#ifndef LLONG_WIDTH -# define LLONG_WIDTH ULLONG_WIDTH -#endif -#ifndef SIZE_WIDTH -# define SIZE_WIDTH UMAX_WIDTH(SIZE_MAX) -#endif -#ifndef PTRDIFF_WIDTH -# define PTRDIFF_WIDTH (UMAX_WIDTH(PTRDIFF_MAX) + 1) -#endif -#ifndef UINTPTR_WIDTH -# define UINTPTR_WIDTH UMAX_WIDTH(UINTPTR_MAX) -#endif -#ifndef INTPTR_WIDTH -# define INTPTR_WIDTH UINTPTR_WIDTH -#endif -#ifndef UINTMAX_WIDTH -# define UINTMAX_WIDTH UMAX_WIDTH(UINTMAX_MAX) -#endif -#ifndef INTMAX_WIDTH -# define INTMAX_WIDTH UINTMAX_WIDTH -#endif - -// C23 polyfill: byte order - -#ifdef __STDC_ENDIAN_LITTLE__ -# define ENDIAN_LITTLE __STDC_ENDIAN_LITTLE__ -#elif defined(__ORDER_LITTLE_ENDIAN__) -# define ENDIAN_LITTLE __ORDER_LITTLE_ENDIAN__ -#else -# define ENDIAN_LITTLE 1234 -#endif - -#ifdef __STDC_ENDIAN_BIG__ -# define ENDIAN_BIG __STDC_ENDIAN_BIG__ -#elif defined(__ORDER_BIG_ENDIAN__) -# define ENDIAN_BIG __ORDER_BIG_ENDIAN__ -#else -# define ENDIAN_BIG 4321 -#endif - -#ifdef __STDC_ENDIAN_NATIVE__ -# define ENDIAN_NATIVE __STDC_ENDIAN_NATIVE__ -#elif defined(__ORDER_NATIVE_ENDIAN__) -# define ENDIAN_NATIVE __ORDER_NATIVE_ENDIAN__ -#else -# define ENDIAN_NATIVE 0 -#endif - -#if __STDC_VERSION__ >= 202311L -# define bswap16 stdc_memreverse8u16 -# define bswap32 stdc_memreverse8u32 -# define bswap64 stdc_memreverse8u64 -#elif __GNUC__ -# define bswap16 __builtin_bswap16 -# define bswap32 __builtin_bswap32 -# define bswap64 __builtin_bswap64 -#else - -static inline uint16_t bswap16(uint16_t n) { - return (n << 8) | (n >> 8); -} - -static inline uint32_t bswap32(uint32_t n) { - return ((uint32_t)bswap16(n) << 16) | bswap16(n >> 16); -} - -static inline uint64_t bswap64(uint64_t n) { - return ((uint64_t)bswap32(n) << 32) | bswap32(n >> 32); -} - -#endif - -static inline uint8_t bswap8(uint8_t n) { - return n; -} - -/** - * Reverse the byte order of an integer. - */ -#define bswap(n) \ - _Generic((n), \ - uint8_t: bswap8, \ - uint16_t: bswap16, \ - uint32_t: bswap32, \ - uint64_t: bswap64)(n) - -// Define an overload for each unsigned type -#define UINT_OVERLOADS(macro) \ - macro(unsigned char, _uc, UCHAR_WIDTH) \ - macro(unsigned short, _us, USHRT_WIDTH) \ - macro(unsigned int, _ui, UINT_WIDTH) \ - macro(unsigned long, _ul, ULONG_WIDTH) \ - macro(unsigned long long, _ull, ULLONG_WIDTH) - -// 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__ >= 202311L -# 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 -# define trailing_ones stdc_trailing_ones -# define first_leading_zero stdc_first_leading_zero -# define first_leading_one stdc_first_leading_one -# define first_trailing_zero stdc_first_trailing_zero -# define first_trailing_one stdc_first_trailing_one -# define has_single_bit stdc_has_single_bit -# define bit_width stdc_bit_width -# define bit_ceil stdc_bit_ceil -# define bit_floor stdc_bit_floor -#else - -#if __GNUC__ - -// GCC provides builtins for unsigned {int,long,long long}, so promote char/short -#define UINT_BUILTIN_uc(name) __builtin_##name -#define UINT_BUILTIN_us(name) __builtin_##name -#define UINT_BUILTIN_ui(name) __builtin_##name -#define UINT_BUILTIN_ul(name) __builtin_##name##l -#define UINT_BUILTIN_ull(name) __builtin_##name##ll -#define UINT_BUILTIN(name, suffix) UINT_BUILTIN##suffix(name) - -#define BUILTIN_WIDTH_uc UINT_WIDTH -#define BUILTIN_WIDTH_us UINT_WIDTH -#define BUILTIN_WIDTH_ui UINT_WIDTH -#define BUILTIN_WIDTH_ul ULONG_WIDTH -#define BUILTIN_WIDTH_ull ULLONG_WIDTH -#define BUILTIN_WIDTH(suffix) BUILTIN_WIDTH##suffix - -#define COUNT_ONES(type, suffix, width) \ - static inline 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) { \ - 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) { \ - return n ? UINT_BUILTIN(ctz, suffix)(n) : width; \ - } - -#define FIRST_TRAILING_ONE(type, suffix, width) \ - static inline 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) { \ - int ret; \ - for (ret = 0; n; ++ret) { \ - n &= n - 1; \ - } \ - return ret; \ - } - -#define LEADING_ZEROS(type, suffix, width) \ - static inline int leading_zeros##suffix(type n) { \ - type bit = (type)1 << (width - 1); \ - int ret; \ - for (ret = 0; bit && !(n & bit); ++ret, bit >>= 1); \ - return ret; \ - } - -#define TRAILING_ZEROS(type, suffix, width) \ - static inline int trailing_zeros##suffix(type n) { \ - type bit = 1; \ - int ret; \ - for (ret = 0; bit && !(n & bit); ++ret, bit <<= 1); \ - return ret; \ - } - -#define FIRST_TRAILING_ONE(type, suffix, width) \ - static inline int first_trailing_one##suffix(type n) { \ - return n ? trailing_zeros##suffix(n) + 1 : 0; \ - } - -#endif // !__GNUC__ - -UINT_OVERLOADS(COUNT_ONES) -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); \ - } - -#define HAS_SINGLE_BIT(type, suffix, width) \ - static inline bool has_single_bit##suffix(type n) { \ - return n && !(n & (n - 1)); \ - } - -UINT_OVERLOADS(ROTATE_LEFT) -UINT_OVERLOADS(ROTATE_RIGHT) -UINT_OVERLOADS(FIRST_LEADING_ONE) -UINT_OVERLOADS(HAS_SINGLE_BIT) - -#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)) - -#define trailing_zeros(n) UINT_SELECT(n, trailing_zeros)(n) -#define trailing_ones(n) UINT_SELECT(n, trailing_zeros)(~(n)) - -#define first_leading_one(n) UINT_SELECT(n, first_leading_one)(n) -#define first_leading_zero(n) UINT_SELECT(n, first_leading_one)(~(n)) - -#define first_trailing_one(n) UINT_SELECT(n, first_trailing_one)(n) -#define first_trailing_zero(n) UINT_SELECT(n, first_trailing_one)(~(n)) - -#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_CEIL(type, suffix, width) \ - static inline type bit_ceil##suffix(type n) { \ - return (type)1 << first_leading_one##suffix(n - !!n); \ - } - -UINT_OVERLOADS(BIT_FLOOR) -UINT_OVERLOADS(BIT_CEIL) - -#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) - -#endif // __STDC_VERSION__ < 202311L - -#endif // BFS_INT_H diff --git a/src/trie.c b/src/trie.c index 2226890..7c2f65d 100644 --- a/src/trie.c +++ b/src/trie.c @@ -82,9 +82,9 @@ */ #include "trie.h" +#include "bit.h" #include "config.h" #include "diag.h" -#include "int.h" #include "list.h" #include #include diff --git a/tests/bit.c b/tests/bit.c new file mode 100644 index 0000000..7a7b0f3 --- /dev/null +++ b/tests/bit.c @@ -0,0 +1,121 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#undef NDEBUG + +#include "../src/bit.h" +#include "../src/diag.h" +#include +#include +#include +#include + +bfs_static_assert(UMAX_WIDTH(0x1) == 1); +bfs_static_assert(UMAX_WIDTH(0x3) == 2); +bfs_static_assert(UMAX_WIDTH(0x7) == 3); +bfs_static_assert(UMAX_WIDTH(0xF) == 4); +bfs_static_assert(UMAX_WIDTH(0xFF) == 8); +bfs_static_assert(UMAX_WIDTH(0xFFF) == 12); +bfs_static_assert(UMAX_WIDTH(0xFFFF) == 16); + +#define UWIDTH_MAX(n) (2 * ((UINTMAX_C(1) << ((n) - 1)) - 1) + 1) +#define IWIDTH_MAX(n) UWIDTH_MAX((n) - 1) +#define IWIDTH_MIN(n) (-(intmax_t)IWIDTH_MAX(n) - 1) + +bfs_static_assert(UCHAR_MAX == UWIDTH_MAX(UCHAR_WIDTH)); +bfs_static_assert(SCHAR_MIN == IWIDTH_MIN(SCHAR_WIDTH)); +bfs_static_assert(SCHAR_MAX == IWIDTH_MAX(SCHAR_WIDTH)); + +bfs_static_assert(USHRT_MAX == UWIDTH_MAX(USHRT_WIDTH)); +bfs_static_assert(SHRT_MIN == IWIDTH_MIN(SHRT_WIDTH)); +bfs_static_assert(SHRT_MAX == IWIDTH_MAX(SHRT_WIDTH)); + +bfs_static_assert(UINT_MAX == UWIDTH_MAX(UINT_WIDTH)); +bfs_static_assert(INT_MIN == IWIDTH_MIN(INT_WIDTH)); +bfs_static_assert(INT_MAX == IWIDTH_MAX(INT_WIDTH)); + +bfs_static_assert(ULONG_MAX == UWIDTH_MAX(ULONG_WIDTH)); +bfs_static_assert(LONG_MIN == IWIDTH_MIN(LONG_WIDTH)); +bfs_static_assert(LONG_MAX == IWIDTH_MAX(LONG_WIDTH)); + +bfs_static_assert(ULLONG_MAX == UWIDTH_MAX(ULLONG_WIDTH)); +bfs_static_assert(LLONG_MIN == IWIDTH_MIN(LLONG_WIDTH)); +bfs_static_assert(LLONG_MAX == IWIDTH_MAX(LLONG_WIDTH)); + +bfs_static_assert(SIZE_MAX == UWIDTH_MAX(SIZE_WIDTH)); +bfs_static_assert(PTRDIFF_MIN == IWIDTH_MIN(PTRDIFF_WIDTH)); +bfs_static_assert(PTRDIFF_MAX == IWIDTH_MAX(PTRDIFF_WIDTH)); + +bfs_static_assert(UINTPTR_MAX == UWIDTH_MAX(UINTPTR_WIDTH)); +bfs_static_assert(INTPTR_MIN == IWIDTH_MIN(INTPTR_WIDTH)); +bfs_static_assert(INTPTR_MAX == IWIDTH_MAX(INTPTR_WIDTH)); + +bfs_static_assert(UINTMAX_MAX == UWIDTH_MAX(UINTMAX_WIDTH)); +bfs_static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH)); +bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); + +int main(void) { + assert(bswap((uint8_t)0x12) == 0x12); + assert(bswap((uint16_t)0x1234) == 0x3412); + assert(bswap((uint32_t)0x12345678) == 0x78563412); + assert(bswap((uint64_t)0x1234567812345678) == 0x7856341278563412); + + assert(count_ones(0x0) == 0); + assert(count_ones(0x1) == 1); + assert(count_ones(0x2) == 1); + assert(count_ones(0x3) == 2); + assert(count_ones(0x137F) == 10); + + assert(count_zeros(0) == INT_WIDTH); + assert(count_zeros(0L) == LONG_WIDTH); + assert(count_zeros(0LL) == LLONG_WIDTH); + assert(count_zeros((uint8_t)0) == 8); + assert(count_zeros((uint16_t)0) == 16); + assert(count_zeros((uint32_t)0) == 32); + assert(count_zeros((uint64_t)0) == 64); + + assert(rotate_left((uint8_t)0xA1, 4) == 0x1A); + assert(rotate_left((uint16_t)0x1234, 12) == 0x4123); + assert(rotate_left((uint32_t)0x12345678, 20) == 0x67812345); + assert(rotate_left((uint32_t)0x12345678, 0) == 0x12345678); + + assert(rotate_right((uint8_t)0xA1, 4) == 0x1A); + assert(rotate_right((uint16_t)0x1234, 12) == 0x2341); + assert(rotate_right((uint32_t)0x12345678, 20) == 0x45678123); + assert(rotate_right((uint32_t)0x12345678, 0) == 0x12345678); + + for (int i = 0; i < 16; ++i) { + uint16_t n = (uint16_t)1 << i; + for (int j = i; j < 16; ++j) { + uint16_t m = (uint16_t)1 << j; + uint16_t nm = n | m; + assert(count_ones(nm) == 1 + (n != m)); + assert(count_zeros(nm) == 15 - (n != m)); + assert(leading_zeros(nm) == 15 - j); + assert(trailing_zeros(nm) == i); + assert(first_leading_one(nm) == j + 1); + assert(first_trailing_one(nm) == i + 1); + assert(bit_width(nm) == j + 1); + assert(bit_floor(nm) == m); + if (n == m) { + assert(bit_ceil(nm) == m); + assert(has_single_bit(nm)); + } else { + if (j < 15) { + assert(bit_ceil(nm) == (m << 1)); + } + assert(!has_single_bit(nm)); + } + } + } + + assert(leading_zeros((uint16_t)0) == 16); + assert(trailing_zeros((uint16_t)0) == 16); + assert(first_leading_one(0) == 0); + assert(first_trailing_one(0) == 0); + assert(bit_width(0) == 0); + assert(bit_floor(0) == 0); + assert(bit_ceil(0) == 1); + + return EXIT_SUCCESS; +} diff --git a/tests/int.c b/tests/int.c deleted file mode 100644 index e59efde..0000000 --- a/tests/int.c +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#undef NDEBUG - -#include "../src/int.h" -#include "../src/diag.h" -#include -#include -#include -#include - -bfs_static_assert(UMAX_WIDTH(0x1) == 1); -bfs_static_assert(UMAX_WIDTH(0x3) == 2); -bfs_static_assert(UMAX_WIDTH(0x7) == 3); -bfs_static_assert(UMAX_WIDTH(0xF) == 4); -bfs_static_assert(UMAX_WIDTH(0xFF) == 8); -bfs_static_assert(UMAX_WIDTH(0xFFF) == 12); -bfs_static_assert(UMAX_WIDTH(0xFFFF) == 16); - -#define UWIDTH_MAX(n) (2 * ((UINTMAX_C(1) << ((n) - 1)) - 1) + 1) -#define IWIDTH_MAX(n) UWIDTH_MAX((n) - 1) -#define IWIDTH_MIN(n) (-(intmax_t)IWIDTH_MAX(n) - 1) - -bfs_static_assert(UCHAR_MAX == UWIDTH_MAX(UCHAR_WIDTH)); -bfs_static_assert(SCHAR_MIN == IWIDTH_MIN(SCHAR_WIDTH)); -bfs_static_assert(SCHAR_MAX == IWIDTH_MAX(SCHAR_WIDTH)); - -bfs_static_assert(USHRT_MAX == UWIDTH_MAX(USHRT_WIDTH)); -bfs_static_assert(SHRT_MIN == IWIDTH_MIN(SHRT_WIDTH)); -bfs_static_assert(SHRT_MAX == IWIDTH_MAX(SHRT_WIDTH)); - -bfs_static_assert(UINT_MAX == UWIDTH_MAX(UINT_WIDTH)); -bfs_static_assert(INT_MIN == IWIDTH_MIN(INT_WIDTH)); -bfs_static_assert(INT_MAX == IWIDTH_MAX(INT_WIDTH)); - -bfs_static_assert(ULONG_MAX == UWIDTH_MAX(ULONG_WIDTH)); -bfs_static_assert(LONG_MIN == IWIDTH_MIN(LONG_WIDTH)); -bfs_static_assert(LONG_MAX == IWIDTH_MAX(LONG_WIDTH)); - -bfs_static_assert(ULLONG_MAX == UWIDTH_MAX(ULLONG_WIDTH)); -bfs_static_assert(LLONG_MIN == IWIDTH_MIN(LLONG_WIDTH)); -bfs_static_assert(LLONG_MAX == IWIDTH_MAX(LLONG_WIDTH)); - -bfs_static_assert(SIZE_MAX == UWIDTH_MAX(SIZE_WIDTH)); -bfs_static_assert(PTRDIFF_MIN == IWIDTH_MIN(PTRDIFF_WIDTH)); -bfs_static_assert(PTRDIFF_MAX == IWIDTH_MAX(PTRDIFF_WIDTH)); - -bfs_static_assert(UINTPTR_MAX == UWIDTH_MAX(UINTPTR_WIDTH)); -bfs_static_assert(INTPTR_MIN == IWIDTH_MIN(INTPTR_WIDTH)); -bfs_static_assert(INTPTR_MAX == IWIDTH_MAX(INTPTR_WIDTH)); - -bfs_static_assert(UINTMAX_MAX == UWIDTH_MAX(UINTMAX_WIDTH)); -bfs_static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH)); -bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); - -int main(void) { - assert(bswap((uint8_t)0x12) == 0x12); - assert(bswap((uint16_t)0x1234) == 0x3412); - assert(bswap((uint32_t)0x12345678) == 0x78563412); - assert(bswap((uint64_t)0x1234567812345678) == 0x7856341278563412); - - assert(count_ones(0x0) == 0); - assert(count_ones(0x1) == 1); - assert(count_ones(0x2) == 1); - assert(count_ones(0x3) == 2); - assert(count_ones(0x137F) == 10); - - assert(count_zeros(0) == INT_WIDTH); - assert(count_zeros(0L) == LONG_WIDTH); - assert(count_zeros(0LL) == LLONG_WIDTH); - assert(count_zeros((uint8_t)0) == 8); - assert(count_zeros((uint16_t)0) == 16); - assert(count_zeros((uint32_t)0) == 32); - assert(count_zeros((uint64_t)0) == 64); - - assert(rotate_left((uint8_t)0xA1, 4) == 0x1A); - assert(rotate_left((uint16_t)0x1234, 12) == 0x4123); - assert(rotate_left((uint32_t)0x12345678, 20) == 0x67812345); - assert(rotate_left((uint32_t)0x12345678, 0) == 0x12345678); - - assert(rotate_right((uint8_t)0xA1, 4) == 0x1A); - assert(rotate_right((uint16_t)0x1234, 12) == 0x2341); - assert(rotate_right((uint32_t)0x12345678, 20) == 0x45678123); - assert(rotate_right((uint32_t)0x12345678, 0) == 0x12345678); - - for (int i = 0; i < 16; ++i) { - uint16_t n = (uint16_t)1 << i; - for (int j = i; j < 16; ++j) { - uint16_t m = (uint16_t)1 << j; - uint16_t nm = n | m; - assert(count_ones(nm) == 1 + (n != m)); - assert(count_zeros(nm) == 15 - (n != m)); - assert(leading_zeros(nm) == 15 - j); - assert(trailing_zeros(nm) == i); - assert(first_leading_one(nm) == j + 1); - assert(first_trailing_one(nm) == i + 1); - assert(bit_width(nm) == j + 1); - assert(bit_floor(nm) == m); - if (n == m) { - assert(bit_ceil(nm) == m); - assert(has_single_bit(nm)); - } else { - if (j < 15) { - assert(bit_ceil(nm) == (m << 1)); - } - assert(!has_single_bit(nm)); - } - } - } - - assert(leading_zeros((uint16_t)0) == 16); - assert(trailing_zeros((uint16_t)0) == 16); - assert(first_leading_one(0) == 0); - assert(first_trailing_one(0) == 0); - assert(bit_width(0) == 0); - assert(bit_floor(0) == 0); - assert(bit_ceil(0) == 1); - - return EXIT_SUCCESS; -} -- cgit v1.2.3 From a2fd2dab3c1eb1f64554a4daee96fb92a3c831dd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 11:28:19 -0400 Subject: trie: Use SIZE_WIDTH --- src/trie.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/trie.c b/src/trie.c index 7c2f65d..a2921de 100644 --- a/src/trie.c +++ b/src/trie.c @@ -92,7 +92,7 @@ #include #include -bfs_static_assert(CHAR_BIT == 8); +bfs_static_assert(CHAR_WIDTH == 8); #if BFS_TARGET_CLONES && (__i386__ || __x86_64__) # define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) @@ -101,11 +101,11 @@ bfs_static_assert(CHAR_BIT == 8); #endif /** Number of bits for the sparse array bitmap, aka the range of a nibble. */ -#define BITMAP_BITS 16 +#define BITMAP_WIDTH 16 /** The number of remaining bits in a word, to hold the offset. */ -#define OFFSET_BITS (sizeof(size_t)*CHAR_BIT - BITMAP_BITS) +#define OFFSET_WIDTH (SIZE_WIDTH - BITMAP_WIDTH) /** The highest representable offset (only 64k on a 32-bit architecture). */ -#define OFFSET_MAX (((size_t)1 << OFFSET_BITS) - 1) +#define OFFSET_MAX (((size_t)1 << OFFSET_WIDTH) - 1) /** * An internal node of the trie. @@ -116,13 +116,13 @@ struct trie_node { * Bit i will be set if a child exists at logical index i, and its index * into the array will be popcount(bitmap & ((1 << i) - 1)). */ - size_t bitmap : BITMAP_BITS; + size_t bitmap : BITMAP_WIDTH; /** * The offset into the key in nibbles. This is relative to the parent * node, to support offsets larger than OFFSET_MAX. */ - size_t offset : OFFSET_BITS; + size_t offset : OFFSET_WIDTH; /** * Flexible array of children. Each pointer uses the lowest bit as a -- cgit v1.2.3 From 796e384cae90e5ef5e14ba9aed907a5aaed1999d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 11:28:57 -0400 Subject: bar: Use USHRT_WIDTH --- src/bar.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bar.c b/src/bar.c index bd5d381..d2c663c 100644 --- a/src/bar.c +++ b/src/bar.c @@ -3,11 +3,11 @@ #include "bar.h" #include "bfstd.h" +#include "bit.h" #include "config.h" #include "dstring.h" #include #include -#include #include #include #include @@ -49,7 +49,7 @@ static int ass_puts(int fd, const char *str) { } /** Number of decimal digits needed for terminal sizes. */ -#define ITOA_DIGITS ((sizeof(unsigned short) * CHAR_BIT + 2) / 3) +#define ITOA_DIGITS ((USHRT_WIDTH + 2) / 3) /** Async Signal Safe itoa(). */ static char *ass_itoa(char *str, unsigned int n) { -- cgit v1.2.3 From 873e6bf836b2dd2d7dedc905c0386b7cd66c0d85 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 11:50:38 -0400 Subject: config: Include --- src/config.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/config.h b/src/config.h index 47aa529..f06300e 100644 --- a/src/config.h +++ b/src/config.h @@ -13,6 +13,7 @@ #if __STDC_VERSION__ < 202311L # include # include +# include #endif // bfs packaging configuration -- cgit v1.2.3 From ded8567215afa498295660d123159f26210e066a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 12:16:30 -0400 Subject: diag: New bfs_abort() and bfs_bug() macros --- src/diag.c | 10 +++++++++- src/diag.h | 24 ++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/diag.c b/src/diag.c index 53db98e..d7ffaa6 100644 --- a/src/diag.c +++ b/src/diag.c @@ -9,7 +9,15 @@ #include #include #include -#include +#include + +noreturn void bfs_abortf(const char *format, ...) { + va_list args; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); + abort(); +} void bfs_perror(const struct bfs_ctx *ctx, const char *str) { bfs_error(ctx, "%s: %m.\n", str); diff --git a/src/diag.h b/src/diag.h index a086942..a3e0e1d 100644 --- a/src/diag.h +++ b/src/diag.h @@ -22,6 +22,30 @@ # define BFS_STATIC_ASSERT(expr, msg, ...) _Static_assert(expr, msg) #endif +/** + * Print a message to standard error and abort. + */ +BFS_FORMATTER(1, 2) +noreturn void bfs_abortf(const char *format, ...); + +/** + * Unconditional abort with a message. + */ +#define bfs_abort(...) \ + BFS_ABORT(__VA_ARGS__, "\n") + +#define BFS_ABORT(format, ...) \ + bfs_abortf((format) ? "%s: %s:%d:%s(): " format "%s" : "", BFS_COMMAND, __FILE__, __LINE__, __func__, __VA_ARGS__) + +/** + * Abort in debug builds; no-op in release builds. + */ +#ifdef NDEBUG +# define bfs_bug(...) ((void)0) +#else +# define bfs_bug bfs_abort +#endif + struct bfs_expr; /** -- cgit v1.2.3 From 88581b9d505342e20d03cc4c6557d30c3f66f0f5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 13:27:04 -0400 Subject: Use bfs_bug("...") over assert(!"...") --- src/bfstd.c | 3 ++- src/color.c | 3 ++- src/ctx.c | 3 +-- src/dstring.c | 4 ++-- src/eval.c | 6 +++--- src/parse.c | 4 ++-- src/stat.c | 6 +++--- 7 files changed, 15 insertions(+), 14 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 932f2c4..91383a2 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -3,6 +3,7 @@ #include "bfstd.h" #include "config.h" +#include "diag.h" #include "xregex.h" #include #include @@ -95,7 +96,7 @@ FILE *xfopen(const char *path, int flags) { strcpy(mode, "r+b"); break; default: - assert(!"Invalid access mode"); + bfs_bug("Invalid access mode"); errno = EINVAL; return NULL; } diff --git a/src/color.c b/src/color.c index eeadf98..43ea9a4 100644 --- a/src/color.c +++ b/src/color.c @@ -5,6 +5,7 @@ #include "bfstd.h" #include "bftw.h" #include "config.h" +#include "diag.h" #include "dir.h" #include "dstring.h" #include "expr.h" @@ -1085,7 +1086,7 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { return 0; invalid: - assert(!"Invalid format string"); + bfs_bug("Invalid format string"); errno = EINVAL; return -1; } diff --git a/src/ctx.c b/src/ctx.c index ff4a2a7..c4b2fb2 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -11,7 +11,6 @@ #include "stat.h" #include "trie.h" #include "xtime.h" -#include #include #include #include @@ -38,7 +37,7 @@ const char *debug_flag_name(enum debug_flags flag) { break; } - assert(!"Unrecognized debug flag"); + bfs_bug("Unrecognized debug flag"); return "???"; } diff --git a/src/dstring.c b/src/dstring.c index 05efb10..2c9869d 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -2,7 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "dstring.h" -#include +#include "diag.h" #include #include #include @@ -184,7 +184,7 @@ int dstrvcatf(char **str, const char *format, va_list args) { tail = *str + len; ret = vsnprintf(tail, tail_len + 1, format, copy); if (ret < 0 || (size_t)ret != tail_len) { - assert(!"Length of formatted string changed"); + bfs_bug("Length of formatted string changed"); goto fail; } } diff --git a/src/eval.c b/src/eval.c index 7444ec9..6cad3a9 100644 --- a/src/eval.c +++ b/src/eval.c @@ -142,7 +142,7 @@ bool bfs_expr_cmp(const struct bfs_expr *expr, long long n) { return n > expr->num; } - assert(!"Invalid comparison mode"); + bfs_bug("Invalid comparison mode"); return false; } @@ -476,7 +476,7 @@ bool eval_flags(const struct bfs_expr *expr, struct bfs_eval *state) { return (flags & set) || (flags & clear) != clear; } - assert(!"Invalid comparison mode"); + bfs_bug("Invalid comparison mode"); return false; } @@ -626,7 +626,7 @@ bool eval_perm(const struct bfs_expr *expr, struct bfs_eval *state) { return !(mode & target) == !target; } - assert(!"Invalid comparison mode"); + bfs_bug("Invalid comparison mode"); return false; } diff --git a/src/parse.c b/src/parse.c index 55f1e74..807892c 100644 --- a/src/parse.c +++ b/src/parse.c @@ -638,7 +638,7 @@ static const char *parse_int(const struct parser_state *state, char **arg, const break; default: - assert(!"Invalid int size"); + bfs_bug("Invalid int size"); goto bad; } @@ -3492,7 +3492,7 @@ static const char *bftw_strategy_name(enum bftw_strategy strategy) { return "eds"; } - assert(!"Invalid strategy"); + bfs_bug("Invalid strategy"); return "???"; } diff --git a/src/stat.c b/src/stat.c index 50ea345..590a1d6 100644 --- a/src/stat.c +++ b/src/stat.c @@ -4,7 +4,7 @@ #include "stat.h" #include "bfstd.h" #include "config.h" -#include +#include "diag.h" #include #include #include @@ -57,7 +57,7 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { return "modification time"; } - assert(!"Unrecognized stat field"); + bfs_bug("Unrecognized stat field"); return "???"; } @@ -340,7 +340,7 @@ const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_f case BFS_STAT_MTIME: return &buf->mtime; default: - assert(!"Invalid stat field for time"); + bfs_bug("Invalid stat field for time"); errno = EINVAL; return NULL; } -- cgit v1.2.3 From 24386b8b369dcac60953af3327b75f1870639f5f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 15:45:51 -0400 Subject: build: Error on implicit function declarations --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 1d3bd20..546c605 100644 --- a/Makefile +++ b/Makefile @@ -34,6 +34,7 @@ DEFAULT_CFLAGS := \ -g \ -Wall \ -Wformat=2 \ + -Werror=implicit \ -Wimplicit-fallthrough \ -Wmissing-declarations \ -Wshadow \ -- cgit v1.2.3 From 63a52b1bfc99c58f0a944174282da79aab5bde3a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 15:51:12 -0400 Subject: diag: New bfs_verify() and bfs_assert() macros --- src/diag.h | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/diag.h b/src/diag.h index a3e0e1d..71abb1a 100644 --- a/src/diag.h +++ b/src/diag.h @@ -46,6 +46,28 @@ noreturn void bfs_abortf(const char *format, ...); # define bfs_bug bfs_abort #endif +/** + * Unconditional assert. + */ +#define bfs_verify(...) \ + BFS_VERIFY(#__VA_ARGS__, __VA_ARGS__, "", "\n") + +#define BFS_VERIFY(str, cond, format, ...) \ + ((cond) ? (void)0 : bfs_abortf( \ + sizeof(format) > 1 \ + ? "%s: %s:%d: %s(): %.0s" format "%s%s" \ + : "%s: %s:%d: %s(): Assertion failed: `%s`%s", \ + BFS_COMMAND, __FILE__, __LINE__, __func__, str, __VA_ARGS__)) + +/** + * Assert in debug builds; no-op in release builds. + */ +#ifdef NDEBUG +# define bfs_assert(...) ((void)0) +#else +# define bfs_assert bfs_verify +#endif + struct bfs_expr; /** -- cgit v1.2.3 From 526133c11eb9a26a4cffb20bcd10bcbb36d940de Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 May 2023 16:44:30 -0400 Subject: Switch from assert() to bfs_assert()/bfs_verify() --- src/bfstd.c | 3 +- src/bftw.c | 28 ++++++++-------- src/color.c | 5 ++- src/diag.c | 3 +- src/dir.c | 4 +-- src/eval.c | 11 +++---- src/exec.c | 7 ++-- src/opt.c | 13 ++++---- src/parse.c | 7 ++-- src/printf.c | 5 ++- src/trie.c | 33 ++++++++++--------- src/xregex.c | 4 +-- tests/bfstd.c | 13 ++++---- tests/bit.c | 100 +++++++++++++++++++++++++++++----------------------------- tests/trie.c | 56 ++++++++++++++++---------------- 15 files changed, 140 insertions(+), 152 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 91383a2..37f5276 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -5,7 +5,6 @@ #include "config.h" #include "diag.h" #include "xregex.h" -#include #include #include #include @@ -393,7 +392,7 @@ void close_quietly(int fd) { int xclose(int fd) { int ret = close(fd); if (ret != 0) { - assert(errno != EBADF); + bfs_verify(errno != EBADF); } return ret; } diff --git a/src/bftw.c b/src/bftw.c index 14805de..6ec5cfa 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -19,13 +19,13 @@ #include "bftw.h" #include "bfstd.h" #include "config.h" +#include "diag.h" #include "dir.h" #include "dstring.h" #include "list.h" #include "mtab.h" #include "stat.h" #include "trie.h" -#include #include #include #include @@ -114,7 +114,7 @@ static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) /** Close a bftw_file. */ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { - assert(file->fd >= 0); + bfs_assert(file->fd >= 0); if (LIST_ATTACHED(cache, file, lru)) { bftw_cache_remove(cache, file); @@ -137,7 +137,7 @@ static int bftw_cache_pop(struct bftw_cache *cache) { /** Add a bftw_file to the cache. */ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { - assert(file->fd >= 0); + bfs_assert(file->fd >= 0); if (cache->capacity == 0 && bftw_cache_pop(cache) != 0) { bftw_file_close(cache, file); @@ -145,7 +145,7 @@ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { return -1; } - assert(cache->capacity > 0); + bfs_assert(cache->capacity > 0); --cache->capacity; LIST_INSERT(cache, cache->target, file, lru); @@ -169,9 +169,9 @@ static size_t bftw_child_nameoff(const struct bftw_file *parent) { /** Destroy a cache. */ static void bftw_cache_destroy(struct bftw_cache *cache) { - assert(!cache->head); - assert(!cache->tail); - assert(!cache->target); + bfs_assert(!cache->head); + bfs_assert(!cache->tail); + bfs_assert(!cache->target); } /** Create a new bftw_file. */ @@ -230,7 +230,7 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam * The opened file descriptor, or negative on error. */ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, struct bftw_file *base, const char *at_path) { - assert(file->fd < 0); + bfs_assert(file->fd < 0); int at_fd = AT_FDCWD; if (base) { @@ -332,7 +332,7 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_f /** Free a bftw_file. */ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { - assert(file->refcount == 0); + bfs_assert(file->refcount == 0); if (file->fd >= 0) { bftw_file_close(cache, file); @@ -770,7 +770,7 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { - assert(!state->file); + bfs_assert(!state->file); if (!state->dirs.head) { return false; @@ -787,7 +787,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { - assert(!state->file); + bfs_assert(!state->file); state->file = state->files.head; if (state->file) { @@ -802,8 +802,8 @@ static bool bftw_pop_file(struct bftw_state *state) { * Open the current directory. */ static int bftw_opendir(struct bftw_state *state) { - assert(!state->dir); - assert(!state->de); + bfs_assert(!state->dir); + bfs_assert(!state->de); state->direrror = 0; @@ -863,7 +863,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { if (state->dir) { struct bftw_file *file = state->file; - assert(file && file->fd >= 0); + bfs_assert(file && file->fd >= 0); if (file->refcount > 1) { // Keep the fd around if any subdirectories exist diff --git a/src/color.c b/src/color.c index 43ea9a4..a723084 100644 --- a/src/color.c +++ b/src/color.c @@ -12,7 +12,6 @@ #include "fsade.h" #include "stat.h" #include "trie.h" -#include #include #include #include @@ -699,7 +698,7 @@ static int print_colored(CFILE *cfile, const char *esc, const char *str, size_t /** Find the offset of the first broken path component. */ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, size_t max) { ssize_t ret = max; - assert(ret >= 0); + bfs_assert(ret >= 0); if (bftw_type(ftwbuf, flags) != BFS_ERROR) { goto out; @@ -1100,7 +1099,7 @@ static int cbuff(CFILE *cfile, const char *format, ...) { } int cvfprintf(CFILE *cfile, const char *format, va_list args) { - assert(dstrlen(cfile->buffer) == 0); + bfs_assert(dstrlen(cfile->buffer) == 0); int ret = -1; if (cvbuff(cfile, format, args) == 0) { diff --git a/src/diag.c b/src/diag.c index d7ffaa6..04e3678 100644 --- a/src/diag.c +++ b/src/diag.c @@ -6,7 +6,6 @@ #include "ctx.h" #include "color.h" #include "expr.h" -#include #include #include #include @@ -117,7 +116,7 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs for (size_t i = 0; i < ctx->argc; ++i) { if (&ctx->argv[i] == expr->argv) { for (size_t j = 0; j < expr->argc; ++j) { - assert(i + j < ctx->argc); + bfs_assert(i + j < ctx->argc); args[i + j] = true; ret = true; } diff --git a/src/dir.c b/src/dir.c index 6739f10..d9ee63b 100644 --- a/src/dir.c +++ b/src/dir.c @@ -4,7 +4,7 @@ #include "dir.h" #include "bfstd.h" #include "config.h" -#include +#include "diag.h" #include #include #include @@ -249,7 +249,7 @@ int bfs_closedir(struct bfs_dir *dir) { int ret = xclose(dir->fd); #else int ret = closedir(dir->dir); - assert(ret == 0 || errno != EBADF); + bfs_verify(ret == 0 || errno != EBADF); #endif free(dir); return ret; diff --git a/src/eval.c b/src/eval.c index 6cad3a9..342debd 100644 --- a/src/eval.c +++ b/src/eval.c @@ -26,7 +26,6 @@ #include "trie.h" #include "xregex.h" #include "xtime.h" -#include #include #include #include @@ -990,7 +989,7 @@ static bool eval_expr(struct bfs_expr *expr, struct bfs_eval *state) { } } - assert(!state->quit); + bfs_assert(!state->quit); bool ret = expr->eval_fn(expr, state); @@ -1006,10 +1005,10 @@ static bool eval_expr(struct bfs_expr *expr, struct bfs_eval *state) { } if (bfs_expr_never_returns(expr)) { - assert(state->quit); + bfs_assert(state->quit); } else if (!state->quit) { - assert(!expr->always_true || ret); - assert(!expr->always_false || !ret); + bfs_assert(!expr->always_true || ret); + bfs_assert(!expr->always_false || !ret); } return ret; @@ -1511,7 +1510,7 @@ static void dump_bftw_flags(enum bftw_flags flags) { DEBUG_FLAG(flags, BFTW_SORT); DEBUG_FLAG(flags, BFTW_BUFFER); - assert(!flags); + bfs_assert(flags == 0, "Missing bftw flag 0x%X", flags); } /** diff --git a/src/exec.c b/src/exec.c index 7f22d36..5912ad6 100644 --- a/src/exec.c +++ b/src/exec.c @@ -10,7 +10,6 @@ #include "diag.h" #include "dstring.h" #include "xspawn.h" -#include #include #include #include @@ -276,12 +275,12 @@ static void bfs_exec_free_arg(char *arg, const char *tmpl) { /** Open a file to use as the working directory. */ static int bfs_exec_openwd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf) { - assert(execbuf->wd_fd < 0); - assert(!execbuf->wd_path); + bfs_assert(execbuf->wd_fd < 0); + bfs_assert(!execbuf->wd_path); if (ftwbuf->at_fd != AT_FDCWD) { // Rely on at_fd being the immediate parent - assert(xbaseoff(ftwbuf->at_path) == 0); + bfs_assert(xbaseoff(ftwbuf->at_path) == 0); execbuf->wd_fd = ftwbuf->at_fd; if (!(execbuf->flags & BFS_EXEC_MULTI)) { diff --git a/src/opt.c b/src/opt.c index 4ce9425..4699af4 100644 --- a/src/opt.c +++ b/src/opt.c @@ -35,7 +35,6 @@ #include "exec.h" #include "expr.h" #include "pwcache.h" -#include #include #include #include @@ -308,7 +307,7 @@ struct opt_state { /** Log an optimization. */ BFS_FORMATTER(3, 4) static bool opt_debug(const struct opt_state *state, int level, const char *format, ...) { - assert(state->ctx->optlevel >= level); + bfs_assert(state->ctx->optlevel >= level); if (bfs_debug(state->ctx, DEBUG_OPT, "${cyn}-O%d${rs}: ", level)) { va_list args; @@ -387,7 +386,7 @@ static struct bfs_expr *de_morgan(const struct opt_state *state, struct bfs_expr has_parent = false; } - assert(expr->eval_fn == eval_and || expr->eval_fn == eval_or); + bfs_assert(expr->eval_fn == eval_and || expr->eval_fn == eval_or); if (expr->eval_fn == eval_and) { expr->eval_fn = eval_or; expr->argv = &fake_or_arg; @@ -446,7 +445,7 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct * Optimize a negation. */ static struct bfs_expr *optimize_not_expr(const struct opt_state *state, struct bfs_expr *expr) { - assert(expr->eval_fn == eval_not); + bfs_assert(expr->eval_fn == eval_not); struct bfs_expr *rhs = expr->rhs; @@ -498,7 +497,7 @@ fail: /** Optimize a conjunction. */ static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct bfs_expr *expr) { - assert(expr->eval_fn == eval_and); + bfs_assert(expr->eval_fn == eval_and); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; @@ -569,7 +568,7 @@ fail: /** Optimize a disjunction. */ static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct bfs_expr *expr) { - assert(expr->eval_fn == eval_or); + bfs_assert(expr->eval_fn == eval_or); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; @@ -673,7 +672,7 @@ static struct bfs_expr *ignore_result(const struct opt_state *state, struct bfs_ /** Optimize a comma expression. */ static struct bfs_expr *optimize_comma_expr(const struct opt_state *state, struct bfs_expr *expr) { - assert(expr->eval_fn == eval_comma); + bfs_assert(expr->eval_fn == eval_comma); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; diff --git a/src/parse.c b/src/parse.c index 807892c..1a04e68 100644 --- a/src/parse.c +++ b/src/parse.c @@ -29,7 +29,6 @@ #include "xregex.h" #include "xspawn.h" #include "xtime.h" -#include #include #include #include @@ -134,7 +133,7 @@ static struct bfs_expr *new_unary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *rh expr->lhs = NULL; expr->rhs = rhs; - assert(bfs_expr_is_parent(expr)); + bfs_assert(bfs_expr_is_parent(expr)); expr->persistent_fds = rhs->persistent_fds; expr->ephemeral_fds = rhs->ephemeral_fds; @@ -154,7 +153,7 @@ static struct bfs_expr *new_binary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *l expr->lhs = lhs; expr->rhs = rhs; - assert(bfs_expr_is_parent(expr)); + bfs_assert(bfs_expr_is_parent(expr)); expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; if (lhs->ephemeral_fds > rhs->ephemeral_fds) { @@ -263,7 +262,7 @@ static void init_highlight(const struct bfs_ctx *ctx, bool *args) { static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, bool *args) { size_t i = argv - ctx->argv; for (size_t j = 0; j < argc; ++j) { - assert(i + j < ctx->argc); + bfs_assert(i + j < ctx->argc); args[i + j] = true; } } diff --git a/src/printf.c b/src/printf.c index 9ccc216..6520d2d 100644 --- a/src/printf.c +++ b/src/printf.c @@ -16,7 +16,6 @@ #include "pwcache.h" #include "stat.h" #include "xtime.h" -#include #include #include #include @@ -74,7 +73,7 @@ static bool should_color(CFILE *cfile, const struct bfs_printf *directive) { #define BFS_PRINTF_BUF(buf, format, ...) \ char buf[256]; \ int ret = snprintf(buf, sizeof(buf), format, __VA_ARGS__); \ - assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ + bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ (void)ret /** @@ -190,7 +189,7 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, break; } - assert(ret >= 0 && (size_t)ret < sizeof(buf)); + bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); (void)ret; return dyn_fprintf(cfile->file, directive, buf); diff --git a/src/trie.c b/src/trie.c index a2921de..8543eb1 100644 --- a/src/trie.c +++ b/src/trie.c @@ -86,7 +86,6 @@ #include "config.h" #include "diag.h" #include "list.h" -#include #include #include #include @@ -139,27 +138,27 @@ static bool trie_is_leaf(uintptr_t ptr) { /** Decode a pointer to a leaf. */ static struct trie_leaf *trie_decode_leaf(uintptr_t ptr) { - assert(trie_is_leaf(ptr)); + bfs_assert(trie_is_leaf(ptr)); return (struct trie_leaf *)(ptr ^ 1); } /** Encode a pointer to a leaf. */ static uintptr_t trie_encode_leaf(const struct trie_leaf *leaf) { uintptr_t ptr = (uintptr_t)leaf ^ 1; - assert(trie_is_leaf(ptr)); + bfs_assert(trie_is_leaf(ptr)); return ptr; } /** Decode a pointer to an internal node. */ static struct trie_node *trie_decode_node(uintptr_t ptr) { - assert(!trie_is_leaf(ptr)); + bfs_assert(!trie_is_leaf(ptr)); return (struct trie_node *)ptr; } /** Encode a pointer to an internal node. */ static uintptr_t trie_encode_node(const struct trie_node *node) { uintptr_t ptr = (uintptr_t)node; - assert(!trie_is_leaf(ptr)); + bfs_assert(!trie_is_leaf(ptr)); return ptr; } @@ -341,9 +340,9 @@ static void trie_leaf_free(struct trie *trie, struct trie_leaf *leaf) { /** Compute the size of a trie node with a certain number of children. */ static size_t trie_node_size(unsigned int size) { // Empty nodes aren't supported - assert(size > 0); + bfs_assert(size > 0); // Node size must be a power of two - assert(has_single_bit(size)); + bfs_assert(has_single_bit(size)); return flex_sizeof(struct trie_node, children, size); } @@ -435,7 +434,7 @@ static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, str unsigned int bit = 1U << nibble; // The child must not already be present - assert(!(node->bitmap & bit)); + bfs_assert(!(node->bitmap & bit)); node->bitmap |= bit; unsigned int target = count_ones(node->bitmap & (bit - 1)); @@ -474,7 +473,7 @@ static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, str static uintptr_t *trie_jump(uintptr_t *ptr, const char *key, size_t *offset) { // We only ever need to jump to leaf nodes, since internal nodes are // guaranteed to be within OFFSET_MAX anyway - assert(trie_is_leaf(*ptr)); + bfs_assert(trie_is_leaf(*ptr)); struct trie_node *node = malloc(trie_node_size(1)); if (!node) { @@ -512,7 +511,7 @@ static uintptr_t *trie_jump(uintptr_t *ptr, const char *key, size_t *offset) { static struct trie_leaf *trie_split(struct trie *trie, uintptr_t *ptr, struct trie_leaf *leaf, struct trie_leaf *rep, size_t offset, size_t mismatch) { unsigned char key_nibble = trie_key_nibble(leaf->key, mismatch); unsigned char rep_nibble = trie_key_nibble(rep->key, mismatch); - assert(key_nibble != rep_nibble); + bfs_assert(key_nibble != rep_nibble); struct trie_node *node = malloc(trie_node_size(2)); if (!node) { @@ -570,11 +569,11 @@ static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; if (node->bitmap & bit) { - assert(offset < mismatch); + bfs_assert(offset < mismatch); unsigned int index = count_ones(node->bitmap & (bit - 1)); ptr = &node->children[index]; } else { - assert(offset == mismatch); + bfs_assert(offset == mismatch); return trie_node_insert(trie, ptr, leaf, nibble); } } @@ -600,7 +599,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 - assert(has_single_bit(node->bitmap)); + bfs_assert(has_single_bit(node->bitmap)); ptr = node->children[0]; free(node); @@ -651,12 +650,12 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { while (!trie_is_leaf(*child)) { struct trie_node *node = trie_decode_node(*child); offset += node->offset; - assert((offset >> 1) < leaf->length); + bfs_assert((offset >> 1) < leaf->length); unsigned char nibble = trie_key_nibble(leaf->key, offset); unsigned int bit = 1U << nibble; unsigned int bitmap = node->bitmap; - assert(bitmap & bit); + bfs_assert(bitmap & bit); unsigned int index = count_ones(bitmap & (bit - 1)); // Advance the parent pointer, unless this node had only one child @@ -669,7 +668,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { child = &node->children[index]; } - assert(trie_decode_leaf(*child) == leaf); + bfs_assert(trie_decode_leaf(*child) == leaf); if (!parent) { trie_free_singletons(trie, trie->root); @@ -683,7 +682,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { node->bitmap ^= child_bit; unsigned int parent_size = count_ones(node->bitmap); - assert(parent_size > 0); + bfs_assert(parent_size > 0); if (parent_size == 1 && trie_collapse_node(parent, node, child_index) == 0) { return; } diff --git a/src/xregex.c b/src/xregex.c index a7153b7..1143f23 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -3,7 +3,7 @@ #include "xregex.h" #include "config.h" -#include +#include "diag.h" #include #include #include @@ -140,7 +140,7 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ syntax = ONIG_SYNTAX_GREP; break; } - assert(syntax); + bfs_assert(syntax); OnigOptionType options = syntax->options; if (flags & BFS_REGEX_ICASE) { diff --git a/tests/bfstd.c b/tests/bfstd.c index a986a23..1812a00 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,10 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD -#undef NDEBUG #include "../src/bfstd.h" #include "../src/config.h" -#include +#include "../src/diag.h" #include #include #include @@ -39,11 +38,11 @@ int main(void) { alignas(64) int foo; int bar[]; }; - assert(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); - assert(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); - assert(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) - == align_floor(alignof(struct flexible), SIZE_MAX)); - assert(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); + bfs_verify(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); + bfs_verify(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); + bfs_verify(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) + == align_floor(alignof(struct flexible), SIZE_MAX)); + bfs_verify(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); // From man 3p basename check_base_dir("usr", ".", "usr"); diff --git a/tests/bit.c b/tests/bit.c index 7a7b0f3..cb339f4 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -1,11 +1,8 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD -#undef NDEBUG - #include "../src/bit.h" #include "../src/diag.h" -#include #include #include #include @@ -54,68 +51,71 @@ bfs_static_assert(UINTMAX_MAX == UWIDTH_MAX(UINTMAX_WIDTH)); bfs_static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH)); bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); +#define verify_eq(a, b) \ + bfs_verify((a) == (b), "(0x%jX) %s != %s (0x%jX)", (uintmax_t)(a), #a, #b, (uintmax_t)(b)) + int main(void) { - assert(bswap((uint8_t)0x12) == 0x12); - assert(bswap((uint16_t)0x1234) == 0x3412); - assert(bswap((uint32_t)0x12345678) == 0x78563412); - assert(bswap((uint64_t)0x1234567812345678) == 0x7856341278563412); - - assert(count_ones(0x0) == 0); - assert(count_ones(0x1) == 1); - assert(count_ones(0x2) == 1); - assert(count_ones(0x3) == 2); - assert(count_ones(0x137F) == 10); - - assert(count_zeros(0) == INT_WIDTH); - assert(count_zeros(0L) == LONG_WIDTH); - assert(count_zeros(0LL) == LLONG_WIDTH); - assert(count_zeros((uint8_t)0) == 8); - assert(count_zeros((uint16_t)0) == 16); - assert(count_zeros((uint32_t)0) == 32); - assert(count_zeros((uint64_t)0) == 64); - - assert(rotate_left((uint8_t)0xA1, 4) == 0x1A); - assert(rotate_left((uint16_t)0x1234, 12) == 0x4123); - assert(rotate_left((uint32_t)0x12345678, 20) == 0x67812345); - assert(rotate_left((uint32_t)0x12345678, 0) == 0x12345678); - - assert(rotate_right((uint8_t)0xA1, 4) == 0x1A); - assert(rotate_right((uint16_t)0x1234, 12) == 0x2341); - assert(rotate_right((uint32_t)0x12345678, 20) == 0x45678123); - assert(rotate_right((uint32_t)0x12345678, 0) == 0x12345678); + verify_eq(bswap((uint8_t)0x12), 0x12); + verify_eq(bswap((uint16_t)0x1234), 0x3412); + verify_eq(bswap((uint32_t)0x12345678), 0x78563412); + verify_eq(bswap((uint64_t)0x1234567812345678), 0x7856341278563412); + + verify_eq(count_ones(0x0), 0); + verify_eq(count_ones(0x1), 1); + verify_eq(count_ones(0x2), 1); + verify_eq(count_ones(0x3), 2); + verify_eq(count_ones(0x137F), 10); + + verify_eq(count_zeros(0), INT_WIDTH); + verify_eq(count_zeros(0L), LONG_WIDTH); + verify_eq(count_zeros(0LL), LLONG_WIDTH); + verify_eq(count_zeros((uint8_t)0), 8); + verify_eq(count_zeros((uint16_t)0), 16); + verify_eq(count_zeros((uint32_t)0), 32); + verify_eq(count_zeros((uint64_t)0), 64); + + verify_eq(rotate_left((uint8_t)0xA1, 4), 0x1A); + verify_eq(rotate_left((uint16_t)0x1234, 12), 0x4123); + verify_eq(rotate_left((uint32_t)0x12345678, 20), 0x67812345); + verify_eq(rotate_left((uint32_t)0x12345678, 0), 0x12345678); + + verify_eq(rotate_right((uint8_t)0xA1, 4), 0x1A); + verify_eq(rotate_right((uint16_t)0x1234, 12), 0x2341); + verify_eq(rotate_right((uint32_t)0x12345678, 20), 0x45678123); + verify_eq(rotate_right((uint32_t)0x12345678, 0), 0x12345678); for (int i = 0; i < 16; ++i) { uint16_t n = (uint16_t)1 << i; for (int j = i; j < 16; ++j) { uint16_t m = (uint16_t)1 << j; uint16_t nm = n | m; - assert(count_ones(nm) == 1 + (n != m)); - assert(count_zeros(nm) == 15 - (n != m)); - assert(leading_zeros(nm) == 15 - j); - assert(trailing_zeros(nm) == i); - assert(first_leading_one(nm) == j + 1); - assert(first_trailing_one(nm) == i + 1); - assert(bit_width(nm) == j + 1); - assert(bit_floor(nm) == m); + verify_eq(count_ones(nm), 1 + (n != m)); + verify_eq(count_zeros(nm), 15 - (n != m)); + verify_eq(leading_zeros(nm), 15 - j); + verify_eq(trailing_zeros(nm), i); + verify_eq(first_leading_one(nm), j + 1); + verify_eq(first_trailing_one(nm), i + 1); + verify_eq(bit_width(nm), j + 1); + verify_eq(bit_floor(nm), m); if (n == m) { - assert(bit_ceil(nm) == m); - assert(has_single_bit(nm)); + verify_eq(bit_ceil(nm), m); + bfs_verify(has_single_bit(nm)); } else { if (j < 15) { - assert(bit_ceil(nm) == (m << 1)); + verify_eq(bit_ceil(nm), (m << 1)); } - assert(!has_single_bit(nm)); + bfs_verify(!has_single_bit(nm)); } } } - assert(leading_zeros((uint16_t)0) == 16); - assert(trailing_zeros((uint16_t)0) == 16); - assert(first_leading_one(0) == 0); - assert(first_trailing_one(0) == 0); - assert(bit_width(0) == 0); - assert(bit_floor(0) == 0); - assert(bit_ceil(0) == 1); + verify_eq(leading_zeros((uint16_t)0), 16); + verify_eq(trailing_zeros((uint16_t)0), 16); + verify_eq(first_leading_one(0), 0); + verify_eq(first_trailing_one(0), 0); + verify_eq(bit_width(0), 0); + verify_eq(bit_floor(0), 0); + verify_eq(bit_ceil(0), 1); return EXIT_SUCCESS; } diff --git a/tests/trie.c b/tests/trie.c index ced14d4..e687f96 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -1,11 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD -#undef NDEBUG - #include "../src/trie.h" #include "../src/config.h" -#include +#include "../src/diag.h" #include #include @@ -45,7 +43,7 @@ int main(void) { trie_init(&trie); for (size_t i = 0; i < nkeys; ++i) { - assert(!trie_find_str(&trie, keys[i])); + bfs_verify(!trie_find_str(&trie, keys[i])); const char *prefix = NULL; for (size_t j = 0; j < i; ++j) { @@ -58,38 +56,38 @@ int main(void) { struct trie_leaf *leaf = trie_find_prefix(&trie, keys[i]); if (prefix) { - assert(leaf); - assert(strcmp(prefix, leaf->key) == 0); + bfs_verify(leaf); + bfs_verify(strcmp(prefix, leaf->key) == 0); } else { - assert(!leaf); + bfs_verify(!leaf); } leaf = trie_insert_str(&trie, keys[i]); - assert(leaf); - assert(strcmp(keys[i], leaf->key) == 0); - assert(leaf->length == strlen(keys[i]) + 1); + bfs_verify(leaf); + bfs_verify(strcmp(keys[i], leaf->key) == 0); + bfs_verify(leaf->length == strlen(keys[i]) + 1); } { size_t i = 0; TRIE_FOR_EACH(&trie, leaf) { - assert(leaf == trie_find_str(&trie, keys[i])); - assert(!leaf->prev || leaf->prev->next == leaf); - assert(!leaf->next || leaf->next->prev == leaf); + bfs_verify(leaf == trie_find_str(&trie, keys[i])); + bfs_verify(!leaf->prev || leaf->prev->next == leaf); + bfs_verify(!leaf->next || leaf->next->prev == leaf); ++i; } - assert(i == nkeys); + bfs_verify(i == nkeys); } for (size_t i = 0; i < nkeys; ++i) { struct trie_leaf *leaf = trie_find_str(&trie, keys[i]); - assert(leaf); - assert(strcmp(keys[i], leaf->key) == 0); - assert(leaf->length == strlen(keys[i]) + 1); + bfs_verify(leaf); + bfs_verify(strcmp(keys[i], leaf->key) == 0); + bfs_verify(leaf->length == strlen(keys[i]) + 1); trie_remove(&trie, leaf); leaf = trie_find_str(&trie, keys[i]); - assert(!leaf); + bfs_verify(!leaf); const char *postfix = NULL; for (size_t j = i + 1; j < nkeys; ++j) { @@ -102,33 +100,33 @@ int main(void) { leaf = trie_find_postfix(&trie, keys[i]); if (postfix) { - assert(leaf); - assert(strcmp(postfix, leaf->key) == 0); + bfs_verify(leaf); + bfs_verify(strcmp(postfix, leaf->key) == 0); } else { - assert(!leaf); + bfs_verify(!leaf); } } TRIE_FOR_EACH(&trie, leaf) { - assert(false); + bfs_verify(false); } // This tests the "jump" node handling on 32-bit platforms size_t longsize = 1 << 20; char *longstr = malloc(longsize); - assert(longstr); + bfs_verify(longstr); memset(longstr, 0xAC, longsize); - assert(!trie_find_mem(&trie, longstr, longsize)); - assert(trie_insert_mem(&trie, longstr, longsize)); + bfs_verify(!trie_find_mem(&trie, longstr, longsize)); + bfs_verify(trie_insert_mem(&trie, longstr, longsize)); memset(longstr + longsize/2, 0xAB, longsize/2); - assert(!trie_find_mem(&trie, longstr, longsize)); - assert(trie_insert_mem(&trie, longstr, longsize)); + bfs_verify(!trie_find_mem(&trie, longstr, longsize)); + bfs_verify(trie_insert_mem(&trie, longstr, longsize)); memset(longstr, 0xAA, longsize/2); - assert(!trie_find_mem(&trie, longstr, longsize)); - assert(trie_insert_mem(&trie, longstr, longsize)); + bfs_verify(!trie_find_mem(&trie, longstr, longsize)); + bfs_verify(trie_insert_mem(&trie, longstr, longsize)); free(longstr); trie_destroy(&trie); -- cgit v1.2.3 From f642b3dd1271e4a1503ce7629ba36e25d6a59fce Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 May 2023 11:33:32 -0400 Subject: tests/bfstd: Use bfs_verify() over explicit abort() --- tests/bfstd.c | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/tests/bfstd.c b/tests/bfstd.c index 1812a00..7fea9b5 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -4,6 +4,7 @@ #include "../src/bfstd.h" #include "../src/config.h" #include "../src/diag.h" +#include #include #include #include @@ -12,23 +13,13 @@ /** Check the result of xdirname()/xbasename(). */ static void check_base_dir(const char *path, const char *dir, const char *base) { char *xdir = xdirname(path); - if (!xdir) { - perror("xdirname()"); - abort(); - } else if (strcmp(xdir, dir) != 0) { - fprintf(stderr, "xdirname(\"%s\") == \"%s\" (!= \"%s\")\n", path, xdir, dir); - abort(); - } + bfs_verify(xdir, "xdirname(): %s", strerror(errno)); + bfs_verify(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir); free(xdir); char *xbase = xbasename(path); - if (!xbase) { - perror("xbasename()"); - abort(); - } else if (strcmp(xbase, base) != 0) { - fprintf(stderr, "xbasename(\"%s\") == \"%s\" (!= \"%s\")\n", path, xbase, base); - abort(); - } + bfs_verify(xbase, "xbasename(): %s", strerror(errno)); + bfs_verify(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base); free(xbase); } -- cgit v1.2.3 From bc952b51f48905392cec5685853fbb44565057a8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 20 May 2023 14:37:56 -0400 Subject: list: Return the removed item from SLIST_POP() --- src/bftw.c | 6 ++---- src/list.h | 21 ++++++++++++++++----- src/xspawn.c | 4 +--- 3 files changed, 19 insertions(+), 12 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 6ec5cfa..966f03d 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -780,8 +780,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } - state->file = state->dirs.head; - SLIST_POP(&state->dirs); + state->file = SLIST_POP(&state->dirs); return true; } @@ -952,8 +951,7 @@ static void bftw_list_sort(struct bftw_list *list) { // Split for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) { - struct bftw_file *tortoise = list->head; - SLIST_POP(list); + struct bftw_file *tortoise = SLIST_POP(list); SLIST_APPEND(&left, tortoise); } SLIST_EXTEND(&right, list); diff --git a/src/list.h b/src/list.h index 87e2bf2..89f00ed 100644 --- a/src/list.h +++ b/src/list.h @@ -79,6 +79,7 @@ #define BFS_LIST_H #include +#include /** * Initialize a singly-linked list. @@ -250,17 +251,27 @@ * A pointer to the item to remove, either &list->head or &prev->next. * @param node (optional) * If specified, use item->node.next rather than item->next. + * @return + * The removed item. */ #define SLIST_REMOVE(list, ...) SLIST_REMOVE_(list, __VA_ARGS__, ) #define SLIST_REMOVE_(list, cursor, ...) \ - LIST_BLOCK_(SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__))) + SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__)) #define SLIST_REMOVE__(list, cursor, next) \ - void *_next = (void *)(*cursor)->next; \ - (*cursor)->next = NULL; \ - *cursor = _next; \ - list->tail = _next ? list->tail : cursor; + (list->tail = (*cursor)->next ? list->tail : cursor, \ + slist_remove_impl(*cursor, cursor, &(*cursor)->next, list->tail, sizeof(*cursor))) + +// Helper for SLIST_REMOVE() +static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void *tail, size_t size) { + // ret = *cursor; + // *cursor = ret->next; + memcpy(cursor, next, size); + // ret->next = *list->tail; (NULL) + memcpy(next, tail, size); + return ret; +} /** * Pop the head off a singly-linked list. diff --git a/src/xspawn.c b/src/xspawn.c index 00fb76e..740e38e 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -49,9 +49,7 @@ int bfs_spawn_init(struct bfs_spawn *ctx) { int bfs_spawn_destroy(struct bfs_spawn *ctx) { while (ctx->head) { - struct bfs_spawn_action *action = ctx->head; - SLIST_POP(ctx); - free(action); + free(SLIST_POP(ctx)); } return 0; -- cgit v1.2.3 From eef75524aec3910097cb6923c30b898ad98179fe Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 May 2023 11:54:33 -0400 Subject: list: Allow popping from an empty list --- src/bftw.c | 19 +++---------------- src/list.h | 4 +++- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 966f03d..916a56b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -299,11 +299,10 @@ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, cons SLIST_PREPEND(&parents, cur); } - while ((cur = parents.head)) { + while ((cur = SLIST_POP(&parents))) { if (!cur->parent || cur->parent->fd >= 0) { bftw_file_openat(cache, cur, cur->parent, cur->name); } - SLIST_POP(&parents); } return file->fd; @@ -772,29 +771,17 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - if (!state->dirs.head) { - return false; - } - if (state->files.head && state->strategy == BFTW_BFS) { return false; } - state->file = SLIST_POP(&state->dirs); - return true; + return (state->file = SLIST_POP(&state->dirs)); } /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - - state->file = state->files.head; - if (state->file) { - SLIST_POP(&state->files); - return true; - } else { - return false; - } + return (state->file = SLIST_POP(&state->files)); } /** diff --git a/src/list.h b/src/list.h index 89f00ed..a284c25 100644 --- a/src/list.h +++ b/src/list.h @@ -280,11 +280,13 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * The list to pop from. * @param node (optional) * If specified, use head->node.next rather than head->next. + * @return + * The popped item, or NULL if the list was empty. */ #define SLIST_POP(...) SLIST_POP_(__VA_ARGS__, ) #define SLIST_POP_(list, ...) \ - SLIST_REMOVE_(list, &(list)->head, __VA_ARGS__) + ((list)->head ? SLIST_REMOVE_(list, &(list)->head, __VA_ARGS__) : NULL) /** * Initialize a doubly-linked list. -- cgit v1.2.3 From 24a67ef8265e5873c0967ded296a9e57ed9f2914 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 25 May 2023 14:19:21 -0400 Subject: sanity: Add wrappers for sanitizer interfaces --- src/config.h | 9 ------ src/dir.c | 12 ++------ src/fsade.c | 9 +++--- src/parse.c | 1 + src/sanity.h | 89 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/stat.c | 18 ++++++------ src/xregex.c | 8 ++---- 7 files changed, 110 insertions(+), 36 deletions(-) create mode 100644 src/sanity.h diff --git a/src/config.h b/src/config.h index f06300e..dbe3c74 100644 --- a/src/config.h +++ b/src/config.h @@ -191,15 +191,6 @@ static inline size_t flex_sizeof_impl(size_t align, size_t min, size_t offset, s return ret; } -/** - * Initialize a variable, unless sanitizers would detect uninitialized uses. - */ -#if __has_feature(memory_sanitizer) -# define uninit(var, value) var -#else -# define uninit(var, value) value -#endif - // Wrappers for attributes /** diff --git a/src/dir.c b/src/dir.c index d9ee63b..01d26db 100644 --- a/src/dir.c +++ b/src/dir.c @@ -5,6 +5,7 @@ #include "bfstd.h" #include "config.h" #include "diag.h" +#include "sanity.h" #include #include #include @@ -18,18 +19,13 @@ #endif #if BFS_GETDENTS -# if __has_feature(memory_sanitizer) -# include -# endif # if __linux__ # include # endif /** getdents() syscall wrapper. */ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { -#if __has_feature(memory_sanitizer) - __msan_allocated_memory(buf, size); -#endif + sanitize_uninit(buf, size); #if __linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30) ssize_t ret = syscall(SYS_getdents64, fd, buf, size); @@ -39,11 +35,9 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { ssize_t ret = getdents(fd, buf, size); #endif -#if __has_feature(memory_sanitizer) if (ret > 0) { - __msan_unpoison(buf, ret); + sanitize_init(buf, ret); } -#endif return ret; } diff --git a/src/fsade.c b/src/fsade.c index 4d67940..ba89b60 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -159,13 +159,12 @@ static int bfs_check_acl_type(acl_t acl, acl_type_t type) { #if __FreeBSD__ int trivial; + int ret = acl_is_trivial_np(acl, &trivial); -#if __has_feature(memory_sanitizer) - // msan seems to be missing an interceptor for acl_is_trivial_np() - trivial = 0; -#endif + // msan seems to be missing an interceptor for acl_is_trivial_np() + sanitize_init(&trivial); - if (acl_is_trivial_np(acl, &trivial) < 0) { + if (ret < 0) { return -1; } else if (trivial) { return 0; diff --git a/src/parse.c b/src/parse.c index 1a04e68..59a1e7d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -24,6 +24,7 @@ #include "opt.h" #include "printf.h" #include "pwcache.h" +#include "sanity.h" #include "stat.h" #include "typo.h" #include "xregex.h" diff --git a/src/sanity.h b/src/sanity.h new file mode 100644 index 0000000..5696036 --- /dev/null +++ b/src/sanity.h @@ -0,0 +1,89 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Sanitizer interface. + */ + +#ifndef BFS_SANITY_H +#define BFS_SANITY_H + +#include "config.h" +#include + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# define SANITIZE_ADDRESS true +#endif + +#if __has_feature(memory_sanitizer) || defined(__SANITIZE_MEMORY__) +# define SANITIZE_MEMORY true +#endif + +#if __has_feature(thread_sanitizer) || defined(__SANITIZE_THREAD__) +# define SANITIZE_THREAD true +#endif + +// Call macro(ptr, size) or macro(ptr, sizeof(*ptr)) +#define SANITIZE_CALL(...) \ + SANITIZE_CALL_(__VA_ARGS__, ) + +#define SANITIZE_CALL_(macro, ptr, ...) \ + SANITIZE_CALL__(macro, ptr, __VA_ARGS__ sizeof(*(ptr)), ) + +#define SANITIZE_CALL__(macro, ptr, size, ...) \ + macro(ptr, size) + +#if SANITIZE_ADDRESS +# include + +/** + * sanitize_alloc(ptr, size = sizeof(*ptr)) + * + * Mark a memory region as allocated. + */ +#define sanitize_alloc(...) SANITIZE_CALL(__asan_unpoison_memory_region, __VA_ARGS__) + +/** + * sanitize_free(ptr, size = sizeof(*ptr)) + * + * Mark a memory region as free. + */ +#define sanitize_free(...) SANITIZE_CALL(__asan_poison_memory_region, __VA_ARGS__) + +#else +# define sanitize_alloc sanitize_uninit +# define sanitize_free sanitize_uninit +#endif + +#if SANITIZE_MEMORY +# include + +/** + * sanitize_init(ptr, size = sizeof(*ptr)) + * + * Mark a memory region as initialized. + */ +#define sanitize_init(...) SANITIZE_CALL(__msan_unpoison, __VA_ARGS__) + +/** + * sanitize_uninit(ptr, size = sizeof(*ptr)) + * + * Mark a memory region as uninitialized. + */ +#define sanitize_uninit(...) SANITIZE_CALL(__msan_allocated_memory, __VA_ARGS__) + +#else +# define sanitize_init(...) +# define sanitize_uninit(...) +#endif + +/** + * Initialize a variable, unless sanitizers would detect uninitialized uses. + */ +#if SANITIZE_MEMORY +# define uninit(var, value) var +#else +# define uninit(var, value) value +#endif + +#endif // BFS_SANITY_H diff --git a/src/stat.c b/src/stat.c index 590a1d6..3f70e6c 100644 --- a/src/stat.c +++ b/src/stat.c @@ -5,6 +5,7 @@ #include "bfstd.h" #include "config.h" #include "diag.h" +#include "sanity.h" #include #include #include @@ -132,17 +133,18 @@ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bf * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { -#if __has_feature(memory_sanitizer) - // -fsanitize=memory doesn't know about statx(), so tell it the memory - // got initialized - memset(buf, 0, sizeof(*buf)); -#endif - #if BFS_LIBC_STATX - return statx(at_fd, at_path, at_flags, mask, buf); + int ret = statx(at_fd, at_path, at_flags, mask, buf); #else - return syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf); + int ret = syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf); #endif + + if (ret == 0) { + // -fsanitize=memory doesn't know about statx() + sanitize_init(buf); + } + + return ret; } /** diff --git a/src/xregex.c b/src/xregex.c index 1143f23..ce59ff5 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -4,6 +4,7 @@ #include "xregex.h" #include "config.h" #include "diag.h" +#include "sanity.h" #include #include #include @@ -175,13 +176,10 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ cflags |= REG_ICASE; } -#if __has_feature(memory_sanitizer) - // https://github.com/google/sanitizers/issues/1496 - memset(®ex->impl, 0, sizeof(regex->impl)); -#endif - regex->err = regcomp(®ex->impl, pattern, cflags); if (regex->err != 0) { + // https://github.com/google/sanitizers/issues/1496 + sanitize_init(®ex->impl); return -1; } #endif -- cgit v1.2.3 From 6934c4355b75994052861bf935fae5edef7b988f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 25 May 2023 14:31:50 -0400 Subject: fsade: Add missing #include "sanity.h" --- src/fsade.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fsade.c b/src/fsade.c index ba89b60..ca3a99c 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -7,6 +7,7 @@ #include "bftw.h" #include "dir.h" #include "dstring.h" +#include "sanity.h" #include #include #include -- cgit v1.2.3 From 4c2d7e8271a35409acaba228968ab7641397bbe6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 25 May 2023 14:42:21 -0400 Subject: main: Add some missing headers to the overview --- src/main.c | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main.c b/src/main.c index 24a5035..c354553 100644 --- a/src/main.c +++ b/src/main.c @@ -21,6 +21,7 @@ * * - Utilities: * - bar.[ch] (a terminal status bar) + * - bit.h (bit manipulation) * - bfstd.[ch] (standard library wrappers/polyfills) * - color.[ch] (for pretty terminal colors) * - config.h (configuration and feature/platform detection) @@ -29,8 +30,10 @@ * - dir.[ch] (a directory API facade) * - dstring.[ch] (a dynamic string library) * - fsade.[ch] (a facade over non-standard filesystem features) + * - list.h (linked list macros) * - mtab.[ch] (parses the system's mount table) * - pwcache.[ch] (a cache for the user/group tables) + * - sanity.h (sanitizer interfaces) * - stat.[ch] (wraps stat(), or statx() on Linux) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) -- cgit v1.2.3 From 1d1443193878ae72e54560bb21de79668cd954b9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 8 Jun 2023 13:19:57 -0400 Subject: color: `fi=0` should not fall back to `no` --- src/color.c | 8 ++++---- tests/bfs/color_cd0_no.out | 20 ++++++++++++++++++++ tests/bfs/color_cd0_no.sh | 1 + tests/bfs/color_fi0_no.out | 20 ++++++++++++++++++++ tests/bfs/color_fi0_no.sh | 1 + tests/bfs/color_fi_no.out | 20 ++++++++++++++++++++ tests/bfs/color_fi_no.sh | 1 + tests/bfs/color_no.out | 20 ++++++++++++++++++++ tests/bfs/color_no.sh | 1 + 9 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 tests/bfs/color_cd0_no.out create mode 100644 tests/bfs/color_cd0_no.sh create mode 100644 tests/bfs/color_fi0_no.out create mode 100644 tests/bfs/color_fi0_no.sh create mode 100644 tests/bfs/color_fi_no.out create mode 100644 tests/bfs/color_fi_no.sh create mode 100644 tests/bfs/color_no.out create mode 100644 tests/bfs/color_no.sh diff --git a/src/color.c b/src/color.c index a723084..87575c9 100644 --- a/src/color.c +++ b/src/color.c @@ -436,10 +436,11 @@ struct colors *parse_colors(void) { ret |= init_color(colors, "err", "01;31", &colors->error); // Defaults from man dir_colors + // "" means fall back to ->normal ret |= init_color(colors, "no", NULL, &colors->normal); - ret |= init_color(colors, "fi", NULL, &colors->file); + ret |= init_color(colors, "fi", "", &colors->file); ret |= init_color(colors, "mh", NULL, &colors->multi_hard); ret |= init_color(colors, "ex", "01;32", &colors->executable); ret |= init_color(colors, "ca", NULL, &colors->capable); @@ -472,8 +473,7 @@ struct colors *parse_colors(void) { if (colors->link && strcmp(colors->link, "target") == 0) { colors->link_as_target = true; - dstrfree(colors->link); - colors->link = NULL; + dstresize(&colors->link, 0); } return colors; @@ -634,7 +634,7 @@ static const char *file_color(const struct colors *colors, const char *filename, break; } - if (!color) { + if (color && !color[0]) { color = colors->normal; } diff --git a/tests/bfs/color_cd0_no.out b/tests/bfs/color_cd0_no.out new file mode 100644 index 0000000..d5c98a9 --- /dev/null +++ b/tests/bfs/color_cd0_no.out @@ -0,0 +1,20 @@ +rainbow +rainbow/exec.sh +rainbow/socket +rainbow/broken +rainbow/file.dat +rainbow/file.txt +rainbow/link.txt +rainbow/mh1 +rainbow/mh2 +rainbow/star.gz +rainbow/star.tar +rainbow/star.tar.gz +rainbow/sticky_ow +rainbow/sgid +rainbow/pipe +rainbow/ow +rainbow/sugid +rainbow/suid +rainbow/sticky +rainbow/chardev_link diff --git a/tests/bfs/color_cd0_no.sh b/tests/bfs/color_cd0_no.sh new file mode 100644 index 0000000..325a782 --- /dev/null +++ b/tests/bfs/color_cd0_no.sh @@ -0,0 +1 @@ +LS_COLORS="ln=target:cd=0:no=01;92:" bfs_diff rainbow -color diff --git a/tests/bfs/color_fi0_no.out b/tests/bfs/color_fi0_no.out new file mode 100644 index 0000000..77fc8a8 --- /dev/null +++ b/tests/bfs/color_fi0_no.out @@ -0,0 +1,20 @@ +rainbow +rainbow/exec.sh +rainbow/socket +rainbow/broken +rainbow/chardev_link +rainbow/link.txt +rainbow/sticky_ow +rainbow/sgid +rainbow/pipe +rainbow/ow +rainbow/sugid +rainbow/suid +rainbow/sticky +rainbow/file.dat +rainbow/file.txt +rainbow/mh1 +rainbow/mh2 +rainbow/star.gz +rainbow/star.tar +rainbow/star.tar.gz diff --git a/tests/bfs/color_fi0_no.sh b/tests/bfs/color_fi0_no.sh new file mode 100644 index 0000000..f947d64 --- /dev/null +++ b/tests/bfs/color_fi0_no.sh @@ -0,0 +1 @@ +LS_COLORS="fi=0:no=01;92:" bfs_diff rainbow -color diff --git a/tests/bfs/color_fi_no.out b/tests/bfs/color_fi_no.out new file mode 100644 index 0000000..7162ded --- /dev/null +++ b/tests/bfs/color_fi_no.out @@ -0,0 +1,20 @@ +rainbow +rainbow/exec.sh +rainbow/socket +rainbow/broken +rainbow/chardev_link +rainbow/link.txt +rainbow/file.dat +rainbow/file.txt +rainbow/mh1 +rainbow/mh2 +rainbow/star.gz +rainbow/star.tar +rainbow/star.tar.gz +rainbow/sticky_ow +rainbow/sgid +rainbow/pipe +rainbow/ow +rainbow/sugid +rainbow/suid +rainbow/sticky diff --git a/tests/bfs/color_fi_no.sh b/tests/bfs/color_fi_no.sh new file mode 100644 index 0000000..c2b4ec7 --- /dev/null +++ b/tests/bfs/color_fi_no.sh @@ -0,0 +1 @@ +LS_COLORS="fi=01;91:no=01;92:" bfs_diff rainbow -color diff --git a/tests/bfs/color_no.out b/tests/bfs/color_no.out new file mode 100644 index 0000000..9f0dd66 --- /dev/null +++ b/tests/bfs/color_no.out @@ -0,0 +1,20 @@ +rainbow +rainbow/exec.sh +rainbow/socket +rainbow/broken +rainbow/chardev_link +rainbow/link.txt +rainbow/file.dat +rainbow/file.txt +rainbow/mh1 +rainbow/mh2 +rainbow/star.gz +rainbow/star.tar +rainbow/star.tar.gz +rainbow/sticky_ow +rainbow/sgid +rainbow/pipe +rainbow/ow +rainbow/sugid +rainbow/suid +rainbow/sticky diff --git a/tests/bfs/color_no.sh b/tests/bfs/color_no.sh new file mode 100644 index 0000000..b7527cb --- /dev/null +++ b/tests/bfs/color_no.sh @@ -0,0 +1 @@ +LS_COLORS="no=01;92:" bfs_diff rainbow -color -- cgit v1.2.3 From 2a5ffc60c339ccc108a2feedd522d871da4d094b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 8 Jun 2023 13:29:45 -0400 Subject: eval: Print non-path -ls text in the "normal" color, like ls -l --- src/eval.c | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/eval.c b/src/eval.c index 342debd..7015f6c 100644 --- a/src/eval.c +++ b/src/eval.c @@ -642,6 +642,11 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { goto done; } + // ls -l prints non-path text in the "normal" color, so do the same + if (cfprintf(cfile, "${no}") < 0) { + goto error; + } + uintmax_t ino = statbuf->ino; uintmax_t block_size = ctx->posixly_correct ? 512 : 1024; uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + block_size - 1)/block_size; @@ -709,7 +714,7 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { errno = EOVERFLOW; goto error; } - if (fprintf(file, " %s", time_str) < 0) { + if (cfprintf(cfile, " %s${rs}", time_str) < 0) { goto error; } -- cgit v1.2.3 From 24f19eb15cbae1c6d21e77fcdccdf1cf89ef6faf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 8 Jun 2023 14:20:35 -0400 Subject: eval: For -ls, track the longest user/group names to keep alignment --- src/eval.c | 46 ++++++++++++++++++++++++++-------------------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/src/eval.c b/src/eval.c index 7015f6c..e2c19a9 100644 --- a/src/eval.c +++ b/src/eval.c @@ -629,6 +629,24 @@ bool eval_perm(const struct bfs_expr *expr, struct bfs_eval *state) { return false; } +/** Print a user/group name/id, and update the column width. */ +static int print_owner(FILE *file, const char *name, uintmax_t id, int *width) { + if (name) { + int len = xstrwidth(name); + if (*width < len) { + *width = len; + } + + return fprintf(file, " %s%*s", name, *width - len, ""); + } else { + int ret = fprintf(file, " %-*ju", *width, id); + if (ret >= 0 && *width < ret - 1) { + *width = ret - 1; + } + return ret; + } +} + /** * -f?ls action. */ @@ -658,28 +676,16 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { goto error; } - uintmax_t uid = statbuf->uid; - const struct passwd *pwd = bfs_getpwuid(ctx->users, uid); - if (pwd) { - if (fprintf(file, " %-8s", pwd->pw_name) < 0) { - goto error; - } - } else { - if (fprintf(file, " %-8ju", uid) < 0) { - goto error; - } + const struct passwd *pwd = bfs_getpwuid(ctx->users, statbuf->uid); + static int uwidth = 8; + if (print_owner(file, pwd ? pwd->pw_name : NULL, statbuf->uid, &uwidth) < 0) { + goto error; } - uintmax_t gid = statbuf->gid; - const struct group *grp = bfs_getgrgid(ctx->groups, gid); - if (grp) { - if (fprintf(file, " %-8s", grp->gr_name) < 0) { - goto error; - } - } else { - if (fprintf(file, " %-8ju", gid) < 0) { - goto error; - } + const struct group *grp = bfs_getgrgid(ctx->groups, statbuf->gid); + static int gwidth = 8; + if (print_owner(file, grp ? grp->gr_name : NULL, statbuf->gid, &gwidth) < 0) { + goto error; } if (ftwbuf->type == BFS_BLK || ftwbuf->type == BFS_CHR) { -- cgit v1.2.3 From 02a21219571332850c7b2fa129e88fa14dbb462c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 6 Apr 2023 14:52:19 -0400 Subject: build: Add tsan to distcheck --- Makefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Makefile b/Makefile index 546c605..65e1301 100644 --- a/Makefile +++ b/Makefile @@ -196,6 +196,9 @@ all: bfs tests $(BIN)/%: @$(MKDIR) $(@D) +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ +ifeq ($(OS) $(TSAN),FreeBSD tsan) + elfctl -e +noaslr $@ +endif $(OBJ)/%.o: %.c $(OBJ)/FLAGS @$(MKDIR) $(@D) @@ -279,6 +282,7 @@ distcheck: ifneq ($(OS),Darwin) +$(MAKE) -B msan ubsan check CC=clang $(DISTCHECK_FLAGS) endif + +$(MAKE) -B tsan ubsan check CC=clang $(DISTCHECK_FLAGS) ifeq ($(OS) $(ARCH),Linux x86_64) +$(MAKE) -B check EXTRA_CFLAGS="-m32" ONIG_CONFIG= $(DISTCHECK_FLAGS) endif -- cgit v1.2.3 From d001ab384ae9b694a833a5d6140106970f31ceb1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 May 2023 09:53:10 -0400 Subject: build: Update to C17 This lets us avoid ATOMIC_VAR_INIT(). --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 65e1301..aa8617e 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ LOCAL_CPPFLAGS := \ -D_TIME_BITS=64 \ -DBFS_VERSION=\"$(VERSION)\" -LOCAL_CFLAGS := -std=c11 +LOCAL_CFLAGS := -std=c17 LOCAL_LDFLAGS := LOCAL_LDLIBS := -- cgit v1.2.3 From 87a714861c5746d990b44b6e0839f082d7068f81 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 11 Apr 2023 18:05:25 -0400 Subject: lock: Add wrappers for POSIX synchronization primitives --- Makefile | 3 ++- src/lock.h | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.c | 1 + 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 src/lock.h diff --git a/Makefile b/Makefile index aa8617e..20b4d07 100644 --- a/Makefile +++ b/Makefile @@ -55,7 +55,8 @@ LOCAL_CPPFLAGS := \ -D_LARGEFILE64_SOURCE \ -D_FILE_OFFSET_BITS=64 \ -D_TIME_BITS=64 \ - -DBFS_VERSION=\"$(VERSION)\" + -DBFS_VERSION=\"$(VERSION)\" \ + -pthread LOCAL_CFLAGS := -std=c17 LOCAL_LDFLAGS := diff --git a/src/lock.h b/src/lock.h new file mode 100644 index 0000000..2ca7876 --- /dev/null +++ b/src/lock.h @@ -0,0 +1,84 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Wrappers for POSIX synchronization primitives. + */ + +#ifndef BFS_LOCK_H +#define BFS_LOCK_H + +#include "diag.h" +#include +#include + +#define lock_verify(expr, cond) \ + bfs_verify((errno = (expr), (cond)), "%s: %s", #expr, strerror(errno)) + +/** + * Wrapper for pthread_mutex_init(). + * + * @return + * 0 on success, -1 on error. + */ +#define mutex_init(mutex, attr) \ + ((errno = pthread_mutex_init(mutex, attr)) ? -1 : 0) + +/** + * Wrapper for pthread_mutex_lock(). + */ +#define mutex_lock(mutex) \ + lock_verify(pthread_mutex_lock(mutex), errno == 0) + +/** + * Wrapper for pthread_mutex_trylock(). + * + * @return + * Whether the mutex was locked. + */ +#define mutex_trylock(mutex) \ + (lock_verify(pthread_mutex_trylock(mutex), errno == 0 || errno == EBUSY), errno == 0) + +/** + * Wrapper for pthread_mutex_unlock(). + */ +#define mutex_unlock(mutex) \ + lock_verify(pthread_mutex_unlock(mutex), errno == 0) + +/** + * Wrapper for pthread_mutex_destroy(). + */ +#define mutex_destroy(mutex) \ + lock_verify(pthread_mutex_destroy(mutex), errno == 0) + +/** + * Wrapper for pthread_cond_init(). + */ +#define cond_init(cond, attr) \ + ((errno = pthread_cond_init(cond, attr)) ? -1 : 0) + +/** + * Wrapper for pthread_cond_wait(). + */ +#define cond_wait(cond, mutex) \ + lock_verify(pthread_cond_wait(cond, mutex), errno == 0) + +/** + * Wrapper for pthread_cond_signal(). + */ +#define cond_signal(cond) \ + lock_verify(pthread_cond_signal(cond), errno == 0) + +/** + * Wrapper for pthread_cond_broadcast(). + */ +#define cond_broadcast(cond) \ + lock_verify(pthread_cond_broadcast(cond), errno == 0) + +/** + * Wrapper for pthread_cond_destroy(). + */ +#define cond_destroy(cond) \ + lock_verify(pthread_cond_destroy(cond), errno == 0) + +#endif // BFS_LOCK_H diff --git a/src/main.c b/src/main.c index c354553..95be48f 100644 --- a/src/main.c +++ b/src/main.c @@ -31,6 +31,7 @@ * - dstring.[ch] (a dynamic string library) * - fsade.[ch] (a facade over non-standard filesystem features) * - list.h (linked list macros) + * - lock.h (mutexes, condition variables, etc.) * - mtab.[ch] (parses the system's mount table) * - pwcache.[ch] (a cache for the user/group tables) * - sanity.h (sanitizer interfaces) -- cgit v1.2.3 From a7136999f9a4a04ed7ac6f83de37c34bac01ade1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 7 Apr 2023 14:56:50 -0400 Subject: atomic: Add shorthands for explicit atomic operations --- src/atomic.h | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.c | 1 + 2 files changed, 86 insertions(+) create mode 100644 src/atomic.h diff --git a/src/atomic.h b/src/atomic.h new file mode 100644 index 0000000..f1a6bea --- /dev/null +++ b/src/atomic.h @@ -0,0 +1,85 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Shorthand for standard C atomic operations. + */ + +#ifndef BFS_ATOMIC_H +#define BFS_ATOMIC_H + +#include + +/** + * Prettier spelling of _Atomic. + */ +#define atomic _Atomic + +/** + * Shorthand for atomic_load_explicit(). + * + * @param obj + * A pointer to the atomic object. + * @param order + * The memory ordering to use, without the memory_order_ prefix. + * @return + * The loaded value. + */ +#define load(obj, order) \ + atomic_load_explicit(obj, memory_order_##order) + +/** + * Shorthand for atomic_store_explicit(). + */ +#define store(obj, value, order) \ + atomic_store_explicit(obj, value, memory_order_##order) + +/** + * Shorthand for atomic_exchange_explicit(). + */ +#define exchange(obj, value, order) \ + atomic_exchange_explicit(obj, value, memory_order_##order) + +/** + * Shorthand for atomic_compare_exchange_weak_explicit(). + */ +#define compare_exchange_weak(obj, expected, desired, succ, fail) \ + atomic_compare_exchange_weak_explicit(obj, expected, desired, memory_order_##succ, memory_order_##fail) + +/** + * Shorthand for atomic_compare_exchange_strong_explicit(). + */ +#define compare_exchange_strong(obj, expected, desired, succ, fail) \ + atomic_compare_exchange_strong_explicit(obj, expected, desired, memory_order_##succ, memory_order_##fail) + +/** + * Shorthand for atomic_fetch_add_explicit(). + */ +#define fetch_add(obj, arg, order) \ + atomic_fetch_add_explicit(obj, arg, memory_order_##order) + +/** + * Shorthand for atomic_fetch_sub_explicit(). + */ +#define fetch_sub(obj, arg, order) \ + atomic_fetch_sub_explicit(obj, arg, memory_order_##order) + +/** + * Shorthand for atomic_fetch_or_explicit(). + */ +#define fetch_or(obj, arg, order) \ + atomic_fetch_or_explicit(obj, arg, memory_order_##order) + +/** + * Shorthand for atomic_fetch_xor_explicit(). + */ +#define fetch_xor(obj, arg, order) \ + atomic_fetch_xor_explicit(obj, arg, memory_order_##order) + +/** + * Shorthand for atomic_fetch_and_explicit(). + */ +#define fetch_and(obj, arg, order) \ + atomic_fetch_and_explicit(obj, arg, memory_order_##order) + +#endif // BFS_ATOMIC_H diff --git a/src/main.c b/src/main.c index 95be48f..d84f7c2 100644 --- a/src/main.c +++ b/src/main.c @@ -20,6 +20,7 @@ * - bftw.[ch] (an extended version of nftw(3)) * * - Utilities: + * - atomic.h (atomic operations) * - bar.[ch] (a terminal status bar) * - bit.h (bit manipulation) * - bfstd.[ch] (standard library wrappers/polyfills) -- cgit v1.2.3 From 0a11b516c439c645d90583a661db0380d9696617 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 7 Apr 2023 14:57:15 -0400 Subject: fsade: Make syscall support checks thread-safe --- src/fsade.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/fsade.c b/src/fsade.c index ca3a99c..c401426 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "fsade.h" +#include "atomic.h" #include "config.h" #include "bfstd.h" #include "bftw.h" @@ -34,11 +35,10 @@ * emulate something similar if /proc/self/fd is available. */ static const char *fake_at(const struct BFTW *ftwbuf) { - static bool proc_works = true; - static bool proc_checked = false; + static atomic int proc_works = -1; char *path = NULL; - if (!proc_works || ftwbuf->at_fd == AT_FDCWD) { + if (ftwbuf->at_fd == AT_FDCWD || load(&proc_works, relaxed) == 0) { goto fail; } @@ -47,11 +47,12 @@ static const char *fake_at(const struct BFTW *ftwbuf) { goto fail; } - if (!proc_checked) { - proc_checked = true; + if (load(&proc_works, relaxed) < 0) { if (xfaccessat(AT_FDCWD, path, F_OK) != 0) { - proc_works = false; + store(&proc_works, 0, relaxed); goto fail; + } else { + store(&proc_works, 1, relaxed); } } -- cgit v1.2.3 From c1c509759130967fa201636850337599b6ec3c75 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 7 Apr 2023 14:58:08 -0400 Subject: stat: Make syscall support checks thread-safe --- src/stat.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/stat.c b/src/stat.c index 3f70e6c..e3e5aaa 100644 --- a/src/stat.c +++ b/src/stat.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "stat.h" +#include "atomic.h" #include "bfstd.h" #include "config.h" #include "diag.h" @@ -248,14 +249,14 @@ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct b */ static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x_flags, struct bfs_stat *buf) { #if BFS_STATX - static bool has_statx = true; + static atomic bool has_statx = true; - if (has_statx) { + if (load(&has_statx, relaxed)) { int ret = bfs_statx_impl(at_fd, at_path, at_flags | x_flags, buf); // EPERM is commonly returned in a seccomp() sandbox that does // not allow statx() if (ret != 0 && (errno == ENOSYS || errno == EPERM)) { - has_statx = false; + store(&has_statx, false, relaxed); } else { return ret; } @@ -305,12 +306,12 @@ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct b // Check __GNU__ to work around https://lists.gnu.org/archive/html/bug-hurd/2021-12/msg00001.html #if defined(AT_EMPTY_PATH) && !__GNU__ - static bool has_at_ep = true; - if (has_at_ep) { + static atomic bool has_at_ep = true; + if (load(&has_at_ep, relaxed)) { at_flags |= AT_EMPTY_PATH; int ret = bfs_stat_explicit(at_fd, "", at_flags, x_flags, buf); if (ret != 0 && errno == EINVAL) { - has_at_ep = false; + store(&has_at_ep, false, relaxed); } else { return ret; } -- cgit v1.2.3 From 36906834d9e6f8381e71ec3ddb66952c509ad869 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 9 Jun 2023 10:50:05 -0400 Subject: xregex: Initialize Oniguruma thread-safely --- src/xregex.c | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/xregex.c b/src/xregex.c index ce59ff5..89c2e90 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -6,6 +6,7 @@ #include "diag.h" #include "sanity.h" #include +#include #include #include @@ -29,16 +30,14 @@ struct bfs_regex { }; #if BFS_WITH_ONIGURUMA -/** Get (and initialize) the appropriate encoding for the current locale. */ -static int bfs_onig_encoding(OnigEncoding *penc) { - static OnigEncoding enc = NULL; - if (enc) { - *penc = enc; - return ONIG_NORMAL; - } +static int bfs_onig_status; +static OnigEncoding bfs_onig_enc; + +/** pthread_once() callback. */ +static void bfs_onig_once(void) { // Fall back to ASCII by default - enc = ONIG_ENCODING_ASCII; + bfs_onig_enc = ONIG_ENCODING_ASCII; // Oniguruma has no locale support, so try to guess the right encoding // from the current locale. @@ -47,7 +46,7 @@ static int bfs_onig_encoding(OnigEncoding *penc) { #define BFS_MAP_ENCODING(name, value) \ do { \ if (strcmp(charmap, name) == 0) { \ - enc = value; \ + bfs_onig_enc = value; \ } \ } while (0) #define BFS_MAP_ENCODING2(name1, name2, value) \ @@ -97,12 +96,21 @@ static int bfs_onig_encoding(OnigEncoding *penc) { BFS_MAP_ENCODING("GB18030", ONIG_ENCODING_BIG5); } - int ret = onig_initialize(&enc, 1); - if (ret != ONIG_NORMAL) { - enc = NULL; + bfs_onig_status = onig_initialize(&bfs_onig_enc, 1); + if (bfs_onig_status != ONIG_NORMAL) { + bfs_onig_enc = NULL; + } +} + +/** Initialize Oniguruma. */ +static int bfs_onig_initialize(OnigEncoding *enc) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + if (pthread_once(&once, bfs_onig_once) != 0) { + return ONIGERR_FAIL_TO_INITIALIZE; } - *penc = enc; - return ret; + + *enc = bfs_onig_enc; + return bfs_onig_status; } #endif @@ -141,7 +149,7 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ syntax = ONIG_SYNTAX_GREP; break; } - bfs_assert(syntax); + bfs_assert(syntax, "Invalid regex type"); OnigOptionType options = syntax->options; if (flags & BFS_REGEX_ICASE) { @@ -149,7 +157,7 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ } OnigEncoding enc; - regex->err = bfs_onig_encoding(&enc); + regex->err = bfs_onig_initialize(&enc); if (regex->err != ONIG_NORMAL) { return -1; } -- cgit v1.2.3 From f5b6e7710fae76f031b899f24710b82e9c5602de Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 May 2023 09:50:22 -0400 Subject: xtime: Make lazy tzset() call thread-safe --- src/xtime.c | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/xtime.c b/src/xtime.c index 406d694..79dafad 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "xtime.h" +#include "atomic.h" #include "config.h" #include #include @@ -10,15 +11,19 @@ #include #include -/** Whether tzset() has been called. */ -static bool tz_is_set = false; +/** Call tzset() if necessary. */ +static void xtzset(void) { + static atomic bool is_set = false; -int xlocaltime(const time_t *timep, struct tm *result) { - // Should be called before localtime_r() according to POSIX.1-2004 - if (!tz_is_set) { + if (!load(&is_set, relaxed)) { tzset(); - tz_is_set = true; + store(&is_set, true, relaxed); } +} + +int xlocaltime(const time_t *timep, struct tm *result) { + // Should be called before localtime_r() according to POSIX.1-2004 + xtzset(); if (localtime_r(timep, result)) { return 0; @@ -29,10 +34,7 @@ int xlocaltime(const time_t *timep, struct tm *result) { int xgmtime(const time_t *timep, struct tm *result) { // Should be called before gmtime_r() according to POSIX.1-2004 - if (!tz_is_set) { - tzset(); - tz_is_set = true; - } + xtzset(); if (gmtime_r(timep, result)) { return 0; -- cgit v1.2.3 From a69327f7844ca88ad5b6293bd334e5cb351d9591 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 12 Jun 2023 13:00:01 -0400 Subject: ci: Use macOS 13 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ae65157..3aacce1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -43,7 +43,7 @@ jobs: macos: name: macOS - runs-on: macos-latest + runs-on: macos-13 steps: - uses: actions/checkout@v3 -- cgit v1.2.3 From e8df57b5a49a70e2daa5bb6c00b8e0e06c51306a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 6 Apr 2023 14:55:25 -0400 Subject: ioq: Implement an async I/O queue --- Makefile | 1 + src/ioq.c | 284 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ioq.h | 94 ++++++++++++++++++++ src/main.c | 1 + 4 files changed, 380 insertions(+) create mode 100644 src/ioq.c create mode 100644 src/ioq.h diff --git a/Makefile b/Makefile index 20b4d07..8fc68fd 100644 --- a/Makefile +++ b/Makefile @@ -229,6 +229,7 @@ LIBBFS := \ $(OBJ)/src/eval.o \ $(OBJ)/src/exec.o \ $(OBJ)/src/fsade.o \ + $(OBJ)/src/ioq.o \ $(OBJ)/src/mtab.o \ $(OBJ)/src/opt.o \ $(OBJ)/src/parse.o \ diff --git a/src/ioq.c b/src/ioq.c new file mode 100644 index 0000000..e09c2a9 --- /dev/null +++ b/src/ioq.c @@ -0,0 +1,284 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "ioq.h" +#include "dir.h" +#include "list.h" +#include "lock.h" +#include "sanity.h" +#include +#include +#include +#include +#include + +/** + * An I/O queue request. + */ +struct ioq_req { + /** Base file descriptor for openat(). */ + int dfd; + /** Relative path to dfd. */ + const char *path; + + /** Arbitrary user data. */ + void *ptr; +}; + +/** + * An I/O queue command. + */ +struct ioq_cmd { + union { + struct ioq_req req; + struct ioq_res res; + }; + + struct ioq_cmd *next; +}; + +/** + * An MPMC queue of I/O commands. + */ +struct ioqq { + pthread_mutex_t mutex; + pthread_cond_t cond; + + bool stop; + + struct ioq_cmd *head; + struct ioq_cmd **tail; +}; + +static struct ioqq *ioqq_create(void) { + struct ioqq *ioqq = malloc(sizeof(*ioqq)); + if (!ioqq) { + goto fail; + } + + if (mutex_init(&ioqq->mutex, NULL) != 0) { + goto fail_free; + } + + if (cond_init(&ioqq->cond, NULL) != 0) { + goto fail_mutex; + } + + ioqq->stop = false; + SLIST_INIT(ioqq); + return ioqq; + +fail_mutex: + mutex_destroy(&ioqq->mutex); +fail_free: + free(ioqq); +fail: + return NULL; +} + +/** Push a command onto the queue. */ +static void ioqq_push(struct ioqq *ioqq, struct ioq_cmd *cmd) { + mutex_lock(&ioqq->mutex); + SLIST_APPEND(ioqq, cmd); + mutex_unlock(&ioqq->mutex); + cond_signal(&ioqq->cond); +} + +/** Pop a command from a queue. */ +static struct ioq_cmd *ioqq_pop(struct ioqq *ioqq) { + mutex_lock(&ioqq->mutex); + + while (!ioqq->stop && !ioqq->head) { + cond_wait(&ioqq->cond, &ioqq->mutex); + } + + struct ioq_cmd *cmd = SLIST_POP(ioqq); + mutex_unlock(&ioqq->mutex); + return cmd; +} + +/** Pop a command from a queue without blocking. */ +static struct ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { + if (!mutex_trylock(&ioqq->mutex)) { + return NULL; + } + + struct ioq_cmd *cmd = SLIST_POP(ioqq); + mutex_unlock(&ioqq->mutex); + return cmd; +} + +/** Stop a queue, waking up any waiters. */ +static void ioqq_stop(struct ioqq *ioqq) { + mutex_lock(&ioqq->mutex); + ioqq->stop = true; + mutex_unlock(&ioqq->mutex); + cond_broadcast(&ioqq->cond); +} + +static void ioqq_destroy(struct ioqq *ioqq) { + if (ioqq) { + cond_destroy(&ioqq->cond); + mutex_destroy(&ioqq->mutex); + free(ioqq); + } +} + +struct ioq { + /** The depth of the queue. */ + size_t depth; + /** The current size of the queue. */ + size_t size; + + /** Pending I/O requests. */ + struct ioqq *pending; + /** Ready I/O responses. */ + struct ioqq *ready; + + /** The number of background threads. */ + size_t nthreads; + /** The background threads themselves. */ + pthread_t *threads; +}; + +/** Background thread entry point. */ +static void *ioq_work(void *ptr) { + struct ioq *ioq = ptr; + + while (true) { + struct ioq_cmd *cmd = ioqq_pop(ioq->pending); + if (!cmd) { + break; + } + + struct ioq_req req = cmd->req; + sanitize_uninit(cmd); + + struct ioq_res *res = &cmd->res; + res->dir = bfs_opendir(req.dfd, req.path); + res->error = errno; + ioqq_push(ioq->ready, cmd); + } + + return NULL; +} + +struct ioq *ioq_create(size_t depth, size_t threads) { + struct ioq *ioq = malloc(sizeof(*ioq)); + if (!ioq) { + goto fail; + } + + ioq->depth = depth; + ioq->size = 0; + ioq->pending = NULL; + ioq->ready = NULL; + ioq->nthreads = 0; + + ioq->pending = ioqq_create(); + if (!ioq->pending) { + goto fail; + } + + ioq->ready = ioqq_create(); + if (!ioq->ready) { + goto fail; + } + + ioq->threads = malloc(threads * sizeof(ioq->threads[0])); + if (!ioq->threads) { + goto fail; + } + + for (size_t i = 0; i < threads; ++i) { + errno = pthread_create(&ioq->threads[i], NULL, ioq_work, ioq); + if (errno != 0) { + goto fail; + } + ++ioq->nthreads; + } + + return ioq; + + int err; +fail: + err = errno; + ioq_destroy(ioq); + errno = err; + return NULL; +} + +int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { + if (ioq->size >= ioq->depth) { + return -1; + } + + struct ioq_cmd *cmd = malloc(sizeof(*cmd)); + if (!cmd) { + return -1; + } + + struct ioq_req *req = &cmd->req; + req->dfd = dfd; + req->path = path; + req->ptr = ptr; + + ++ioq->size; + ioqq_push(ioq->pending, cmd); + return 0; +} + +struct ioq_res *ioq_pop(struct ioq *ioq) { + if (ioq->size == 0) { + return NULL; + } + + struct ioq_cmd *cmd = ioqq_pop(ioq->ready); + if (!cmd) { + return NULL; + } + + --ioq->size; + return &cmd->res; +} + +struct ioq_res *ioq_trypop(struct ioq *ioq) { + if (ioq->size == 0) { + return NULL; + } + + struct ioq_cmd *cmd = ioqq_trypop(ioq->ready); + if (!cmd) { + return NULL; + } + + --ioq->size; + return &cmd->res; +} + +void ioq_free(struct ioq *ioq, struct ioq_res *res) { + struct ioq_cmd *cmd = (struct ioq_cmd *)res; + free(cmd); +} + +void ioq_destroy(struct ioq *ioq) { + if (!ioq) { + return; + } + + if (ioq->pending) { + ioqq_stop(ioq->pending); + } + + for (size_t i = 0; i < ioq->nthreads; ++i) { + if (pthread_join(ioq->threads[i], NULL) != 0) { + abort(); + } + } + free(ioq->threads); + + ioqq_destroy(ioq->ready); + ioqq_destroy(ioq->pending); + + free(ioq); +} diff --git a/src/ioq.h b/src/ioq.h new file mode 100644 index 0000000..9492034 --- /dev/null +++ b/src/ioq.h @@ -0,0 +1,94 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Asynchronous I/O queues. + */ + +#ifndef BFS_IOQ_H +#define BFS_IOQ_H + +#include + +/** + * An queue of asynchronous I/O operations. + */ +struct ioq; + +/** + * An I/O queue response. + */ +struct ioq_res { + /** The opened directory. */ + struct bfs_dir *dir; + /** The error code, if the operation failed. */ + int error; + + /** Arbitrary user data. */ + void *ptr; +}; + +/** + * Create an I/O queue. + * + * @param depth + * The maximum depth of the queue. + * @param threads + * The maximum number of background threads. + * @return + * The new I/O queue, or NULL on failure. + */ +struct ioq *ioq_create(size_t depth, size_t threads); + +/** + * Asynchronous bfs_opendir(). + * + * @param ioq + * The I/O queue. + * @param dfd + * The base file descriptor. + * @param path + * The path to open, relative to dfd. + * @param ptr + * An arbitrary pointer to associate with the request. + * @return + * 0 on success, or -1 on failure. + */ +int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr); + +/** + * Pop a response from the queue. + * + * @param ioq + * The I/O queue. + * @return + * The next response, or NULL. + */ +struct ioq_res *ioq_pop(struct ioq *ioq); + +/** + * Pop a response from the queue, without blocking. + * + * @param ioq + * The I/O queue. + * @return + * The next response, or NULL. + */ +struct ioq_res *ioq_trypop(struct ioq *ioq); + +/** + * Free a response. + * + * @param ioq + * The I/O queue. + * @param res + * The response to free. + */ +void ioq_free(struct ioq *ioq, struct ioq_res *res); + +/** + * Stop and destroy an I/O queue. + */ +void ioq_destroy(struct ioq *ioq); + +#endif // BFS_IOQ_H diff --git a/src/main.c b/src/main.c index d84f7c2..76dde86 100644 --- a/src/main.c +++ b/src/main.c @@ -31,6 +31,7 @@ * - dir.[ch] (a directory API facade) * - dstring.[ch] (a dynamic string library) * - fsade.[ch] (a facade over non-standard filesystem features) + * - ioq.[ch] (an async I/O queue) * - list.h (linked list macros) * - lock.h (mutexes, condition variables, etc.) * - mtab.[ch] (parses the system's mount table) -- cgit v1.2.3 From 0c10a1e984bc2bed1fe3a7dadbd94a9c24139a91 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 11 Apr 2023 22:06:42 -0400 Subject: dir: Add a flag to bfs_freedir() to force the fd to stay the same --- src/bftw.c | 4 ++-- src/dir.c | 18 +++++++++++++----- src/dir.h | 10 ++++++++-- src/ioq.c | 1 + 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 916a56b..a3e8d9b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -853,13 +853,13 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { if (file->refcount > 1) { // Keep the fd around if any subdirectories exist - file->fd = bfs_freedir(state->dir); + file->fd = bfs_freedir(state->dir, false); } else { - bfs_closedir(state->dir); file->fd = -1; } if (file->fd < 0) { + bfs_closedir(state->dir); bftw_cache_remove(&state->cache, file); } } diff --git a/src/dir.c b/src/dir.c index 01d26db..126f473 100644 --- a/src/dir.c +++ b/src/dir.c @@ -243,13 +243,15 @@ int bfs_closedir(struct bfs_dir *dir) { int ret = xclose(dir->fd); #else int ret = closedir(dir->dir); - bfs_verify(ret == 0 || errno != EBADF); + if (ret != 0) { + bfs_verify(errno != EBADF); + } #endif free(dir); return ret; } -int bfs_freedir(struct bfs_dir *dir) { +int bfs_freedir(struct bfs_dir *dir, bool same_fd) { #if BFS_GETDENTS int ret = dir->fd; free(dir); @@ -257,10 +259,16 @@ int bfs_freedir(struct bfs_dir *dir) { int ret = fdclosedir(dir->dir); free(dir); #else + if (same_fd) { + errno = ENOTSUP; + return -1; + } + int ret = dup_cloexec(dirfd(dir->dir)); - int error = errno; - bfs_closedir(dir); - errno = error; + if (ret >= 0) { + bfs_closedir(dir); + } #endif + return ret; } diff --git a/src/dir.h b/src/dir.h index 01eaaba..e0fe913 100644 --- a/src/dir.h +++ b/src/dir.h @@ -8,6 +8,7 @@ #ifndef BFS_DIR_H #define BFS_DIR_H +#include "config.h" #include /** @@ -103,9 +104,14 @@ int bfs_closedir(struct bfs_dir *dir); * * @param dir * The directory to free. + * @param same_fd + * If true, require that the returned file descriptor is the same one + * that bfs_dirfd() would have returned. Otherwise, it may be a new + * file descriptor for the same directory. * @return - * The file descriptor on success, or -1 on failure. + * On success, a file descriptor for the directory is returned. On + * failure, -1 is returned, and the directory remains open. */ -int bfs_freedir(struct bfs_dir *dir); +int bfs_freedir(struct bfs_dir *dir, bool same_fd); #endif // BFS_DIR_H diff --git a/src/ioq.c b/src/ioq.c index e09c2a9..1b61fb0 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -155,6 +155,7 @@ static void *ioq_work(void *ptr) { sanitize_uninit(cmd); struct ioq_res *res = &cmd->res; + res->ptr = req.ptr; res->dir = bfs_opendir(req.dfd, req.path); res->error = errno; ioqq_push(ioq->ready, cmd); -- cgit v1.2.3 From 0cca5b64e1355af5d2c3d935da4e110982273703 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 30 Mar 2023 12:39:37 -0400 Subject: bftw: Implement open file pinning --- src/bftw.c | 132 ++++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 100 insertions(+), 32 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index a3e8d9b..4a49457 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -51,11 +51,15 @@ struct bftw_file { /** This file's depth in the walk. */ size_t depth; - /** Reference count. */ + /** Reference count (for ->parent). */ size_t refcount; + /** Pin count (for ->fd). */ + size_t pincount; /** An open descriptor to this file, or -1. */ int fd; + /** An open directory for this file, if any. */ + struct bfs_dir *dir; /** This file's type, if known. */ enum bfs_type type; @@ -101,27 +105,58 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { cache->capacity = capacity; } -/** Remove a bftw_file from the cache. */ -static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { +/** Remove a bftw_file from the LRU list. */ +static void bftw_lru_remove(struct bftw_cache *cache, struct bftw_file *file) { if (cache->target == file) { cache->target = file->lru.prev; } LIST_REMOVE(cache, file, lru); +} +/** Remove a bftw_file from the cache. */ +static void bftw_cache_remove(struct bftw_cache *cache, struct bftw_file *file) { + bftw_lru_remove(cache, file); ++cache->capacity; } /** Close a bftw_file. */ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->fd >= 0); + bfs_assert(file->pincount == 0); - if (LIST_ATTACHED(cache, file, lru)) { - bftw_cache_remove(cache, file); + if (file->dir) { + bfs_assert(file->fd == bfs_dirfd(file->dir)); + bfs_closedir(file->dir); + file->dir = NULL; + } else { + xclose(file->fd); } - xclose(file->fd); file->fd = -1; + bftw_cache_remove(cache, file); +} + +/** Free an open directory. */ +static void bftw_file_freedir(struct bftw_cache *cache, struct bftw_file *file) { + if (!file->dir) { + return; + } + + // Try to keep an open fd if any children exist + bool reffed = file->refcount > 1; + // Keep the fd the same if it's pinned + bool pinned = file->pincount > 0; + + if (reffed || pinned) { + int fd = bfs_freedir(file->dir, pinned); + if (fd >= 0) { + file->fd = fd; + file->dir = NULL; + } + } else { + bftw_file_close(cache, file); + } } /** Pop the least recently used directory from the cache. */ @@ -135,6 +170,18 @@ static int bftw_cache_pop(struct bftw_cache *cache) { return 0; } +/** Add a bftw_file to the LRU list. */ +static void bftw_lru_add(struct bftw_cache *cache, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + + LIST_INSERT(cache, cache->target, file, lru); + + // Prefer to keep the root paths open by keeping them at the head of the list + if (file->depth == 0) { + cache->target = file; + } +} + /** Add a bftw_file to the cache. */ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->fd >= 0); @@ -148,14 +195,28 @@ static int bftw_cache_add(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(cache->capacity > 0); --cache->capacity; - LIST_INSERT(cache, cache->target, file, lru); + bftw_lru_add(cache, file); + return 0; +} - // Prefer to keep the root paths open by keeping them at the head of the list - if (file->depth == 0) { - cache->target = file; +/** Pin a cache entry so it won't be closed. */ +static void bftw_cache_pin(struct bftw_cache *cache, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + + if (file->pincount++ == 0) { + bftw_lru_remove(cache, file); } +} - return 0; +/** Unpin a cache entry. */ +static void bftw_cache_unpin(struct bftw_cache *cache, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + bfs_assert(file->pincount > 0); + + if (--file->pincount == 0) { + bftw_lru_add(cache, file); + bftw_file_freedir(cache, file); + } } /** Compute the name offset of a child path. */ @@ -201,7 +262,9 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam file->lru.prev = file->lru.next = NULL; file->refcount = 1; + file->pincount = 0; file->fd = -1; + file->dir = NULL; file->type = BFS_UNKNOWN; file->dev = -1; @@ -234,8 +297,7 @@ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, st int at_fd = AT_FDCWD; if (base) { - // Remove base from the cache temporarily so it stays open - bftw_cache_remove(cache, base); + bftw_cache_pin(cache, base); at_fd = base->fd; } @@ -250,7 +312,7 @@ static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, st } if (base) { - bftw_cache_add(cache, base); + bftw_cache_unpin(cache, base); } if (fd >= 0) { @@ -308,6 +370,19 @@ static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, cons return file->fd; } +/** + * Associate an open directory with a bftw_file. + */ +static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, struct bfs_dir *dir) { + bfs_assert(!file->dir); + file->dir = dir; + + if (file->fd < 0) { + file->fd = bfs_dirfd(dir); + bftw_cache_add(cache, file); + } +} + /** * Open a bftw_file as a directory. * @@ -326,7 +401,11 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_f return NULL; } - return bfs_opendir(fd, NULL); + struct bfs_dir *dir = bfs_opendir(fd, NULL); + if (dir) { + bftw_file_set_dir(cache, file, dir); + } + return dir; } /** Free a bftw_file. */ @@ -798,7 +877,9 @@ static int bftw_opendir(struct bftw_state *state) { } state->dir = bftw_file_opendir(&state->cache, state->file, state->path); - if (!state->dir) { + if (state->dir) { + bftw_cache_pin(&state->cache, state->file); + } else { state->direrror = errno; } return 0; @@ -848,22 +929,9 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; if (state->dir) { - struct bftw_file *file = state->file; - bfs_assert(file && file->fd >= 0); - - if (file->refcount > 1) { - // Keep the fd around if any subdirectories exist - file->fd = bfs_freedir(state->dir, false); - } else { - file->fd = -1; - } - - if (file->fd < 0) { - bfs_closedir(state->dir); - bftw_cache_remove(&state->cache, file); - } + bftw_cache_unpin(&state->cache, state->file); + bftw_file_freedir(&state->cache, state->file); } - state->dir = NULL; state->de = NULL; @@ -876,8 +944,8 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { } else { state->error = state->direrror; } - state->direrror = 0; } + state->direrror = 0; enum bftw_gc_flags visit = BFTW_VISIT_FILE; while (state->file) { -- cgit v1.2.3 From c023cceb3f50d92ed565ea3f085883f86de0f3f0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 9 Jun 2023 16:34:41 -0400 Subject: bftw: Use an I/O queue to open directories Parallelism is controlled by the new -j flag. --- docs/bfs.1 | 8 +++- src/bftw.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/bftw.h | 2 + src/ctx.c | 1 + src/ctx.h | 2 + src/eval.c | 22 +++++++++ src/parse.c | 26 +++++++++- 7 files changed, 206 insertions(+), 9 deletions(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index 53a9831..bc82457 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -171,12 +171,18 @@ consumes too much memory. .TP .I eds Exponential deepening search. -A compromise between breadth- and depth-first search, which searches exponentially increasing depth ranges (e.g 0-1, 1-2, 2-4, 4-8, etc.). +A compromise between breadth- and depth-first search, which searches exponentially increasing depth ranges (e.g. 0-1, 1-2, 2-4, 4-8, etc.). Provides many of the benefits of breadth-first search with depth-first's reduced memory consumption. Typically far faster than .B \-S .IR ids . .RE +.TP +\fB\-j\fIN\fR +Search with +.I N +threads in parallel (default: number of CPUs, up to +.IR 8 ). .SH OPERATORS .TP \fB( \fIexpression \fB)\fR diff --git a/src/bftw.c b/src/bftw.c index 4a49457..e711963 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -22,6 +22,7 @@ #include "diag.h" #include "dir.h" #include "dstring.h" +#include "ioq.h" #include "list.h" #include "mtab.h" #include "stat.h" @@ -58,6 +59,8 @@ struct bftw_file { size_t pincount; /** An open descriptor to this file, or -1. */ int fd; + /** Whether this file has a pending ioq request. */ + bool ioqueued; /** An open directory for this file, if any. */ struct bfs_dir *dir; @@ -264,6 +267,7 @@ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *nam file->refcount = 1; file->pincount = 0; file->fd = -1; + file->ioqueued = false; file->dir = NULL; file->type = BFS_UNKNOWN; @@ -439,6 +443,8 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; + /** The async I/O queue. */ + struct ioq *ioq; /** The queue of directories to read. */ struct bftw_list dirs; /** The queue of files to visit. */ @@ -494,6 +500,25 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg bftw_cache_init(&state->cache, args->nopenfd); + size_t qdepth = args->nopenfd - 1; + if (qdepth > 1024) { + qdepth = 1024; + } + + size_t nthreads = args->nthreads; + if (nthreads > qdepth) { + nthreads = qdepth; + } + + state->ioq = NULL; + if (nthreads > 0) { + state->ioq = ioq_create(qdepth, nthreads); + if (!state->ioq) { + dstrfree(state->path); + return -1; + } + } + SLIST_INIT(&state->dirs); SLIST_INIT(&state->files); SLIST_INIT(&state->batch); @@ -846,15 +871,122 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam } } +/** Push a directory onto the queue. */ +static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->type == BFS_DIR); + + struct bftw_cache *cache = &state->cache; + + if (!state->ioq) { + goto append; + } + + int dfd = AT_FDCWD; + if (file->parent) { + dfd = file->parent->fd; + if (dfd < 0) { + goto append; + } + bftw_cache_pin(cache, file->parent); + } + + if (cache->capacity == 0) { + if (bftw_cache_pop(cache) != 0) { + goto unpin; + } + } + --cache->capacity; + + if (ioq_opendir(state->ioq, dfd, file->name, file) != 0) { + ++cache->capacity; + goto unpin; + } + + file->ioqueued = true; + + if (state->flags & BFTW_SORT) { + goto append; + } else { + return; + } + +unpin: + if (file->parent) { + bftw_cache_unpin(cache, file->parent); + } +append: + SLIST_APPEND(&state->dirs, file); +} + +/** Pop a response from the I/O queue. */ +static int bftw_ioq_pop(struct bftw_state *state, bool block) { + if (!state->ioq) { + return -1; + } + + struct ioq_res *res; + if (block) { + res = ioq_pop(state->ioq); + } else { + res = ioq_trypop(state->ioq); + } + + if (!res) { + return -1; + } + + struct bftw_cache *cache = &state->cache; + ++cache->capacity; + + struct bftw_file *file = res->ptr; + file->ioqueued = false; + + if (file->parent) { + bftw_cache_unpin(cache, file->parent); + } + + if (res->dir) { + bftw_file_set_dir(cache, file, res->dir); + } + + ioq_free(state->ioq, res); + + if (!(state->flags & BFTW_SORT)) { + SLIST_PREPEND(&state->dirs, file); + } + + return 0; +} + /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - if (state->files.head && state->strategy == BFTW_BFS) { + bool have_dirs = state->dirs.head; + bool have_files = state->files.head; + bool have_room = state->cache.capacity > 0; + + if (state->flags & BFTW_SORT) { + // Keep strict breadth-first order when sorting + if (state->strategy != BFTW_DFS && have_files) { + return false; + } + } else { + // Block if we have no other files/dirs to visit, or no room in the cache + bool block = !(have_dirs || have_files) || !have_room; + bftw_ioq_pop(state, block); + } + + struct bftw_file *dir = state->file = SLIST_POP(&state->dirs); + if (!dir) { return false; } - return (state->file = SLIST_POP(&state->dirs)); + while (dir->ioqueued) { + bftw_ioq_pop(state, true); + } + + return true; } /** Pop a file to visit from the queue. */ @@ -872,16 +1004,22 @@ static int bftw_opendir(struct bftw_state *state) { state->direrror = 0; - if (bftw_build_path(state, NULL) != 0) { - return -1; + struct bftw_file *file = state->file; + if (file->dir) { + state->dir = file->dir; + } else { + if (bftw_build_path(state, NULL) != 0) { + return -1; + } + state->dir = bftw_file_opendir(&state->cache, file, state->path); } - state->dir = bftw_file_opendir(&state->cache, state->file, state->path); if (state->dir) { - bftw_cache_pin(&state->cache, state->file); + bftw_cache_pin(&state->cache, file); } else { state->direrror = errno; } + return 0; } @@ -988,6 +1126,8 @@ static int bftw_state_destroy(struct bftw_state *state) { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); + ioq_destroy(state->ioq); + bftw_cache_destroy(&state->cache); errno = state->error; @@ -1100,7 +1240,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { } bftw_save_ftwbuf(file, &state->ftwbuf); - SLIST_APPEND(&state->dirs, file); + bftw_push_dir(state, file); return 0; case BFTW_PRUNE: diff --git a/src/bftw.h b/src/bftw.h index 77697ed..940532c 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -186,6 +186,8 @@ struct bftw_args { void *ptr; /** The maximum number of file descriptors to keep open. */ int nopenfd; + /** The maximum number of threads to use. */ + int nthreads; /** Flags that control bftw() behaviour. */ enum bftw_flags flags; /** The search strategy to use. */ diff --git a/src/ctx.c b/src/ctx.c index c4b2fb2..e8ce0e8 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -56,6 +56,7 @@ struct bfs_ctx *bfs_ctx_new(void) { ctx->maxdepth = INT_MAX; ctx->flags = BFTW_RECOVER; ctx->strategy = BFTW_BFS; + ctx->threads = 0; ctx->optlevel = 3; ctx->debug = 0; ctx->ignore_races = false; diff --git a/src/ctx.h b/src/ctx.h index 0dc9f08..2b8e8cb 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -68,6 +68,8 @@ struct bfs_ctx { /** bftw() search strategy. */ enum bftw_strategy strategy; + /** Threads (-j). */ + int threads; /** Optimization level (-O). */ int optlevel; /** Debugging flags (-D). */ diff --git a/src/eval.c b/src/eval.c index e2c19a9..b9bce6c 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1505,6 +1505,19 @@ static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) { return ret; } +static int infer_nproc(void) { + long nproc = sysconf(_SC_NPROCESSORS_ONLN); + + if (nproc < 0) { + nproc = 0; + } else if (nproc > 8) { + // Not much speedup after 8 threads + nproc = 8; + } + + return nproc; +} + /** * Dump the bftw() flags for -D search. */ @@ -1593,12 +1606,20 @@ int bfs_eval(const struct bfs_ctx *ctx) { int fdlimit = raise_fdlimit(ctx); fdlimit = infer_fdlimit(ctx, fdlimit); + int nthreads; + if (ctx->threads > 0) { + nthreads = ctx->threads - 1; + } else { + nthreads = infer_nproc(); + } + struct bftw_args bftw_args = { .paths = ctx->paths, .npaths = darray_length(ctx->paths), .callback = eval_callback, .ptr = &args, .nopenfd = fdlimit, + .nthreads = nthreads, .flags = ctx->flags, .strategy = ctx->strategy, .mtab = bfs_ctx_mtab(ctx), @@ -1618,6 +1639,7 @@ int bfs_eval(const struct bfs_ctx *ctx) { fprintf(stderr, "\t.callback = eval_callback,\n"); fprintf(stderr, "\t.ptr = &args,\n"); fprintf(stderr, "\t.nopenfd = %d,\n", bftw_args.nopenfd); + fprintf(stderr, "\t.nthreads = %d,\n", bftw_args.nthreads); fprintf(stderr, "\t.flags = "); dump_bftw_flags(bftw_args.flags); fprintf(stderr, ",\n\t.strategy = %s,\n", dump_bftw_strategy(bftw_args.strategy)); diff --git a/src/parse.c b/src/parse.c index 59a1e7d..96def14 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1636,6 +1636,23 @@ static struct bfs_expr *parse_inum(struct parser_state *state, int arg1, int arg return parse_test_icmp(state, eval_inum); } +/** + * Parse -j. + */ +static struct bfs_expr *parse_jobs(struct parser_state *state, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_flag(state); + if (!expr) { + return NULL; + } + + if (!parse_int(state, expr->argv, expr->argv[0] + 2, &state->ctx->threads, IF_INT | IF_UNSIGNED)) { + bfs_expr_free(expr); + return NULL; + } + + return expr; +} + /** * Parse -links N. */ @@ -2753,7 +2770,9 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg cfprintf(cout, " Enable optimization level ${bld}N${rs} (default: ${bld}3${rs})\n"); cfprintf(cout, " ${cyn}-S${rs} ${bld}bfs${rs}|${bld}dfs${rs}|${bld}ids${rs}|${bld}eds${rs}\n"); cfprintf(cout, " Use ${bld}b${rs}readth-${bld}f${rs}irst/${bld}d${rs}epth-${bld}f${rs}irst/${bld}i${rs}terative/${bld}e${rs}xponential ${bld}d${rs}eepening ${bld}s${rs}earch\n"); - cfprintf(cout, " (default: ${cyn}-S${rs} ${bld}bfs${rs})\n\n"); + cfprintf(cout, " (default: ${cyn}-S${rs} ${bld}bfs${rs})\n"); + cfprintf(cout, " ${cyn}-j${bld}N${rs}\n"); + cfprintf(cout, " Search with ${bld}N${rs} threads in parallel (default: number of CPUs, up to ${bld}8${rs})\n\n"); cfprintf(cout, "${bld}Operators:${rs}\n\n"); @@ -3060,6 +3079,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}, {"-links", T_TEST, parse_links}, {"-lname", T_TEST, parse_lname, false}, {"-ls", T_ACTION, parse_ls}, @@ -3552,6 +3572,10 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { cfprintf(cerr, " ${cyn}-O${bld}%d${rs}", ctx->optlevel); } + if (ctx->threads > 0) { + cfprintf(cerr, " ${cyn}-j${bld}%d${rs}", ctx->threads); + } + cfprintf(cerr, " ${cyn}-S${rs} ${bld}%s${rs}", bftw_strategy_name(ctx->strategy)); enum debug_flags debug = ctx->debug; -- cgit v1.2.3 From 9dca9f2bcd06f921312762e0b07254f5f8f51fc2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 23 May 2023 14:34:07 -0400 Subject: eval: Pre-allocate the highest fd This avoids the need to grow the fd table during the search, significantly reducing kernel contention when opening directories in parallel. --- src/eval.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/eval.c b/src/eval.c index b9bce6c..f16821e 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1471,6 +1471,23 @@ static int raise_fdlimit(const struct bfs_ctx *ctx) { return ret; } +/** Preallocate the fd table in the kernel. */ +static void reserve_fds(int limit) { + // Kernels typically implement the fd table as a dynamic array. + // Growing the array can be expensive, especially if files are being + // opened in parallel. We can work around this by allocating the + // highest possible fd, forcing the kernel to grow the table upfront. + +#ifdef F_DUPFD_CLOEXEC + int fd = fcntl(STDIN_FILENO, F_DUPFD_CLOEXEC, limit - 1); +#else + int fd = fcntl(STDIN_FILENO, F_DUPFD, limit - 1); +#endif + if (fd >= 0) { + xclose(fd); + } +} + /** Infer the number of file descriptors available to bftw(). */ static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) { // 3 for std{in,out,err} @@ -1604,6 +1621,7 @@ int bfs_eval(const struct bfs_ctx *ctx) { } int fdlimit = raise_fdlimit(ctx); + reserve_fds(fdlimit); fdlimit = infer_fdlimit(ctx, fdlimit); int nthreads; -- cgit v1.2.3 From 6719107e95a41b9ef241bdaa272eed505865710c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Mar 2023 13:59:48 -0400 Subject: dir: New bfs_polldir() function for directory readahead --- src/dir.c | 88 +++++++++++++++++++++++++++++++++++++++++++++++---------------- src/dir.h | 10 ++++++++ src/ioq.c | 4 +++ 3 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/dir.c b/src/dir.c index 126f473..b9fd74d 100644 --- a/src/dir.c +++ b/src/dir.c @@ -107,7 +107,10 @@ struct bfs_dir { // sys_dirent buf[]; #else DIR *dir; + struct dirent *de; #endif + + bool eof; }; #if BFS_GETDENTS @@ -152,8 +155,10 @@ struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { free(dir); return NULL; } + dir->de = NULL; #endif + dir->eof = false; return dir; } @@ -165,38 +170,52 @@ int bfs_dirfd(const struct bfs_dir *dir) { #endif } -/** Convert de->d_type to a bfs_type, if it exists. */ -static enum bfs_type bfs_d_type(const sys_dirent *de) { -#ifdef DTTOIF - return bfs_mode_to_type(DTTOIF(de->d_type)); -#else - return BFS_UNKNOWN; -#endif -} - -/** Read a single directory entry. */ -static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { +int bfs_polldir(struct bfs_dir *dir) { #if BFS_GETDENTS + if (dir->pos < dir->size) { + return 1; + } else if (dir->eof) { + return 0; + } + char *buf = (char *)(dir + 1); + ssize_t size = bfs_getdents(dir->fd, buf, BUF_SIZE); + if (size == 0) { + dir->eof = true; + return 0; + } else if (size < 0) { + return -1; + } - if (dir->pos >= dir->size) { - ssize_t ret = bfs_getdents(dir->fd, buf, BUF_SIZE); - if (ret <= 0) { - return ret; + dir->pos = 0; + dir->size = size; + + // Like read(), getdents() doesn't indicate EOF until another call returns zero. + // Check that eagerly here to hopefully avoid a syscall in the last bfs_readdir(). + size_t rest = BUF_SIZE - size; + if (rest >= sizeof(sys_dirent)) { + size = bfs_getdents(dir->fd, buf + size, rest); + if (size > 0) { + dir->size += size; + } else if (size == 0) { + dir->eof = true; } - dir->pos = 0; - dir->size = ret; } - *de = (void *)(buf + dir->pos); - dir->pos += (*de)->d_reclen; return 1; -#else +#else // !BFS_GETDENTS + if (dir->de) { + return 1; + } else if (dir->eof) { + return 0; + } + errno = 0; - *de = readdir(dir->dir); - if (*de) { + dir->de = readdir(dir->dir); + if (dir->de) { return 1; } else if (errno == 0) { + dir->eof = true; return 0; } else { return -1; @@ -204,6 +223,22 @@ static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { #endif } +/** Read a single directory entry. */ +static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { + int ret = bfs_polldir(dir); + if (ret > 0) { +#if BFS_GETDENTS + char *buf = (char *)(dir + 1); + *de = (const sys_dirent *)(buf + dir->pos); + dir->pos += (*de)->d_reclen; +#else + *de = dir->de; + dir->de = NULL; +#endif + } + return ret; +} + /** Skip ".", "..", and deleted/empty dirents. */ static bool skip_dirent(const sys_dirent *de) { #if __FreeBSD__ @@ -217,6 +252,15 @@ static bool skip_dirent(const sys_dirent *de) { return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); } +/** Convert de->d_type to a bfs_type, if it exists. */ +static enum bfs_type bfs_d_type(const sys_dirent *de) { +#ifdef DTTOIF + return bfs_mode_to_type(DTTOIF(de->d_type)); +#else + return BFS_UNKNOWN; +#endif +} + int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) { while (true) { const sys_dirent *sysde; diff --git a/src/dir.h b/src/dir.h index e0fe913..6fe7ae2 100644 --- a/src/dir.h +++ b/src/dir.h @@ -79,6 +79,16 @@ struct bfs_dir *bfs_opendir(int at_fd, const char *at_path); */ int bfs_dirfd(const struct bfs_dir *dir); +/** + * Performs any I/O necessary for the next bfs_readdir() call. + * + * @param dir + * The directory to poll. + * @return + * 1 on success, 0 on EOF, or -1 on failure. + */ +int bfs_polldir(struct bfs_dir *dir); + /** * Read a directory entry. * diff --git a/src/ioq.c b/src/ioq.c index 1b61fb0..74d2e09 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -158,6 +158,10 @@ static void *ioq_work(void *ptr) { res->ptr = req.ptr; res->dir = bfs_opendir(req.dfd, req.path); res->error = errno; + if (res->dir) { + bfs_polldir(res->dir); + } + ioqq_push(ioq->ready, cmd); } -- cgit v1.2.3 From 60fce9f4ef5e5c4603271d167142262e07ab46e7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 13 Jun 2023 17:49:38 -0400 Subject: lock: Add missing include --- src/lock.h | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lock.h b/src/lock.h index 2ca7876..2b5f951 100644 --- a/src/lock.h +++ b/src/lock.h @@ -11,6 +11,7 @@ #include "diag.h" #include #include +#include #define lock_verify(expr, cond) \ bfs_verify((errno = (expr), (cond)), "%s: %s", #expr, strerror(errno)) -- cgit v1.2.3 From 2f207fcd8b20aeb804c6a3d2f399d5c350a0405d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Jun 2023 10:26:24 -0400 Subject: sanity: Make sanitize_[un]init() always a void expression --- src/sanity.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sanity.h b/src/sanity.h index 5696036..f2cf0a4 100644 --- a/src/sanity.h +++ b/src/sanity.h @@ -73,8 +73,8 @@ #define sanitize_uninit(...) SANITIZE_CALL(__msan_allocated_memory, __VA_ARGS__) #else -# define sanitize_init(...) -# define sanitize_uninit(...) +# define sanitize_init(...) ((void)0) +# define sanitize_uninit(...) ((void)0) #endif /** -- cgit v1.2.3 From 83150980687d644bb0c4faad1c297d1b5b9018b6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Jun 2023 12:52:20 -0400 Subject: parse: Don't default to depth-first when argv[0] is "find" --- src/parse.c | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/parse.c b/src/parse.c index 96def14..e5981d4 100644 --- a/src/parse.c +++ b/src/parse.c @@ -3736,12 +3736,6 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .now = ctx->now, }; - const char *cmd = state.command + xbaseoff(state.command); - if (strcmp(cmd, "find") == 0) { - // Operate depth-first when invoked as "find" - ctx->strategy = BFTW_DFS; - } - ctx->exclude = bfs_expr_new(eval_false, 1, &fake_false_arg); if (!ctx->exclude) { goto fail; -- cgit v1.2.3 From 29719ace5192ff5c2d81c29d1947e32d7889f62b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Jun 2023 10:35:24 -0400 Subject: config: Add macros for false/true sharing sizes --- src/config.h | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/config.h b/src/config.h index dbe3c74..73348ac 100644 --- a/src/config.h +++ b/src/config.h @@ -191,6 +191,29 @@ static inline size_t flex_sizeof_impl(size_t align, size_t min, size_t offset, s return ret; } +/** + * False sharing/destructive interference/largest cache line size. + */ +#ifdef __GCC_DESTRUCTIVE_SIZE +# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE +#else +# define FALSE_SHARING_SIZE 64 +#endif + +/** + * True sharing/constructive interference/smallest cache line size. + */ +#ifdef __GCC_CONSTRUCTIVE_SIZE +# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE +#else +# define TRUE_SHARING_SIZE 64 +#endif + +/** + * Alignment specifier that avoids false sharing. + */ +#define cache_align alignas(FALSE_SHARING_SIZE) + // Wrappers for attributes /** -- cgit v1.2.3 From d83553b4a2e942f410d56d92bef34def9424a494 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 6 Apr 2023 14:52:50 -0400 Subject: bfstd: Add an aligned_alloc()/posix_memalign() wrapper --- src/bfstd.c | 14 ++++++++++++++ src/bfstd.h | 12 ++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/bfstd.c b/src/bfstd.c index 37f5276..4e1175f 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "bfstd.h" +#include "bit.h" #include "config.h" #include "diag.h" #include "xregex.h" @@ -142,6 +143,19 @@ char *xgetdelim(FILE *file, char delim) { } } +void *xmemalign(size_t align, size_t size) { + bfs_assert(has_single_bit(align)); + bfs_assert((size & (align - 1)) == 0); + +#if __APPLE__ + void *ptr = NULL; + errno = posix_memalign(&ptr, align, size); + return ptr; +#else + return aligned_alloc(align, size); +#endif +} + /** 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 e4fd1f1..ee4cf16 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -105,6 +105,18 @@ char *xgetdelim(FILE *file, char delim); // #include +/** + * Portable version of aligned_alloc()/posix_memalign(). + * + * @param align + * The allocation's alignment. + * @param size + * The allocation's size. + * @return + * The allocation, or NULL on failure. + */ +void *xmemalign(size_t align, size_t size); + /** * Process a yes/no prompt. * -- cgit v1.2.3 From b3aa51d83650bf9f2d264e53110ca248453bb2f0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 15 Jun 2023 12:52:47 -0400 Subject: ioq: Use a circular buffer --- src/ioq.c | 279 ++++++++++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 201 insertions(+), 78 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 74d2e09..d3df7e0 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -2,14 +2,17 @@ // SPDX-License-Identifier: 0BSD #include "ioq.h" +#include "atomic.h" +#include "bfstd.h" +#include "bit.h" +#include "config.h" +#include "diag.h" #include "dir.h" -#include "list.h" #include "lock.h" #include "sanity.h" #include #include #include -#include #include /** @@ -28,102 +31,225 @@ struct ioq_req { /** * An I/O queue command. */ -struct ioq_cmd { - union { - struct ioq_req req; - struct ioq_res res; - }; - - struct ioq_cmd *next; +union ioq_cmd { + struct ioq_req req; + struct ioq_res res; }; /** - * An MPMC queue of I/O commands. + * A monitor for an I/O queue slot. */ -struct ioqq { - pthread_mutex_t mutex; - pthread_cond_t cond; - - bool stop; - - struct ioq_cmd *head; - struct ioq_cmd **tail; +struct ioq_monitor { + cache_align pthread_mutex_t mutex; + pthread_cond_t full; + pthread_cond_t empty; }; -static struct ioqq *ioqq_create(void) { - struct ioqq *ioqq = malloc(sizeof(*ioqq)); - if (!ioqq) { +/** Initialize an ioq_monitor. */ +static int ioq_monitor_init(struct ioq_monitor *monitor) { + if (mutex_init(&monitor->mutex, NULL) != 0) { goto fail; } - if (mutex_init(&ioqq->mutex, NULL) != 0) { - goto fail_free; + if (cond_init(&monitor->full, NULL) != 0) { + goto fail_mutex; } - if (cond_init(&ioqq->cond, NULL) != 0) { - goto fail_mutex; + if (cond_init(&monitor->empty, NULL) != 0) { + goto fail_full; } - ioqq->stop = false; - SLIST_INIT(ioqq); - return ioqq; + return 0; +fail_full: + cond_destroy(&monitor->full); fail_mutex: - mutex_destroy(&ioqq->mutex); -fail_free: - free(ioqq); + mutex_destroy(&monitor->mutex); fail: - return NULL; + return -1; } -/** Push a command onto the queue. */ -static void ioqq_push(struct ioqq *ioqq, struct ioq_cmd *cmd) { - mutex_lock(&ioqq->mutex); - SLIST_APPEND(ioqq, cmd); - mutex_unlock(&ioqq->mutex); - cond_signal(&ioqq->cond); +/** Destroy an ioq_monitor. */ +static void ioq_monitor_destroy(struct ioq_monitor *monitor) { + cond_destroy(&monitor->empty); + cond_destroy(&monitor->full); + mutex_destroy(&monitor->mutex); } -/** Pop a command from a queue. */ -static struct ioq_cmd *ioqq_pop(struct ioqq *ioqq) { - mutex_lock(&ioqq->mutex); +/** + * A slot in an I/O queue. + */ +struct ioq_slot { + struct ioq_monitor *monitor; + union ioq_cmd *cmd; +}; + +/** Initialize an ioq_slot. */ +static void ioq_slot_init(struct ioq_slot *slot, struct ioq_monitor *monitor) { + slot->monitor = monitor; + slot->cmd = NULL; +} - while (!ioqq->stop && !ioqq->head) { - cond_wait(&ioqq->cond, &ioqq->mutex); +/** Push a command into a slot. */ +static void ioq_slot_push(struct ioq_slot *slot, union ioq_cmd *cmd) { + struct ioq_monitor *monitor = slot->monitor; + + mutex_lock(&monitor->mutex); + while (slot->cmd) { + cond_wait(&monitor->empty, &monitor->mutex); } + slot->cmd = cmd; + mutex_unlock(&monitor->mutex); - struct ioq_cmd *cmd = SLIST_POP(ioqq); - mutex_unlock(&ioqq->mutex); - return cmd; + cond_broadcast(&monitor->full); } -/** Pop a command from a queue without blocking. */ -static struct ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { - if (!mutex_trylock(&ioqq->mutex)) { - return NULL; +/** Pop a command from a slot. */ +static union ioq_cmd *ioq_slot_pop(struct ioq_slot *slot) { + struct ioq_monitor *monitor = slot->monitor; + + mutex_lock(&monitor->mutex); + while (!slot->cmd) { + cond_wait(&monitor->full, &monitor->mutex); } + union ioq_cmd *ret = slot->cmd; + slot->cmd = NULL; + mutex_unlock(&monitor->mutex); - struct ioq_cmd *cmd = SLIST_POP(ioqq); - mutex_unlock(&ioqq->mutex); - return cmd; + cond_broadcast(&monitor->empty); + + return ret; } -/** Stop a queue, waking up any waiters. */ -static void ioqq_stop(struct ioqq *ioqq) { - mutex_lock(&ioqq->mutex); - ioqq->stop = true; - mutex_unlock(&ioqq->mutex); - cond_broadcast(&ioqq->cond); +/** Pop a command from a slot, if one exists. */ +static union ioq_cmd *ioq_slot_trypop(struct ioq_slot *slot) { + struct ioq_monitor *monitor = slot->monitor; + + if (!mutex_trylock(&monitor->mutex)) { + return NULL; + } + + union ioq_cmd *ret = slot->cmd; + slot->cmd = NULL; + + mutex_unlock(&monitor->mutex); + + if (ret) { + cond_broadcast(&monitor->empty); + } + return ret; } +/** + * An MPMC queue of I/O commands. + */ +struct ioqq { + /** Circular buffer index mask. */ + size_t mask; + + /** Number of monitors. */ + size_t nmonitors; + /** Array of monitors used by the slots. */ + struct ioq_monitor *monitors; + + /** Index of next writer. */ + cache_align atomic size_t head; + /** Index of next reader. */ + cache_align atomic size_t tail; + + /** The circular buffer itself. */ + cache_align struct ioq_slot slots[]; +}; + +// If we assign slots sequentially, threads will likely be operating on +// consecutive slots. If these slots are in the same cache line, that will +// result in false sharing. We can mitigate this by assigning slots with a +// stride larger than a cache line e.g. 0, 9, 18, ..., 1, 10, 19, ... +// As long as the stride is relatively prime to circular buffer length, we'll +// still use every available slot. Since the length is a power of two, that +// means the stride must be odd. + +#define IOQ_STRIDE ((FALSE_SHARING_SIZE / sizeof(struct ioq_slot)) | 1) +bfs_static_assert(IOQ_STRIDE % 2 == 1); + +/** Destroy an I/O command queue. */ static void ioqq_destroy(struct ioqq *ioqq) { - if (ioqq) { - cond_destroy(&ioqq->cond); - mutex_destroy(&ioqq->mutex); - free(ioqq); + for (size_t i = 0; i < ioqq->nmonitors; ++i) { + ioq_monitor_destroy(&ioqq->monitors[i]); + } + free(ioqq->monitors); + free(ioqq); +} + +/** Create an I/O command queue. */ +static struct ioqq *ioqq_create(size_t size) { + // Circular buffer size must be a power of two + size = bit_ceil(size); + + struct ioqq *ioqq = xmemalign(alignof(struct ioqq), flex_sizeof(struct ioqq, slots, size)); + if (!ioqq) { + return NULL; + } + + // Use a pool of monitors + size_t nmonitors = size < 64 ? size : 64; + ioqq->nmonitors = 0; + ioqq->monitors = xmemalign(alignof(struct ioq_monitor), nmonitors * sizeof(struct ioq_monitor)); + if (!ioqq->monitors) { + ioqq_destroy(ioqq); + return NULL; + } + + for (size_t i = 0; i < nmonitors; ++i) { + if (ioq_monitor_init(&ioqq->monitors[i]) != 0) { + ioqq_destroy(ioqq); + return NULL; + } + ++ioqq->nmonitors; + } + + ioqq->mask = size - 1; + + atomic_init(&ioqq->head, 0); + atomic_init(&ioqq->tail, 0); + + for (size_t i = 0; i < size; ++i) { + ioq_slot_init(&ioqq->slots[i], &ioqq->monitors[i % nmonitors]); + } + + return ioqq; +} + +/** Push a command onto the queue. */ +static void ioqq_push(struct ioqq *ioqq, union ioq_cmd *cmd) { + size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); + ioq_slot_push(&ioqq->slots[i & ioqq->mask], cmd); +} + +/** Pop a command from a queue. */ +static union ioq_cmd *ioqq_pop(struct ioqq *ioqq) { + size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); + return ioq_slot_pop(&ioqq->slots[i & ioqq->mask]); +} + +/** Pop a command from a queue if one is available. */ +static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { + size_t i = load(&ioqq->tail, relaxed); + union ioq_cmd *cmd = ioq_slot_trypop(&ioqq->slots[i & ioqq->mask]); + if (cmd) { +#ifdef NDEBUG + store(&ioqq->tail, i + IOQ_STRIDE, relaxed); +#else + size_t j = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); + bfs_assert(j == i, "ioqq_trypop() only supports a single consumer"); +#endif } + return cmd; } +/** Sentinel stop command. */ +static union ioq_cmd IOQ_STOP; + struct ioq { /** The depth of the queue. */ size_t depth; @@ -146,8 +272,8 @@ static void *ioq_work(void *ptr) { struct ioq *ioq = ptr; while (true) { - struct ioq_cmd *cmd = ioqq_pop(ioq->pending); - if (!cmd) { + union ioq_cmd *cmd = ioqq_pop(ioq->pending); + if (cmd == &IOQ_STOP) { break; } @@ -176,16 +302,17 @@ struct ioq *ioq_create(size_t depth, size_t threads) { ioq->depth = depth; ioq->size = 0; + ioq->pending = NULL; ioq->ready = NULL; ioq->nthreads = 0; - ioq->pending = ioqq_create(); + ioq->pending = ioqq_create(depth); if (!ioq->pending) { goto fail; } - ioq->ready = ioqq_create(); + ioq->ready = ioqq_create(depth); if (!ioq->ready) { goto fail; } @@ -218,7 +345,7 @@ int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { return -1; } - struct ioq_cmd *cmd = malloc(sizeof(*cmd)); + union ioq_cmd *cmd = malloc(sizeof(*cmd)); if (!cmd) { return -1; } @@ -228,8 +355,8 @@ int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { req->path = path; req->ptr = ptr; - ++ioq->size; ioqq_push(ioq->pending, cmd); + ++ioq->size; return 0; } @@ -238,11 +365,7 @@ struct ioq_res *ioq_pop(struct ioq *ioq) { return NULL; } - struct ioq_cmd *cmd = ioqq_pop(ioq->ready); - if (!cmd) { - return NULL; - } - + union ioq_cmd *cmd = ioqq_pop(ioq->ready); --ioq->size; return &cmd->res; } @@ -252,7 +375,7 @@ struct ioq_res *ioq_trypop(struct ioq *ioq) { return NULL; } - struct ioq_cmd *cmd = ioqq_trypop(ioq->ready); + union ioq_cmd *cmd = ioqq_trypop(ioq->ready); if (!cmd) { return NULL; } @@ -262,7 +385,7 @@ struct ioq_res *ioq_trypop(struct ioq *ioq) { } void ioq_free(struct ioq *ioq, struct ioq_res *res) { - struct ioq_cmd *cmd = (struct ioq_cmd *)res; + union ioq_cmd *cmd = (union ioq_cmd *)res; free(cmd); } @@ -271,8 +394,8 @@ void ioq_destroy(struct ioq *ioq) { return; } - if (ioq->pending) { - ioqq_stop(ioq->pending); + for (size_t i = 0; i < ioq->nthreads; ++i) { + ioqq_push(ioq->pending, &IOQ_STOP); } for (size_t i = 0; i < ioq->nthreads; ++i) { -- cgit v1.2.3 From 425956c9022fda1e98544c4b2d495e91dfde4b4f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 15 Jun 2023 14:22:00 -0400 Subject: ioq: Implement a non-blocking fast path --- src/ioq.c | 205 ++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 107 insertions(+), 98 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index d3df7e0..5550c91 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -41,114 +41,38 @@ union ioq_cmd { */ struct ioq_monitor { cache_align pthread_mutex_t mutex; - pthread_cond_t full; - pthread_cond_t empty; + pthread_cond_t cond; }; /** Initialize an ioq_monitor. */ static int ioq_monitor_init(struct ioq_monitor *monitor) { if (mutex_init(&monitor->mutex, NULL) != 0) { - goto fail; - } - - if (cond_init(&monitor->full, NULL) != 0) { - goto fail_mutex; + return -1; } - if (cond_init(&monitor->empty, NULL) != 0) { - goto fail_full; + if (cond_init(&monitor->cond, NULL) != 0) { + mutex_destroy(&monitor->mutex); + return -1; } return 0; - -fail_full: - cond_destroy(&monitor->full); -fail_mutex: - mutex_destroy(&monitor->mutex); -fail: - return -1; } /** Destroy an ioq_monitor. */ static void ioq_monitor_destroy(struct ioq_monitor *monitor) { - cond_destroy(&monitor->empty); - cond_destroy(&monitor->full); + cond_destroy(&monitor->cond); mutex_destroy(&monitor->mutex); } -/** - * A slot in an I/O queue. - */ -struct ioq_slot { - struct ioq_monitor *monitor; - union ioq_cmd *cmd; -}; - -/** Initialize an ioq_slot. */ -static void ioq_slot_init(struct ioq_slot *slot, struct ioq_monitor *monitor) { - slot->monitor = monitor; - slot->cmd = NULL; -} - -/** Push a command into a slot. */ -static void ioq_slot_push(struct ioq_slot *slot, union ioq_cmd *cmd) { - struct ioq_monitor *monitor = slot->monitor; - - mutex_lock(&monitor->mutex); - while (slot->cmd) { - cond_wait(&monitor->empty, &monitor->mutex); - } - slot->cmd = cmd; - mutex_unlock(&monitor->mutex); - - cond_broadcast(&monitor->full); -} - -/** Pop a command from a slot. */ -static union ioq_cmd *ioq_slot_pop(struct ioq_slot *slot) { - struct ioq_monitor *monitor = slot->monitor; - - mutex_lock(&monitor->mutex); - while (!slot->cmd) { - cond_wait(&monitor->full, &monitor->mutex); - } - union ioq_cmd *ret = slot->cmd; - slot->cmd = NULL; - mutex_unlock(&monitor->mutex); - - cond_broadcast(&monitor->empty); - - return ret; -} - -/** Pop a command from a slot, if one exists. */ -static union ioq_cmd *ioq_slot_trypop(struct ioq_slot *slot) { - struct ioq_monitor *monitor = slot->monitor; - - if (!mutex_trylock(&monitor->mutex)) { - return NULL; - } - - union ioq_cmd *ret = slot->cmd; - slot->cmd = NULL; - - mutex_unlock(&monitor->mutex); - - if (ret) { - cond_broadcast(&monitor->empty); - } - return ret; -} - /** * An MPMC queue of I/O commands. */ struct ioqq { /** Circular buffer index mask. */ - size_t mask; + size_t slot_mask; - /** Number of monitors. */ - size_t nmonitors; + /** Monitor index mask. */ + size_t monitor_mask; /** Array of monitors used by the slots. */ struct ioq_monitor *monitors; @@ -158,7 +82,7 @@ struct ioqq { cache_align atomic size_t tail; /** The circular buffer itself. */ - cache_align struct ioq_slot slots[]; + cache_align atomic uintptr_t slots[]; }; // If we assign slots sequentially, threads will likely be operating on @@ -169,12 +93,16 @@ struct ioqq { // still use every available slot. Since the length is a power of two, that // means the stride must be odd. -#define IOQ_STRIDE ((FALSE_SHARING_SIZE / sizeof(struct ioq_slot)) | 1) +#define IOQ_STRIDE ((FALSE_SHARING_SIZE / sizeof(atomic uintptr_t)) | 1) bfs_static_assert(IOQ_STRIDE % 2 == 1); +/** Slot flag bit to indicate waiters. */ +#define IOQ_BLOCKED ((uintptr_t)1) +bfs_static_assert(alignof(union ioq_cmd) > 1); + /** Destroy an I/O command queue. */ static void ioqq_destroy(struct ioqq *ioqq) { - for (size_t i = 0; i < ioqq->nmonitors; ++i) { + for (size_t i = 0; i < ioqq->monitor_mask + 1; ++i) { ioq_monitor_destroy(&ioqq->monitors[i]); } free(ioqq->monitors); @@ -191,9 +119,11 @@ static struct ioqq *ioqq_create(size_t size) { return NULL; } + ioqq->slot_mask = size - 1; + ioqq->monitor_mask = -1; + // Use a pool of monitors size_t nmonitors = size < 64 ? size : 64; - ioqq->nmonitors = 0; ioqq->monitors = xmemalign(alignof(struct ioq_monitor), nmonitors * sizeof(struct ioq_monitor)); if (!ioqq->monitors) { ioqq_destroy(ioqq); @@ -205,38 +135,116 @@ static struct ioqq *ioqq_create(size_t size) { ioqq_destroy(ioqq); return NULL; } - ++ioqq->nmonitors; + ++ioqq->monitor_mask; } - ioqq->mask = size - 1; - atomic_init(&ioqq->head, 0); atomic_init(&ioqq->tail, 0); for (size_t i = 0; i < size; ++i) { - ioq_slot_init(&ioqq->slots[i], &ioqq->monitors[i % nmonitors]); + atomic_init(&ioqq->slots[i], 0); } return ioqq; } +/** Atomically wait for a slot to change. */ +static uintptr_t ioqq_wait(struct ioqq *ioqq, size_t i, uintptr_t value) { + atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; + + struct ioq_monitor *monitor = &ioqq->monitors[i & ioqq->monitor_mask]; + mutex_lock(&monitor->mutex); + + uintptr_t ret; + while ((ret = load(slot, relaxed)) == value) { + // To avoid missed wakeups, it is important that + // cond_broadcast() is not called right here + cond_wait(&monitor->cond, &monitor->mutex); + } + + mutex_unlock(&monitor->mutex); + return ret; +} + +/** Wake up any threads waiting on a slot. */ +static void ioqq_wake(struct ioqq *ioqq, size_t i) { + struct ioq_monitor *monitor = &ioqq->monitors[i & ioqq->monitor_mask]; + + // The following implementation would clearly avoid the missed wakeup + // issue mentioned above in ioqq_wait(): + // + // mutex_lock(&monitor->mutex); + // cond_broadcast(&monitor->cond); + // mutex_unlock(&monitor->mutex); + // + // As a minor optimization, we move the broadcast outside of the lock. + // This optimization is correct, even though it leads to a seemingly- + // useless empty critical section. + + mutex_lock(&monitor->mutex); + mutex_unlock(&monitor->mutex); + cond_broadcast(&monitor->cond); +} + /** Push a command onto the queue. */ static void ioqq_push(struct ioqq *ioqq, union ioq_cmd *cmd) { size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); - ioq_slot_push(&ioqq->slots[i & ioqq->mask], cmd); + atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; + + uintptr_t addr = (uintptr_t)cmd; + bfs_assert(!(addr & IOQ_BLOCKED)); + + uintptr_t prev = load(slot, relaxed); + do { + while (prev & ~IOQ_BLOCKED) { + prev = fetch_or(slot, IOQ_BLOCKED, relaxed); + if (prev & ~IOQ_BLOCKED) { + prev = ioqq_wait(ioqq, i, prev | IOQ_BLOCKED); + } + } + } while (!compare_exchange_weak(slot, &prev, addr, release, relaxed)); + + if (prev & IOQ_BLOCKED) { + ioqq_wake(ioqq, i); + } } /** Pop a command from a queue. */ static union ioq_cmd *ioqq_pop(struct ioqq *ioqq) { size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); - return ioq_slot_pop(&ioqq->slots[i & ioqq->mask]); + atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; + + uintptr_t prev = load(slot, relaxed); + do { + while (!(prev & ~IOQ_BLOCKED)) { + prev = fetch_or(slot, IOQ_BLOCKED, relaxed); + if (!(prev & ~IOQ_BLOCKED)) { + prev = ioqq_wait(ioqq, i, IOQ_BLOCKED); + } + } + } while (!compare_exchange_weak(slot, &prev, 0, acquire, relaxed)); + + if (prev & IOQ_BLOCKED) { + ioqq_wake(ioqq, i); + } + prev &= ~IOQ_BLOCKED; + + return (union ioq_cmd *)prev; } /** Pop a command from a queue if one is available. */ static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { size_t i = load(&ioqq->tail, relaxed); - union ioq_cmd *cmd = ioq_slot_trypop(&ioqq->slots[i & ioqq->mask]); - if (cmd) { + atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; + + uintptr_t prev = exchange(slot, 0, acquire); + + if (prev & IOQ_BLOCKED) { + ioqq_wake(ioqq, i); + } + prev &= ~IOQ_BLOCKED; + + if (prev) { #ifdef NDEBUG store(&ioqq->tail, i + IOQ_STRIDE, relaxed); #else @@ -244,7 +252,8 @@ static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { bfs_assert(j == i, "ioqq_trypop() only supports a single consumer"); #endif } - return cmd; + + return (union ioq_cmd *)prev; } /** Sentinel stop command. */ -- cgit v1.2.3 From a5550d478234efbb89d1c23ae3234bed626a47f6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Jun 2023 10:21:53 -0400 Subject: opt: Wait until purity is computed to update facts_when_impure Since we moved purity out of the parser, side-effect detection has been unnecessarily pessimistic due to this bug. The fix restores warnings in cases like $ bfs -false bfs: warning: This command won't do anything. Fixes: 693b5f60dc9787d9237920cc0c87fe0e010194ee --- src/opt.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/opt.c b/src/opt.c index 4699af4..14de081 100644 --- a/src/opt.c +++ b/src/opt.c @@ -1225,10 +1225,6 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct return ret; } - if (!bfs_expr_is_parent(expr) && !expr->pure) { - facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); - } - expr = optimize_expr_lookup(state, expr); if (!expr) { return NULL; @@ -1247,6 +1243,8 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct expr->ephemeral_fds = lhs->ephemeral_fds; } } + } else if (!expr->pure) { + facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); } if (expr->always_true) { -- cgit v1.2.3 From d3f4340a460a023656151f220f5923ec8ccf9894 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Jun 2023 13:44:06 -0400 Subject: bfstd: New wordesc() function to shell-escape strings --- src/bfstd.c | 46 ++++++++++++++++++++++++++++++++++++++++++++++ src/bfstd.h | 13 +++++++++++++ 2 files changed, 59 insertions(+) diff --git a/src/bfstd.c b/src/bfstd.c index 4e1175f..cfff426 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -544,3 +544,49 @@ size_t xstrwidth(const char *str) { return ret; } + +char *wordesc(const char *str) { + size_t len = strlen(str); + + if (strcspn(str, "|&;<>()$`\\\"' \t\n*?[#˜=%") == len) { + // Whole string is safe + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 + if (len > 0) { + return strdup(str); + } else { + return strdup("\"\""); + } + } else if (strcspn(str, "`$\\\"") == len) { + // Safe to double-quote the whole string + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 + char *ret = malloc(len + 3); + if (!ret) { + return NULL; + } + + char *cur = stpcpy(ret, "\""); + cur = stpcpy(cur, str); + cur = stpcpy(cur, "\""); + return ret; + } + + // Every ' is replaced with '\'', so at most a 3x growth + char *ret = malloc(3 * len + 3); + if (!ret) { + return NULL; + } + + char *cur = stpcpy(ret, "'"); + while (*str) { + size_t chunk = strcspn(str, "'"); + cur = stpncpy(cur, str, chunk); + str += chunk; + if (*str) { + cur = stpcpy(cur, "'\\''"); + ++str; + } + } + cur = stpcpy(cur, "'"); + + return ret; +} diff --git a/src/bfstd.h b/src/bfstd.h index ee4cf16..750847e 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -277,4 +277,17 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * */ size_t xstrwidth(const char *str); +// #include + +/** + * Escape a string as a single shell word. + * + * @param str + * The string to escape. + * @return + * A string that a shell would evaluate to str, dynamically allocated, + * or NULL on failure. + */ +char *wordesc(const char *str); + #endif // BFS_BFSTD_H -- cgit v1.2.3 From 2eeee3e0d0d3aeca5e6487cc2431c1f65e35a6d8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Jun 2023 13:44:21 -0400 Subject: color: New %pq formatter for shell-escaped strings --- src/color.c | 18 ++++++++++++++++++ src/color.h | 1 + 2 files changed, 19 insertions(+) diff --git a/src/color.c b/src/color.c index 87575c9..1edd8b5 100644 --- a/src/color.c +++ b/src/color.c @@ -858,6 +858,18 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { return ret; } +/** Print an shell-escaped string. */ +static int print_wordesc(CFILE *cfile, const char *str) { + char *esc = wordesc(str); + if (!esc) { + return -1; + } + + int ret = dstrcat(&cfile->buffer, esc); + free(esc); + return ret; +} + /** Format some colored output to the buffer. */ BFS_FORMATTER(2, 3) static int cbuff(CFILE *cfile, const char *format, ...); @@ -994,6 +1006,12 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { case 'p': switch (*++i) { + case 'q': + if (print_wordesc(cfile, va_arg(args, const char *)) != 0) { + return -1; + } + break; + case 'F': if (print_name(cfile, va_arg(args, const struct BFTW *)) != 0) { return -1; diff --git a/src/color.h b/src/color.h index 3db2b07..aca6a34 100644 --- a/src/color.h +++ b/src/color.h @@ -84,6 +84,7 @@ int cfclose(CFILE *cfile); * %s: A string * %zu: A size_t * %m: strerror(errno) + * %pq: A shell-escaped string, like bash's printf %q * %pF: A colored file name, from a const struct BFTW * argument * %pP: A colored file path, from a const struct BFTW * argument * %pL: A colored link target, from a const struct BFTW * argument -- cgit v1.2.3 From 6a7211d673473c542aad676bec8089c37b71476d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Jun 2023 13:44:33 -0400 Subject: diag: Shell-escape arguments for argv warnings/errors --- src/diag.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/diag.c b/src/diag.c index 04e3678..9e6b7ae 100644 --- a/src/diag.c +++ b/src/diag.c @@ -5,10 +5,12 @@ #include "bfstd.h" #include "ctx.h" #include "color.h" +#include "dstring.h" #include "expr.h" #include #include #include +#include noreturn void bfs_abortf(const char *format, ...) { va_list args; @@ -149,6 +151,17 @@ static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool *args, bool warn bfs_error_prefix(ctx); } + char *argv[ctx->argc]; + for (size_t i = 0; i < ctx->argc; ++i) { + argv[i] = wordesc(ctx->argv[i]); + if (!argv[i]) { + for (size_t j = 0; j < i; ++j) { + free(argv[j]); + } + return; + } + } + size_t max_argc = 0; for (size_t i = 0; i < ctx->argc; ++i) { if (i > 0) { @@ -157,9 +170,9 @@ static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool *args, bool warn if (args[i]) { max_argc = i + 1; - cfprintf(ctx->cerr, "${bld}%s${rs}", ctx->argv[i]); + cfprintf(ctx->cerr, "${bld}%s${rs}", argv[i]); } else { - cfprintf(ctx->cerr, "%s", ctx->argv[i]); + cfprintf(ctx->cerr, "%s", argv[i]); } } @@ -188,7 +201,7 @@ static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool *args, bool warn } } - size_t len = xstrwidth(ctx->argv[i]); + size_t len = xstrwidth(argv[i]); for (size_t j = 0; j < len; ++j) { if (args[i]) { cfprintf(ctx->cerr, "~"); @@ -203,6 +216,10 @@ static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool *args, bool warn } cfprintf(ctx->cerr, "\n"); + + for (size_t i = 0; i < ctx->argc; ++i) { + free(argv[i]); + } } void bfs_argv_error(const struct bfs_ctx *ctx, const bool *args) { -- cgit v1.2.3 From 33f85610b4174f937e1b61caee15fd4d80e4881c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Jun 2023 13:45:10 -0400 Subject: parse: Shell-escape arguments in diagnostics --- src/parse.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/parse.c b/src/parse.c index e5981d4..5c55076 100644 --- a/src/parse.c +++ b/src/parse.c @@ -646,19 +646,19 @@ static const char *parse_int(const struct parser_state *state, char **arg, const bad: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "${bld}%s${rs} is not a valid integer.\n", str); + parse_argv_error(state, arg, 1, "${bld}%pq${rs} is not a valid integer.\n", str); } return NULL; negative: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "Negative integer ${bld}%s${rs} is not allowed here.\n", str); + parse_argv_error(state, arg, 1, "Negative integer ${bld}%pq${rs} is not allowed here.\n", str); } return NULL; range: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "${bld}%s${rs} is too large an integer.\n", str); + parse_argv_error(state, arg, 1, "${bld}%pq${rs} is too large an integer.\n", str); } return NULL; } @@ -1823,7 +1823,7 @@ static int parse_reftime(const struct parser_state *state, struct bfs_expr *expr static struct bfs_expr *parse_newerxy(struct parser_state *state, int arg1, int arg2) { const char *arg = state->argv[0]; if (strlen(arg) != 8) { - parse_error(state, "Expected ${blu}-newer${bld}XY${rs}; found ${blu}-newer${bld}%s${rs}.\n", arg + 6); + parse_error(state, "Expected ${blu}-newer${bld}XY${rs}; found ${blu}-newer${bld}%pq${rs}.\n", arg + 6); return NULL; } @@ -2495,7 +2495,7 @@ static struct bfs_expr *parse_size(struct parser_state *state, int arg1, int arg return expr; bad_unit: - parse_expr_error(state, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%s${rs}.\n", unit); + parse_expr_error(state, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%pq${rs}.\n", unit); fail: bfs_expr_free(expr); return NULL; @@ -3600,7 +3600,7 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { if (c == '-' || c == '(' || c == ')' || c == '!' || c == ',') { cfprintf(cerr, " ${cyn}-f${rs}"); } - cfprintf(cerr, " ${mag}%s${rs}", path); + cfprintf(cerr, " ${mag}%pq${rs}", path); } if (ctx->cout->colors) { -- cgit v1.2.3 From f8ba689c096302e3946db1c35c2a8e5dd8907390 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 20 Jun 2023 14:08:35 -0400 Subject: build: Turn off ASLR for all sanitizers on FreeBSD Newer LLVM versions will reject every sanitizer runtime if ASLR is enabled. Link: https://reviews.llvm.org/D66582 Link: https://reviews.freebsd.org/D33933 --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8fc68fd..fb28e29 100644 --- a/Makefile +++ b/Makefile @@ -197,7 +197,7 @@ all: bfs tests $(BIN)/%: @$(MKDIR) $(@D) +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ -ifeq ($(OS) $(TSAN),FreeBSD tsan) +ifeq ($(OS) $(SANITIZE),FreeBSD y) elfctl -e +noaslr $@ endif -- cgit v1.2.3 From cf2682b4012c397dec79c4998713dfe12744b1a1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 20 Jun 2023 13:25:47 -0400 Subject: ci: Set CC=clang16 on FreeBSD Workaround for https://github.com/llvm/llvm-project/issues/58740 --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3aacce1..0d15028 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,4 +85,4 @@ jobs: run: | spurion=$(tailscale ip -6 spurion) rsync -rl --delete . "[$spurion]:bfs" - ssh "$spurion" 'gmake -C bfs -j$(sysctl -n hw.ncpu) distcheck' + ssh "$spurion" 'gmake -C bfs -j$(sysctl -n hw.ncpu) distcheck CC=clang16' -- cgit v1.2.3 From e062158e0a855cddcd3838fef415a7531929686b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 19 Jun 2023 16:56:56 -0400 Subject: sanity: Suppress unused variable warnings with sanitizers off --- src/sanity.h | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sanity.h b/src/sanity.h index f2cf0a4..29b3519 100644 --- a/src/sanity.h +++ b/src/sanity.h @@ -73,10 +73,15 @@ #define sanitize_uninit(...) SANITIZE_CALL(__msan_allocated_memory, __VA_ARGS__) #else -# define sanitize_init(...) ((void)0) -# define sanitize_uninit(...) ((void)0) +# define sanitize_init(...) SANITIZE_CALL(sanitize_ignore, __VA_ARGS__) +# define sanitize_uninit(...) SANITIZE_CALL(sanitize_ignore, __VA_ARGS__) #endif +/** + * Squelch unused variable warnings when not sanitizing. + */ +#define sanitize_ignore(ptr, size) ((void)(ptr), (void)(size)) + /** * Initialize a variable, unless sanitizers would detect uninitialized uses. */ -- cgit v1.2.3 From 9ceb2b27577f1be3f30edb40a45117066fc78c51 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 19 Jun 2023 12:08:10 -0400 Subject: bfstd: New xmemdup() function --- src/bfstd.c | 8 ++++++++ src/bfstd.h | 12 ++++++++++++ src/parse.c | 7 ++----- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index cfff426..856c76c 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -243,6 +243,14 @@ static char type_char(mode_t mode) { return '?'; } +void *xmemdup(const void *src, size_t size) { + void *ret = malloc(size); + if (ret) { + memcpy(ret, src, size); + } + return ret; +} + void xstrmode(mode_t mode, char str[11]) { strcpy(str, "----------"); diff --git a/src/bfstd.h b/src/bfstd.h index 750847e..6f2e21e 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -126,6 +126,18 @@ int ynprompt(void); // #include +/** + * Allocate a copy of a region of memory. + * + * @param src + * The memory region to copy. + * @param size + * The size of the memory region. + * @return + * A copy of the region, allocated with malloc(), or NULL on failure. + */ +void *xmemdup(const void *src, size_t size); + /** * Format a mode like ls -l (e.g. -rw-r--r--). * diff --git a/src/parse.c b/src/parse.c index 5c55076..64e08cd 100644 --- a/src/parse.c +++ b/src/parse.c @@ -3666,14 +3666,11 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } ctx->argc = argc; - ctx->argv = malloc((argc + 1)*sizeof(*ctx->argv)); + ctx->argv = xmemdup(argv, sizeof_array(char *, argc + 1)); if (!ctx->argv) { - perror("malloc()"); + perror("xmemdup()"); goto fail; } - for (int i = 0; i <= argc; ++i) { - ctx->argv[i] = argv[i]; - } enum use_color use_color = COLOR_AUTO; if (getenv("NO_COLOR")) { -- cgit v1.2.3 From 90ded13e589b0089167ef25ca3d26be599dfec9b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 19 Jun 2023 12:11:36 -0400 Subject: alloc: New header for memory allocation utilities --- Makefile | 3 +- src/alloc.c | 50 ++++++++++++++++++++ src/alloc.h | 149 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/bfstd.c | 13 ----- src/bfstd.h | 12 ----- src/bftw.c | 5 +- src/color.c | 5 +- src/config.h | 50 -------------------- src/ctx.c | 31 ++---------- src/dstring.c | 3 +- src/exec.c | 26 ++++------ src/ioq.c | 26 ++++------ src/ioq.h | 4 +- src/main.c | 1 + src/mtab.c | 5 +- src/parse.c | 28 ++--------- src/pwcache.c | 5 +- src/trie.c | 7 ++- src/xregex.c | 3 +- src/xspawn.c | 3 +- tests/alloc.c | 24 ++++++++++ tests/bfstd.c | 13 +---- 22 files changed, 271 insertions(+), 195 deletions(-) create mode 100644 src/alloc.c create mode 100644 src/alloc.h create mode 100644 tests/alloc.c diff --git a/Makefile b/Makefile index fb28e29..d38f581 100644 --- a/Makefile +++ b/Makefile @@ -217,6 +217,7 @@ $(OBJ)/FLAGS: $(OBJ)/FLAGS.new # All object files except the entry point LIBBFS := \ + $(OBJ)/src/alloc.o \ $(OBJ)/src/bar.o \ $(OBJ)/src/bfstd.o \ $(OBJ)/src/bftw.o \ @@ -246,7 +247,7 @@ LIBBFS := \ $(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) # Standalone unit tests -UNITS := bfstd bit trie xtimegm +UNITS := alloc bfstd bit trie xtimegm UNIT_TESTS := $(UNITS:%=$(BIN)/tests/%) UNIT_CHECKS := $(UNITS:%=check-%) diff --git a/src/alloc.c b/src/alloc.c new file mode 100644 index 0000000..0003108 --- /dev/null +++ b/src/alloc.c @@ -0,0 +1,50 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "alloc.h" +#include "bit.h" +#include "diag.h" +#include +#include +#include + +/** Portable aligned_alloc()/posix_memalign(). */ +static void *xmemalign(size_t align, size_t size) { + bfs_assert(has_single_bit(align)); + bfs_assert(align >= sizeof(void *)); + bfs_assert((size & (align - 1)) == 0); + +#if __APPLE__ + void *ptr = NULL; + errno = posix_memalign(&ptr, align, size); + return ptr; +#else + return aligned_alloc(align, size); +#endif +} + +void *alloc(size_t align, size_t size) { + bfs_assert(has_single_bit(align)); + bfs_assert((size & (align - 1)) == 0); + + if (align <= alignof(max_align_t)) { + return malloc(size); + } else { + return xmemalign(align, size); + } +} + +void *zalloc(size_t align, size_t size) { + bfs_assert(has_single_bit(align)); + bfs_assert((size & (align - 1)) == 0); + + if (align <= alignof(max_align_t)) { + return calloc(1, size); + } + + void *ret = xmemalign(align, size); + if (ret) { + memset(ret, 0, size); + } + return ret; +} diff --git a/src/alloc.h b/src/alloc.h new file mode 100644 index 0000000..899a4ec --- /dev/null +++ b/src/alloc.h @@ -0,0 +1,149 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Memory allocation. + */ + +#ifndef BFS_ALLOC_H +#define BFS_ALLOC_H + +#include "config.h" +#include + +/** Round down to a multiple of an alignment. */ +static inline size_t align_floor(size_t align, size_t size) { + return size & ~(align - 1); +} + +/** Round up to a multiple of an alignment. */ +static inline size_t align_ceil(size_t align, size_t size) { + return align_floor(align, size + align - 1); +} + +/** + * Saturating array size. + * + * @param align + * Array element alignment. + * @param size + * Array element size. + * @param count + * Array element count. + * @return + * size * count, saturating to the maximum aligned value on overflow. + */ +static inline size_t array_size(size_t align, size_t size, size_t count) { + size_t ret = size * count; + return ret / size == count ? ret : ~(align - 1); +} + +/** Saturating array sizeof. */ +#define sizeof_array(type, count) \ + array_size(alignof(type), sizeof(type), count) + +/** Size of a struct/union field. */ +#define sizeof_member(type, member) \ + sizeof(((type *)NULL)->member) + +/** + * Saturating flexible struct size. + * + * @param align + * Struct alignment. + * @param min + * Minimum struct size. + * @param offset + * Flexible array member offset. + * @param size + * Flexible array element size. + * @param count + * Flexible array element count. + * @return + * The size of the struct with count flexible array elements. Saturates + * to the maximum aligned value on overflow. + */ +static inline size_t flex_size(size_t align, size_t min, size_t offset, size_t size, size_t count) { + size_t ret = size * count; + size_t overflow = ret / size != count; + + size_t extra = offset + align - 1; + ret += extra; + overflow |= ret < extra; + ret |= -overflow; + ret = align_floor(align, ret); + + // Make sure flex_sizeof(type, member, 0) >= sizeof(type), even if the + // type has more padding than necessary for alignment + if (min > align_ceil(align, offset)) { + ret = ret < min ? min : ret; + } + + return ret; +} + +/** + * Computes the size of a flexible struct. + * + * @param type + * The type of the struct containing the flexible array. + * @param member + * The name of the flexible array member. + * @param count + * The length of the flexible array. + * @return + * The size of the struct with count flexible array elements. Saturates + * to the maximum aligned value on overflow. + */ +#define sizeof_flex(type, member, count) \ + flex_size(alignof(type), sizeof(type), offsetof(type, member), sizeof_member(type, member[0]), count) + +/** + * General memory allocator. + * + * @param align + * The required alignment. + * @param size + * The size of the allocation. + * @return + * The allocated memory, or NULL on failure. + */ +void *alloc(size_t align, size_t size); + +/** + * Zero-initialized memory allocator. + * + * @param align + * The required alignment. + * @param size + * The size of the allocation. + * @return + * The allocated memory, or NULL on failure. + */ +void *zalloc(size_t align, size_t size); + +/** Allocate memory for the given type. */ +#define ALLOC(type) \ + (type *)alloc(alignof(type), sizeof(type)) + +/** Allocate zeroed memory for the given type. */ +#define ZALLOC(type) \ + (type *)zalloc(alignof(type), sizeof(type)) + +/** Allocate memory for an array. */ +#define ALLOC_ARRAY(type, count) \ + (type *)alloc(alignof(type), sizeof_array(type, count)); + +/** Allocate zeroed memory for an array. */ +#define ZALLOC_ARRAY(type, count) \ + (type *)zalloc(alignof(type), sizeof_array(type, count)); + +/** Allocate memory for a flexible struct. */ +#define ALLOC_FLEX(type, member, count) \ + (type *)alloc(alignof(type), sizeof_flex(type, member, count)) + +/** Allocate zeroed memory for a flexible struct. */ +#define ZALLOC_FLEX(type, member, count) \ + (type *)zalloc(alignof(type), sizeof_flex(type, member, count)) + +#endif // BFS_ALLOC_H diff --git a/src/bfstd.c b/src/bfstd.c index 856c76c..0e8ba5f 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -143,19 +143,6 @@ char *xgetdelim(FILE *file, char delim) { } } -void *xmemalign(size_t align, size_t size) { - bfs_assert(has_single_bit(align)); - bfs_assert((size & (align - 1)) == 0); - -#if __APPLE__ - void *ptr = NULL; - errno = posix_memalign(&ptr, align, size); - return ptr; -#else - return aligned_alloc(align, size); -#endif -} - /** 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 6f2e21e..cafe28f 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -105,18 +105,6 @@ char *xgetdelim(FILE *file, char delim); // #include -/** - * Portable version of aligned_alloc()/posix_memalign(). - * - * @param align - * The allocation's alignment. - * @param size - * The allocation's size. - * @return - * The allocation, or NULL on failure. - */ -void *xmemalign(size_t align, size_t size); - /** * Process a yes/no prompt. * diff --git a/src/bftw.c b/src/bftw.c index e711963..7ab14c7 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -17,6 +17,7 @@ */ #include "bftw.h" +#include "alloc.h" #include "bfstd.h" #include "config.h" #include "diag.h" @@ -241,9 +242,7 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { /** Create a new bftw_file. */ static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); - size_t size = flex_sizeof(struct bftw_file, name, namelen + 1); - - struct bftw_file *file = malloc(size); + struct bftw_file *file = ALLOC_FLEX(struct bftw_file, name, namelen + 1); if (!file) { return NULL; } diff --git a/src/color.c b/src/color.c index 1edd8b5..b54ad53 100644 --- a/src/color.c +++ b/src/color.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "color.h" +#include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "config.h" @@ -404,7 +405,7 @@ static void parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) { } struct colors *parse_colors(void) { - struct colors *colors = malloc(sizeof(struct colors)); + struct colors *colors = ALLOC(struct colors); if (!colors) { return NULL; } @@ -497,7 +498,7 @@ void free_colors(struct colors *colors) { } CFILE *cfwrap(FILE *file, const struct colors *colors, bool close) { - CFILE *cfile = malloc(sizeof(*cfile)); + CFILE *cfile = ALLOC(CFILE); if (!cfile) { return NULL; } diff --git a/src/config.h b/src/config.h index 73348ac..1671a0d 100644 --- a/src/config.h +++ b/src/config.h @@ -141,56 +141,6 @@ */ #define countof(array) (sizeof(array) / sizeof(0[array])) -/** - * Round down to a multiple of an alignment. - */ -static inline size_t align_floor(size_t align, size_t size) { - return size & ~(align - 1); -} - -/** - * Round up to a multiple of an alignment. - */ -static inline size_t align_ceil(size_t align, size_t size) { - return align_floor(align, size + align - 1); -} - -/** - * Computes the size of a struct containing a flexible array member of the given - * length. - * - * @param type - * The type of the struct containing the flexible array. - * @param member - * The name of the flexible array member. - * @param count - * The length of the flexible array. - */ -#define flex_sizeof(type, member, count) \ - flex_sizeof_impl(alignof(type), sizeof(type), offsetof(type, member), sizeof(((type *)NULL)->member[0]), count) - -static inline size_t flex_sizeof_impl(size_t align, size_t min, size_t offset, size_t size, size_t count) { - size_t ret = size * count; - size_t overflow = ret / size != count; - - ret += offset; - overflow |= ret < offset; - - size_t mask = align - 1; - ret += mask; - overflow |= ret < mask; - ret |= -overflow; - ret &= ~mask; - - // Make sure flex_sizeof(type, member, 0) >= sizeof(type), even if the - // type has more padding than necessary for alignment - if (min > align_ceil(align, offset) && ret < min) { - ret = min; - } - - return ret; -} - /** * False sharing/destructive interference/largest cache line size. */ diff --git a/src/ctx.c b/src/ctx.c index e8ce0e8..a940bed 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "ctx.h" +#include "alloc.h" #include "color.h" #include "darray.h" #include "diag.h" @@ -42,43 +43,17 @@ const char *debug_flag_name(enum debug_flags flag) { } struct bfs_ctx *bfs_ctx_new(void) { - struct bfs_ctx *ctx = malloc(sizeof(*ctx)); + struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx); if (!ctx) { return NULL; } - ctx->argv = NULL; - ctx->paths = NULL; - ctx->expr = NULL; - ctx->exclude = NULL; - - ctx->mindepth = 0; ctx->maxdepth = INT_MAX; ctx->flags = BFTW_RECOVER; ctx->strategy = BFTW_BFS; - ctx->threads = 0; ctx->optlevel = 3; - ctx->debug = 0; - ctx->ignore_races = false; - ctx->posixly_correct = false; - ctx->status = false; - ctx->unique = false; - ctx->warn = false; - ctx->xargs_safe = false; - - ctx->colors = NULL; - ctx->colors_error = 0; - ctx->cout = NULL; - ctx->cerr = NULL; - - ctx->users = NULL; - ctx->groups = NULL; - - ctx->mtab = NULL; - ctx->mtab_error = 0; trie_init(&ctx->files); - ctx->nfiles = 0; struct rlimit rl; if (getrlimit(RLIMIT_NOFILE, &rl) != 0) { @@ -155,7 +130,7 @@ CFILE *bfs_ctx_dedup(struct bfs_ctx *ctx, CFILE *cfile, const char *path) { return ctx_file->cfile; } - leaf->value = ctx_file = malloc(sizeof(*ctx_file)); + leaf->value = ctx_file = ALLOC(struct bfs_ctx_file); if (!ctx_file) { trie_remove(&ctx->files, leaf); return NULL; diff --git a/src/dstring.c b/src/dstring.c index 2c9869d..7ca74d0 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "dstring.h" +#include "alloc.h" #include "diag.h" #include #include @@ -24,7 +25,7 @@ static struct dstring *dstrheader(const char *dstr) { /** Get the correct size for a dstring with the given capacity. */ static size_t dstrsize(size_t capacity) { - return flex_sizeof(struct dstring, data, capacity + 1); + return sizeof_flex(struct dstring, data, capacity + 1); } /** Allocate a dstring with the given contents. */ diff --git a/src/exec.c b/src/exec.c index 5912ad6..ea7f897 100644 --- a/src/exec.c +++ b/src/exec.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "exec.h" +#include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "ctx.h" @@ -124,26 +125,16 @@ static void bfs_exec_parse_error(const struct bfs_ctx *ctx, const struct bfs_exe } struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs_exec_flags flags) { - struct bfs_exec *execbuf = malloc(sizeof(*execbuf)); + struct bfs_exec *execbuf = ZALLOC(struct bfs_exec); if (!execbuf) { - bfs_perror(ctx, "malloc()"); + bfs_perror(ctx, "zalloc()"); goto fail; } execbuf->flags = flags; execbuf->ctx = ctx; execbuf->tmpl_argv = argv + 1; - execbuf->tmpl_argc = 0; - execbuf->argv = NULL; - execbuf->argc = 0; - execbuf->argv_cap = 0; - execbuf->arg_size = 0; - execbuf->arg_max = 0; - execbuf->arg_min = 0; execbuf->wd_fd = -1; - execbuf->wd_path = NULL; - execbuf->wd_len = 0; - execbuf->ret = 0; while (true) { const char *arg = execbuf->tmpl_argv[execbuf->tmpl_argc]; @@ -176,9 +167,9 @@ struct bfs_exec *bfs_exec_parse(const struct bfs_ctx *ctx, char **argv, enum bfs } execbuf->argv_cap = execbuf->tmpl_argc + 1; - execbuf->argv = malloc(execbuf->argv_cap*sizeof(*execbuf->argv)); + execbuf->argv = ALLOC_ARRAY(char *, execbuf->argv_cap); if (!execbuf->argv) { - bfs_perror(ctx, "malloc()"); + bfs_perror(ctx, "alloc()"); goto fail; } @@ -224,9 +215,8 @@ static char *bfs_exec_format_path(const struct bfs_exec *execbuf, const struct B return NULL; } - strcpy(path, "./"); - strcpy(path + 2, name); - + char *cur = stpcpy(path, "./"); + cur = stpcpy(cur, name); return path; } @@ -612,7 +602,7 @@ static int bfs_exec_push(struct bfs_exec *execbuf, char *arg) { if (execbuf->argc + 1 >= execbuf->argv_cap) { size_t cap = 2*execbuf->argv_cap; - char **argv = realloc(execbuf->argv, cap*sizeof(*argv)); + char **argv = realloc(execbuf->argv, sizeof_array(char *, cap)); if (!argv) { return -1; } diff --git a/src/ioq.c b/src/ioq.c index 5550c91..3e304ce 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "ioq.h" +#include "alloc.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" @@ -114,7 +115,7 @@ static struct ioqq *ioqq_create(size_t size) { // Circular buffer size must be a power of two size = bit_ceil(size); - struct ioqq *ioqq = xmemalign(alignof(struct ioqq), flex_sizeof(struct ioqq, slots, size)); + struct ioqq *ioqq = ALLOC_FLEX(struct ioqq, slots, size); if (!ioqq) { return NULL; } @@ -124,7 +125,7 @@ static struct ioqq *ioqq_create(size_t size) { // Use a pool of monitors size_t nmonitors = size < 64 ? size : 64; - ioqq->monitors = xmemalign(alignof(struct ioq_monitor), nmonitors * sizeof(struct ioq_monitor)); + ioqq->monitors = ALLOC_ARRAY(struct ioq_monitor, nmonitors); if (!ioqq->monitors) { ioqq_destroy(ioqq); return NULL; @@ -273,7 +274,7 @@ struct ioq { /** The number of background threads. */ size_t nthreads; /** The background threads themselves. */ - pthread_t *threads; + pthread_t threads[]; }; /** Background thread entry point. */ @@ -303,18 +304,13 @@ static void *ioq_work(void *ptr) { return NULL; } -struct ioq *ioq_create(size_t depth, size_t threads) { - struct ioq *ioq = malloc(sizeof(*ioq)); +struct ioq *ioq_create(size_t depth, size_t nthreads) { + struct ioq *ioq = ZALLOC_FLEX(struct ioq, threads, nthreads); if (!ioq) { goto fail; } ioq->depth = depth; - ioq->size = 0; - - ioq->pending = NULL; - ioq->ready = NULL; - ioq->nthreads = 0; ioq->pending = ioqq_create(depth); if (!ioq->pending) { @@ -326,12 +322,7 @@ struct ioq *ioq_create(size_t depth, size_t threads) { goto fail; } - ioq->threads = malloc(threads * sizeof(ioq->threads[0])); - if (!ioq->threads) { - goto fail; - } - - for (size_t i = 0; i < threads; ++i) { + for (size_t i = 0; i < nthreads; ++i) { errno = pthread_create(&ioq->threads[i], NULL, ioq_work, ioq); if (errno != 0) { goto fail; @@ -354,7 +345,7 @@ int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { return -1; } - union ioq_cmd *cmd = malloc(sizeof(*cmd)); + union ioq_cmd *cmd = ALLOC(union ioq_cmd); if (!cmd) { return -1; } @@ -412,7 +403,6 @@ void ioq_destroy(struct ioq *ioq) { abort(); } } - free(ioq->threads); ioqq_destroy(ioq->ready); ioqq_destroy(ioq->pending); diff --git a/src/ioq.h b/src/ioq.h index 9492034..0af5779 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -33,12 +33,12 @@ struct ioq_res { * * @param depth * The maximum depth of the queue. - * @param threads + * @param nthreads * The maximum number of background threads. * @return * The new I/O queue, or NULL on failure. */ -struct ioq *ioq_create(size_t depth, size_t threads); +struct ioq *ioq_create(size_t depth, size_t nthreads); /** * Asynchronous bfs_opendir(). diff --git a/src/main.c b/src/main.c index 76dde86..b7a08c1 100644 --- a/src/main.c +++ b/src/main.c @@ -20,6 +20,7 @@ * - bftw.[ch] (an extended version of nftw(3)) * * - Utilities: + * - alloc.[ch] (memory allocation) * - atomic.h (atomic operations) * - bar.[ch] (a terminal status bar) * - bit.h (bit manipulation) diff --git a/src/mtab.c b/src/mtab.c index 1d1ad94..e5c25ba 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "mtab.h" +#include "alloc.h" #include "bfstd.h" #include "config.h" #include "darray.h" @@ -87,15 +88,13 @@ fail: } struct bfs_mtab *bfs_mtab_parse(void) { - struct bfs_mtab *mtab = malloc(sizeof(*mtab)); + struct bfs_mtab *mtab = ZALLOC(struct bfs_mtab); if (!mtab) { return NULL; } - mtab->entries = NULL; trie_init(&mtab->names); trie_init(&mtab->types); - mtab->types_filled = false; int error = 0; diff --git a/src/parse.c b/src/parse.c index 64e08cd..cf4f696 100644 --- a/src/parse.c +++ b/src/parse.c @@ -9,6 +9,7 @@ */ #include "parse.h" +#include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" @@ -55,39 +56,16 @@ static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { - struct bfs_expr *expr = malloc(sizeof(*expr)); + struct bfs_expr *expr = ZALLOC(struct bfs_expr); if (!expr) { - perror("malloc()"); + perror("zalloc()"); return NULL; } expr->eval_fn = eval_fn; expr->argc = argc; expr->argv = argv; - expr->persistent_fds = 0; - expr->ephemeral_fds = 0; - expr->pure = false; - expr->always_true = false; - expr->always_false = false; - expr->cost = 0.0; expr->probability = 0.5; - expr->evaluations = 0; - expr->successes = 0; - expr->elapsed.tv_sec = 0; - expr->elapsed.tv_nsec = 0; - - // Prevent bfs_expr_free() from freeing uninitialized pointers on error paths - if (bfs_expr_is_parent(expr)) { - expr->lhs = NULL; - expr->rhs = NULL; - } else if (eval_fn == eval_exec) { - expr->exec = NULL; - } else if (eval_fn == eval_fprintf) { - expr->printf = NULL; - } else if (eval_fn == eval_regex) { - expr->regex = NULL; - } - return expr; } diff --git a/src/pwcache.c b/src/pwcache.c index f52e4e1..9f32eb0 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "pwcache.h" +#include "alloc.h" #include "config.h" #include "darray.h" #include "trie.h" @@ -71,7 +72,7 @@ struct bfs_users { }; struct bfs_users *bfs_users_new(void) { - struct bfs_users *users = malloc(sizeof(*users)); + struct bfs_users *users = ALLOC(struct bfs_users); if (!users) { return NULL; } @@ -144,7 +145,7 @@ struct bfs_groups { }; struct bfs_groups *bfs_groups_new(void) { - struct bfs_groups *groups = malloc(sizeof(*groups)); + struct bfs_groups *groups = ALLOC(struct bfs_groups); if (!groups) { return NULL; } diff --git a/src/trie.c b/src/trie.c index 8543eb1..19423cf 100644 --- a/src/trie.c +++ b/src/trie.c @@ -82,6 +82,7 @@ */ #include "trie.h" +#include "alloc.h" #include "bit.h" #include "config.h" #include "diag.h" @@ -317,7 +318,7 @@ struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key) { /** Create a new leaf, holding a copy of the given key. */ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, size_t length) { - struct trie_leaf *leaf = malloc(flex_sizeof(struct trie_leaf, key, length)); + struct trie_leaf *leaf = ALLOC_FLEX(struct trie_leaf, key, length); if (!leaf) { return NULL; } @@ -339,12 +340,10 @@ static void trie_leaf_free(struct trie *trie, struct trie_leaf *leaf) { /** Compute the size of a trie node with a certain number of children. */ static size_t trie_node_size(unsigned int size) { - // Empty nodes aren't supported - bfs_assert(size > 0); // Node size must be a power of two bfs_assert(has_single_bit(size)); - return flex_sizeof(struct trie_node, children, size); + return sizeof_flex(struct trie_node, children, size); } #if ENDIAN_NATIVE == ENDIAN_LITTLE diff --git a/src/xregex.c b/src/xregex.c index 89c2e90..ab5f793 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "xregex.h" +#include "alloc.h" #include "config.h" #include "diag.h" #include "sanity.h" @@ -115,7 +116,7 @@ static int bfs_onig_initialize(OnigEncoding *enc) { #endif int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_type type, enum bfs_regcomp_flags flags) { - struct bfs_regex *regex = *preg = malloc(sizeof(*regex)); + struct bfs_regex *regex = *preg = ALLOC(struct bfs_regex); if (!regex) { return -1; } diff --git a/src/xspawn.c b/src/xspawn.c index 740e38e..2cabdcc 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "xspawn.h" +#include "alloc.h" #include "bfstd.h" #include "config.h" #include "list.h" @@ -62,7 +63,7 @@ int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { /** Add a spawn action to the chain. */ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { - struct bfs_spawn_action *action = malloc(sizeof(*action)); + struct bfs_spawn_action *action = ALLOC(struct bfs_spawn_action); if (!action) { return NULL; } diff --git a/tests/alloc.c b/tests/alloc.c new file mode 100644 index 0000000..91b1b43 --- /dev/null +++ b/tests/alloc.c @@ -0,0 +1,24 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "../src/alloc.h" +#include "../src/diag.h" +#include + +int main(void) { + // Check sizeof_flex() + struct flexible { + alignas(64) int foo; + int bar[]; + }; + bfs_verify(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible)); + bfs_verify(sizeof_flex(struct flexible, bar, 16) % alignof(struct flexible) == 0); + bfs_verify(sizeof_flex(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) + == align_floor(alignof(struct flexible), SIZE_MAX)); + + // Corner case: sizeof(type) > align_ceil(alignof(type), offsetof(type, member)) + // Doesn't happen in typical ABIs + bfs_verify(flex_size(8, 16, 4, 4, 1) == 16); + + return EXIT_SUCCESS; +} diff --git a/tests/bfstd.c b/tests/bfstd.c index 7fea9b5..fa854a8 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -24,17 +24,6 @@ static void check_base_dir(const char *path, const char *dir, const char *base) } int main(void) { - // Check flex_sizeof() - struct flexible { - alignas(64) int foo; - int bar[]; - }; - bfs_verify(flex_sizeof(struct flexible, bar, 0) >= sizeof(struct flexible)); - bfs_verify(flex_sizeof(struct flexible, bar, 16) % alignof(struct flexible) == 0); - bfs_verify(flex_sizeof(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) - == align_floor(alignof(struct flexible), SIZE_MAX)); - bfs_verify(flex_sizeof_impl(8, 16, 4, 4, 1) == 16); - // From man 3p basename check_base_dir("usr", ".", "usr"); check_base_dir("usr/", ".", "usr"); @@ -46,4 +35,6 @@ int main(void) { check_base_dir("/usr/lib", "/usr", "lib"); check_base_dir("//usr//lib//", "//usr", "lib"); check_base_dir("/home//dwc//test", "/home//dwc", "test"); + + return EXIT_SUCCESS; } -- cgit v1.2.3 From eb83f2a91a615c5fa3788d41c3ec80b43bf5ed28 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 19 Jun 2023 13:43:46 -0400 Subject: alloc: Implement an arena allocator --- src/alloc.c | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/alloc.h | 44 ++++++++++++++++++++++++ 2 files changed, 155 insertions(+) diff --git a/src/alloc.c b/src/alloc.c index 0003108..b437975 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -4,6 +4,7 @@ #include "alloc.h" #include "bit.h" #include "diag.h" +#include "sanity.h" #include #include #include @@ -48,3 +49,113 @@ void *zalloc(size_t align, size_t size) { } return ret; } + +/** + * An arena allocator chunk. + */ +union chunk { + /** + * Free chunks are stored in a singly linked list. The pointer to the + * next chunk is represented by an offset from the chunk immediately + * after this one in memory, so that zalloc() correctly initializes a + * linked list of chunks (except for the last one). + */ + uintptr_t next; + + // char object[]; +}; + +/** Decode the next chunk. */ +static union chunk *chunk_next(const struct arena *arena, const union chunk *chunk) { + uintptr_t base = (uintptr_t)chunk + arena->size; + return (union chunk *)(base + chunk->next); +} + +/** Encode the next chunk. */ +static void chunk_set_next(const struct arena *arena, union chunk *chunk, union chunk *next) { + uintptr_t base = (uintptr_t)chunk + arena->size; + chunk->next = (uintptr_t)next - base; +} + +void arena_init(struct arena *arena, size_t align, size_t size) { + bfs_assert(has_single_bit(align)); + bfs_assert((size & (align - 1)) == 0); + + if (align < alignof(union chunk)) { + align = alignof(union chunk); + } + if (size < sizeof(union chunk)) { + size = sizeof(union chunk); + } + bfs_assert((size & (align - 1)) == 0); + + arena->chunks = NULL; + arena->nslabs = 0; + arena->slabs = NULL; + arena->align = align; + arena->size = size; +} + +/** Allocate a new slab. */ +static int slab_alloc(struct arena *arena) { + void **slabs = realloc(arena->slabs, sizeof_array(void *, arena->nslabs + 1)); + if (!slabs) { + return -1; + } + arena->slabs = slabs; + + // Make the initial allocation size ~4K + size_t size = 4096; + if (size < arena->size) { + size = arena->size; + } + // Trim off the excess + size -= size % arena->size; + // Double the size for every slab + size <<= arena->nslabs; + + // Allocate the slab + void *slab = zalloc(arena->align, size); + if (!slab) { + return -1; + } + + // Fix the last chunk->next offset + void *last = (char *)slab + size - arena->size; + chunk_set_next(arena, last, arena->chunks); + + // We can rely on zero-initialized slabs, but others shouldn't + sanitize_uninit(slab, size); + + arena->chunks = arena->slabs[arena->nslabs++] = slab; + return 0; +} + +void *arena_alloc(struct arena *arena) { + if (!arena->chunks && slab_alloc(arena) != 0) { + return NULL; + } + + union chunk *chunk = arena->chunks; + sanitize_alloc(chunk, arena->size); + + sanitize_init(chunk); + arena->chunks = chunk_next(arena, chunk); + sanitize_uninit(chunk, arena->size); + + return chunk; +} + +void arena_free(struct arena *arena, void *ptr) { + union chunk *chunk = ptr; + chunk_set_next(arena, chunk, arena->chunks); + arena->chunks = chunk; + sanitize_free(chunk, arena->size); +} + +void arena_destroy(struct arena *arena) { + for (size_t i = 0; i < arena->nslabs; ++i) { + free(arena->slabs[i]); + } + sanitize_uninit(arena); +} diff --git a/src/alloc.h b/src/alloc.h index 899a4ec..65edb92 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -146,4 +146,48 @@ void *zalloc(size_t align, size_t size); #define ZALLOC_FLEX(type, member, count) \ (type *)zalloc(alignof(type), sizeof_flex(type, member, count)) +/** + * An arena allocator for fixed-size types. + * + * Arena allocators are intentionally not thread safe. + */ +struct arena { + /** The list of free chunks. */ + void *chunks; + /** The number of allocated slabs. */ + size_t nslabs; + /** The array of slabs. */ + void **slabs; + /** Chunk alignment. */ + size_t align; + /** Chunk size. */ + size_t size; +}; + +/** + * Initialize an arena for chunks of the given size and alignment. + */ +void arena_init(struct arena *arena, size_t align, size_t size); + +/** + * Initialize an arena for the given type. + */ +#define ARENA_INIT(arena, type) \ + arena_init((arena), alignof(type), sizeof(type)) + +/** + * Allocate an object out of the arena. + */ +void *arena_alloc(struct arena *arena); + +/** + * Free an object from the arena. + */ +void arena_free(struct arena *arena, void *ptr); + +/** + * Destroy an arena, freeing all allocations. + */ +void arena_destroy(struct arena *arena); + #endif // BFS_ALLOC_H -- cgit v1.2.3 From cbe2c473c9aee71f5f770dc7f41973d2ef4c273c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 19 Jun 2023 16:58:24 -0400 Subject: alloc: Implement an arena for flexible structs --- src/alloc.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/alloc.h | 92 +++++++++++++++++++++++++++++++++++++++++++++ tests/alloc.c | 14 ++++++- 3 files changed, 223 insertions(+), 1 deletion(-) diff --git a/src/alloc.c b/src/alloc.c index b437975..a6910ce 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -157,5 +157,123 @@ void arena_destroy(struct arena *arena) { for (size_t i = 0; i < arena->nslabs; ++i) { free(arena->slabs[i]); } + free(arena->slabs); sanitize_uninit(arena); } + +void varena_init(struct varena *varena, size_t align, size_t min, size_t offset, size_t size) { + varena->align = align; + varena->offset = offset; + varena->size = size; + varena->narenas = 0; + varena->arenas = NULL; + + // The smallest size class is at least as many as fit in the smallest + // aligned allocation size + size_t min_count = (flex_size(align, min, offset, size, 1) - offset + size - 1) / size; + varena->shift = bit_width(min_count - 1); +} + +/** Get the size class for the given array length. */ +static size_t varena_size_class(struct varena *varena, size_t count) { + // Since powers of two are common array lengths, make them the + // (inclusive) upper bound for each size class + return bit_width((count - !!count) >> varena->shift); +} + +/** Get the exact size of a flexible struct. */ +static size_t varena_exact_size(const struct varena *varena, size_t count) { + return flex_size(varena->align, 0, varena->offset, varena->size, count); +} + +/** Get the arena for the given array length. */ +static struct arena *varena_get(struct varena *varena, size_t count) { + size_t i = varena_size_class(varena, count); + + if (i >= varena->narenas) { + size_t narenas = i + 1; + struct arena *arenas = realloc(varena->arenas, sizeof_array(struct arena, narenas)); + if (!arenas) { + return NULL; + } + + for (size_t j = varena->narenas; j < narenas; ++j) { + size_t shift = j + varena->shift; + size_t size = varena_exact_size(varena, (size_t)1 << shift); + arena_init(&arenas[j], varena->align, size); + } + + varena->narenas = narenas; + varena->arenas = arenas; + } + + return &varena->arenas[i]; +} + +void *varena_alloc(struct varena *varena, size_t count) { + struct arena *arena = varena_get(varena, count); + if (!arena) { + return NULL; + } + + void *ret = arena_alloc(arena); + if (!ret) { + return NULL; + } + + // Tell the sanitizers the exact size of the allocated struct + sanitize_free(ret, arena->size); + sanitize_alloc(ret, varena_exact_size(varena, count)); + + return ret; +} + +void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count) { + struct arena *new_arena = varena_get(varena, new_count); + struct arena *old_arena = varena_get(varena, old_count); + if (!new_arena) { + return NULL; + } + + size_t new_exact_size = varena_exact_size(varena, new_count); + size_t old_exact_size = varena_exact_size(varena, old_count); + + if (new_arena == old_arena) { + if (new_count < old_count) { + sanitize_free((char *)ptr + new_exact_size, old_exact_size - new_exact_size); + } else if (new_count > old_count) { + sanitize_alloc((char *)ptr + old_exact_size, new_exact_size - old_exact_size); + } + return ptr; + } + + void *ret = arena_alloc(new_arena); + if (!ret) { + return NULL; + } + + size_t old_size = old_arena->size; + sanitize_alloc((char *)ptr + old_exact_size, old_size - old_exact_size); + + size_t new_size = new_arena->size; + size_t min_size = new_size < old_size ? new_size : old_size; + memcpy(ret, ptr, min_size); + + arena_free(old_arena, ptr); + sanitize_free((char *)ret + new_exact_size, new_size - new_exact_size); + + return ret; +} + +void varena_free(struct varena *varena, void *ptr, size_t count) { + struct arena *arena = varena_get(varena, count); + arena_free(arena, ptr); +} + +void varena_destroy(struct varena *varena) { + for (size_t i = 0; i < varena->narenas; ++i) { + arena_destroy(&varena->arenas[i]); + } + free(varena->arenas); + sanitize_uninit(varena); +} diff --git a/src/alloc.h b/src/alloc.h index 65edb92..c2ea09b 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -190,4 +190,96 @@ void arena_free(struct arena *arena, void *ptr); */ void arena_destroy(struct arena *arena); +/** + * An arena allocator for flexibly-sized types. + */ +struct varena { + /** The alignment of the struct. */ + size_t align; + /** The offset of the flexible array. */ + size_t offset; + /** The size of the flexible array elements. */ + size_t size; + /** Shift amount for the smallest size class. */ + size_t shift; + /** The number of arenas of different sizes. */ + size_t narenas; + /** The array of differently-sized arenas. */ + struct arena *arenas; +}; + +/** + * Initialize a varena for a struct with the given layout. + * + * @param varena + * The varena to initialize. + * @param align + * alignof(type) + * @param min + * sizeof(type) + * @param offset + * offsetof(type, flexible_array) + * @param size + * sizeof(flexible_array[i]) + */ +void varena_init(struct varena *varena, size_t align, size_t min, size_t offset, size_t size); + +/** + * Initialize a varena for the given type and flexible array. + * + * @param varena + * The varena to initialize. + * @param type + * A struct type containing a flexible array. + * @param member + * The name of the flexible array member. + */ +#define VARENA_INIT(varena, type, member) \ + varena_init(varena, alignof(type), sizeof(type), offsetof(type, member), sizeof_member(type, member[0])) + +/** + * Arena-allocate a flexible struct. + * + * @param varena + * The varena to allocate from. + * @param count + * The length of the flexible array. + * @return + * The allocated struct, or NULL on failure. + */ +void *varena_alloc(struct varena *varena, size_t count); + +/** + * Resize a flexible struct. + * + * @param varena + * The varena to allocate from. + * @param ptr + * The object to resize. + * @param old_count + * The old array lenth. + * @param new_count + * The new array length. + * @return + * The resized struct, or NULL on failure. + */ +void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count); + +/** + * Free an arena-allocated flexible struct. + * + * @param varena + * The that allocated the object. + * @param ptr + * The object to free. + * @param count + * The length of the flexible array. + */ +void varena_free(struct varena *varena, void *ptr, size_t count); + +/** + * Destroy a varena, freeing all allocations. + */ +void varena_destroy(struct varena *varena); + #endif // BFS_ALLOC_H diff --git a/tests/alloc.c b/tests/alloc.c index 91b1b43..9e6e892 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -8,7 +8,7 @@ int main(void) { // Check sizeof_flex() struct flexible { - alignas(64) int foo; + alignas(64) int foo[8]; int bar[]; }; bfs_verify(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible)); @@ -20,5 +20,17 @@ int main(void) { // Doesn't happen in typical ABIs bfs_verify(flex_size(8, 16, 4, 4, 1) == 16); + // varena tests + struct varena varena; + VARENA_INIT(&varena, struct flexible, bar); + + for (size_t i = 0; i < 256; ++i) { + bfs_verify(varena_alloc(&varena, i)); + struct arena *arena = &varena.arenas[varena.narenas - 1]; + bfs_verify(arena->size >= sizeof_flex(struct flexible, bar, i)); + } + + varena_destroy(&varena); + return EXIT_SUCCESS; } -- cgit v1.2.3 From 1649d37f7c88f8a5930fdd4c1ff1b59212da90ee Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 28 Oct 2022 21:27:20 -0400 Subject: bftw: Arena-allocate struct bftw_file --- src/bftw.c | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 7ab14c7..d0491e0 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -100,6 +100,9 @@ struct bftw_cache { struct bftw_file *target; /** The remaining capacity of the LRU list. */ size_t capacity; + + /** bftw_file arena. */ + struct varena files; }; /** Initialize a cache. */ @@ -107,6 +110,7 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { LIST_INIT(cache); cache->target = NULL; cache->capacity = capacity; + VARENA_INIT(&cache->files, struct bftw_file, name); } /** Remove a bftw_file from the LRU list. */ @@ -237,12 +241,14 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { bfs_assert(!cache->head); bfs_assert(!cache->tail); bfs_assert(!cache->target); + + varena_destroy(&cache->files); } /** Create a new bftw_file. */ -static struct bftw_file *bftw_file_new(struct bftw_file *parent, const char *name) { +static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_file *parent, const char *name) { size_t namelen = strlen(name); - struct bftw_file *file = ALLOC_FLEX(struct bftw_file, name, namelen + 1); + struct bftw_file *file = varena_alloc(&cache->files, namelen + 1); if (!file) { return NULL; } @@ -419,7 +425,7 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bftw_file_close(cache, file); } - free(file); + varena_free(&cache->files, file, file->namelen + 1); } /** @@ -1212,7 +1218,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { struct bftw_file *file = state->file; if (name && (state->flags & BFTW_BUFFER)) { - file = bftw_file_new(file, name); + file = bftw_file_new(&state->cache, file, name); if (!file) { state->error = errno; return -1; @@ -1229,7 +1235,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { switch (bftw_call_back(state, name, BFTW_PRE)) { case BFTW_CONTINUE: if (name) { - file = bftw_file_new(state->file, name); + file = bftw_file_new(&state->cache, state->file, name); } else { state->file = NULL; } -- cgit v1.2.3 From 4b177a01a7f4e83f67af2200ec69505b75a3c399 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 19 Jun 2023 16:58:45 -0400 Subject: trie: Arena-allocate nodes and leaves --- src/trie.c | 65 ++++++++++++++++++++++++++++++-------------------------------- src/trie.h | 5 +++++ 2 files changed, 36 insertions(+), 34 deletions(-) diff --git a/src/trie.c b/src/trie.c index 19423cf..992d42b 100644 --- a/src/trie.c +++ b/src/trie.c @@ -166,6 +166,8 @@ static uintptr_t trie_encode_node(const struct trie_node *node) { void trie_init(struct trie *trie) { trie->root = 0; LIST_INIT(trie); + VARENA_INIT(&trie->nodes, struct trie_node, children); + VARENA_INIT(&trie->leaves, struct trie_leaf, key); } /** Extract the nibble at a certain offset from a byte sequence. */ @@ -318,7 +320,7 @@ struct trie_leaf *trie_find_prefix(const struct trie *trie, const char *key) { /** Create a new leaf, holding a copy of the given key. */ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, size_t length) { - struct trie_leaf *leaf = ALLOC_FLEX(struct trie_leaf, key, length); + struct trie_leaf *leaf = varena_alloc(&trie->leaves, length); if (!leaf) { return NULL; } @@ -335,15 +337,26 @@ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, siz /** Free a leaf. */ static void trie_leaf_free(struct trie *trie, struct trie_leaf *leaf) { LIST_REMOVE(trie, leaf); - free(leaf); + varena_free(&trie->leaves, leaf, leaf->length); } -/** Compute the size of a trie node with a certain number of children. */ -static size_t trie_node_size(unsigned int size) { - // Node size must be a power of two +/** Create a new node. */ +static struct trie_node *trie_node_alloc(struct trie *trie, size_t size) { bfs_assert(has_single_bit(size)); + return varena_alloc(&trie->nodes, size); +} + +/** Reallocate a trie node. */ +static struct trie_node *trie_node_realloc(struct trie *trie, struct trie_node *node, size_t old_size, size_t new_size) { + bfs_assert(has_single_bit(old_size)); + bfs_assert(has_single_bit(new_size)); + return varena_realloc(&trie->nodes, node, old_size, new_size); +} - return sizeof_flex(struct trie_node, children, size); +/** Free a node. */ +static void trie_node_free(struct trie *trie, struct trie_node *node, size_t size) { + bfs_assert(size == (size_t)count_ones(node->bitmap)); + varena_free(&trie->nodes, node, size); } #if ENDIAN_NATIVE == ENDIAN_LITTLE @@ -422,7 +435,7 @@ static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, str // Double the capacity every power of two if (has_single_bit(size)) { - node = realloc(node, trie_node_size(2 * size)); + node = trie_node_realloc(trie, node, size, 2 * size); if (!node) { trie_leaf_free(trie, leaf); return NULL; @@ -469,12 +482,12 @@ static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, str * | Y * +--->key */ -static uintptr_t *trie_jump(uintptr_t *ptr, const char *key, size_t *offset) { +static uintptr_t *trie_jump(struct trie *trie, uintptr_t *ptr, const char *key, size_t *offset) { // We only ever need to jump to leaf nodes, since internal nodes are // guaranteed to be within OFFSET_MAX anyway bfs_assert(trie_is_leaf(*ptr)); - struct trie_node *node = malloc(trie_node_size(1)); + struct trie_node *node = trie_node_alloc(trie, 1); if (!node) { return NULL; } @@ -512,7 +525,7 @@ static struct trie_leaf *trie_split(struct trie *trie, uintptr_t *ptr, struct tr unsigned char rep_nibble = trie_key_nibble(rep->key, mismatch); bfs_assert(key_nibble != rep_nibble); - struct trie_node *node = malloc(trie_node_size(2)); + struct trie_node *node = trie_node_alloc(trie, 2); if (!node) { trie_leaf_free(trie, leaf); return NULL; @@ -578,7 +591,7 @@ static struct trie_leaf *trie_insert_mem_impl(struct trie *trie, const void *key } while (mismatch - offset > OFFSET_MAX) { - ptr = trie_jump(ptr, key, &offset); + ptr = trie_jump(trie, ptr, key, &offset); if (!ptr) { trie_leaf_free(trie, leaf); return NULL; @@ -601,7 +614,7 @@ static void trie_free_singletons(struct trie *trie, uintptr_t ptr) { bfs_assert(has_single_bit(node->bitmap)); ptr = node->children[0]; - free(node); + trie_node_free(trie, node, 1); } trie_leaf_free(trie, trie_decode_leaf(ptr)); @@ -624,7 +637,7 @@ static void trie_free_singletons(struct trie *trie, uintptr_t ptr) { * v * other */ -static int trie_collapse_node(uintptr_t *parent, struct trie_node *parent_node, unsigned int child_index) { +static int trie_collapse_node(struct trie *trie, uintptr_t *parent, struct trie_node *parent_node, unsigned int child_index) { uintptr_t other = parent_node->children[child_index ^ 1]; if (!trie_is_leaf(other)) { struct trie_node *other_node = trie_decode_node(other); @@ -636,7 +649,7 @@ static int trie_collapse_node(uintptr_t *parent, struct trie_node *parent_node, } *parent = other; - free(parent_node); + trie_node_free(trie, parent_node, 1); return 0; } @@ -682,7 +695,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { node->bitmap ^= child_bit; unsigned int parent_size = count_ones(node->bitmap); bfs_assert(parent_size > 0); - if (parent_size == 1 && trie_collapse_node(parent, node, child_index) == 0) { + if (parent_size == 1 && trie_collapse_node(trie, parent, node, child_index) == 0) { return; } @@ -691,7 +704,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { } if (has_single_bit(parent_size)) { - node = realloc(node, trie_node_size(parent_size)); + node = trie_node_realloc(trie, node, 2 * parent_size, parent_size); if (node) { *parent = trie_encode_node(node); } @@ -702,23 +715,7 @@ void trie_remove(struct trie *trie, struct trie_leaf *leaf) { trie_remove_impl(trie, leaf); } -/** Free an encoded pointer to a node. */ -TARGET_CLONES_POPCNT -static void free_trie_ptr(uintptr_t ptr) { - if (trie_is_leaf(ptr)) { - free(trie_decode_leaf(ptr)); - } else { - struct trie_node *node = trie_decode_node(ptr); - size_t size = count_ones(node->bitmap); - for (size_t i = 0; i < size; ++i) { - free_trie_ptr(node->children[i]); - } - free(node); - } -} - void trie_destroy(struct trie *trie) { - if (trie->root) { - free_trie_ptr(trie->root); - } + varena_destroy(&trie->leaves); + varena_destroy(&trie->nodes); } diff --git a/src/trie.h b/src/trie.h index 6bd211e..6921f62 100644 --- a/src/trie.h +++ b/src/trie.h @@ -5,6 +5,7 @@ #define BFS_TRIE_H #include "config.h" +#include "alloc.h" #include #include @@ -30,6 +31,10 @@ struct trie { uintptr_t root; /** Linked list of leaves. */ struct trie_leaf *head, *tail; + /** Node allocator. */ + struct varena nodes; + /** Leaf allocator. */ + struct varena leaves; }; /** -- cgit v1.2.3 From 4889f3ebb59c926b8e53a2e12edd5009d7cd4cbe Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 19 Jun 2023 13:46:54 -0400 Subject: ioq: Arena-allocate ioq_cmd --- src/ioq.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 3e304ce..33316fa 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -266,6 +266,9 @@ struct ioq { /** The current size of the queue. */ size_t size; + /** ioq_cmd command arena. */ + struct arena cmds; + /** Pending I/O requests. */ struct ioqq *pending; /** Ready I/O responses. */ @@ -311,6 +314,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { } ioq->depth = depth; + ARENA_INIT(&ioq->cmds, union ioq_cmd); ioq->pending = ioqq_create(depth); if (!ioq->pending) { @@ -345,7 +349,7 @@ int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { return -1; } - union ioq_cmd *cmd = ALLOC(union ioq_cmd); + union ioq_cmd *cmd = arena_alloc(&ioq->cmds); if (!cmd) { return -1; } @@ -385,8 +389,7 @@ struct ioq_res *ioq_trypop(struct ioq *ioq) { } void ioq_free(struct ioq *ioq, struct ioq_res *res) { - union ioq_cmd *cmd = (union ioq_cmd *)res; - free(cmd); + arena_free(&ioq->cmds, (union ioq_cmd *)res); } void ioq_destroy(struct ioq *ioq) { @@ -407,5 +410,7 @@ void ioq_destroy(struct ioq *ioq) { ioqq_destroy(ioq->ready); ioqq_destroy(ioq->pending); + arena_destroy(&ioq->cmds); + free(ioq); } -- cgit v1.2.3 From a1490d98a1aebb3bfbd3873613977d0341ec7f98 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 20 Jun 2023 12:02:08 -0400 Subject: dir: Arena-allocate directories --- src/bftw.c | 37 +++++++++++++++++++++++++++++-------- src/dir.c | 44 +++++++++++++++++++++++--------------------- src/dir.h | 27 +++++++++++++++++++++++---- src/eval.c | 34 ++++++++++++++++++++++------------ src/ioq.c | 15 ++++++++++----- src/ioq.h | 4 +++- 6 files changed, 110 insertions(+), 51 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index d0491e0..69e41a2 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -103,6 +103,8 @@ struct bftw_cache { /** bftw_file arena. */ struct varena files; + /** bfs_dir arena. */ + struct arena dirs; }; /** Initialize a cache. */ @@ -111,6 +113,7 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { cache->target = NULL; cache->capacity = capacity; VARENA_INIT(&cache->files, struct bftw_file, name); + bfs_dir_arena(&cache->dirs); } /** Remove a bftw_file from the LRU list. */ @@ -136,6 +139,7 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { if (file->dir) { bfs_assert(file->fd == bfs_dirfd(file->dir)); bfs_closedir(file->dir); + arena_free(&cache->dirs, file->dir); file->dir = NULL; } else { xclose(file->fd); @@ -157,9 +161,10 @@ static void bftw_file_freedir(struct bftw_cache *cache, struct bftw_file *file) bool pinned = file->pincount > 0; if (reffed || pinned) { - int fd = bfs_freedir(file->dir, pinned); + int fd = bfs_fdclosedir(file->dir, pinned); if (fd >= 0) { file->fd = fd; + arena_free(&cache->dirs, file->dir); file->dir = NULL; } } else { @@ -243,6 +248,7 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { bfs_assert(!cache->target); varena_destroy(&cache->files); + arena_destroy(&cache->dirs); } /** Create a new bftw_file. */ @@ -410,10 +416,17 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_f return NULL; } - struct bfs_dir *dir = bfs_opendir(fd, NULL); - if (dir) { - bftw_file_set_dir(cache, file, dir); + struct bfs_dir *dir = arena_alloc(&cache->dirs); + if (!dir) { + return NULL; + } + + if (bfs_opendir(dir, fd, NULL) != 0) { + arena_free(&cache->dirs, dir); + return NULL; } + + bftw_file_set_dir(cache, file, dir); return dir; } @@ -900,14 +913,18 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } } - --cache->capacity; - if (ioq_opendir(state->ioq, dfd, file->name, file) != 0) { - ++cache->capacity; + struct bfs_dir *dir = arena_alloc(&state->cache.dirs); + if (!dir) { goto unpin; } + if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { + goto free; + } + file->ioqueued = true; + --cache->capacity; if (state->flags & BFTW_SORT) { goto append; @@ -915,6 +932,8 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { return; } +free: + arena_free(&state->cache.dirs, dir); unpin: if (file->parent) { bftw_cache_unpin(cache, file->parent); @@ -950,7 +969,9 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { bftw_cache_unpin(cache, file->parent); } - if (res->dir) { + if (res->error) { + arena_free(&state->cache.dirs, res->dir); + } else { bftw_file_set_dir(cache, file, res->dir); } diff --git a/src/dir.c b/src/dir.c index b9fd74d..a24b572 100644 --- a/src/dir.c +++ b/src/dir.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "dir.h" +#include "alloc.h" #include "bfstd.h" #include "config.h" #include "diag.h" @@ -120,26 +121,26 @@ struct bfs_dir { # define DIR_SIZE sizeof(struct bfs_dir) #endif -struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { - struct bfs_dir *dir = malloc(DIR_SIZE); - if (!dir) { - return NULL; - } +struct bfs_dir *bfs_allocdir(void) { + return malloc(DIR_SIZE); +} + +void bfs_dir_arena(struct arena *arena) { + arena_init(arena, alignof(struct bfs_dir), DIR_SIZE); +} +int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { int fd; if (at_path) { fd = openat(at_fd, at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); + if (fd < 0) { + return -1; + } } else if (at_fd >= 0) { fd = at_fd; } else { - free(dir); errno = EBADF; - return NULL; - } - - if (fd < 0) { - free(dir); - return NULL; + return -1; } #if BFS_GETDENTS @@ -152,14 +153,13 @@ struct bfs_dir *bfs_opendir(int at_fd, const char *at_path) { if (at_path) { close_quietly(fd); } - free(dir); - return NULL; + return -1; } dir->de = NULL; #endif dir->eof = false; - return dir; + return 0; } int bfs_dirfd(const struct bfs_dir *dir) { @@ -291,17 +291,16 @@ int bfs_closedir(struct bfs_dir *dir) { bfs_verify(errno != EBADF); } #endif - free(dir); + + sanitize_uninit(dir, DIR_SIZE); return ret; } -int bfs_freedir(struct bfs_dir *dir, bool same_fd) { +int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd) { #if BFS_GETDENTS int ret = dir->fd; - free(dir); #elif __FreeBSD__ int ret = fdclosedir(dir->dir); - free(dir); #else if (same_fd) { errno = ENOTSUP; @@ -309,10 +308,13 @@ int bfs_freedir(struct bfs_dir *dir, bool same_fd) { } int ret = dup_cloexec(dirfd(dir->dir)); - if (ret >= 0) { - bfs_closedir(dir); + if (ret < 0) { + return -1; } + + bfs_closedir(dir); #endif + sanitize_uninit(dir, DIR_SIZE); return ret; } diff --git a/src/dir.h b/src/dir.h index 6fe7ae2..16f592e 100644 --- a/src/dir.h +++ b/src/dir.h @@ -8,6 +8,7 @@ #ifndef BFS_DIR_H #define BFS_DIR_H +#include "alloc.h" #include "config.h" #include @@ -61,18 +62,36 @@ struct bfs_dirent { const char *name; }; +/** + * Allocate space for a directory. + * + * @return + * An allocated, unopen directory, or NULL on failure. + */ +struct bfs_dir *bfs_allocdir(void); + +/** + * Initialize an arena for directories. + * + * @param arena + * The arena to initialize. + */ +void bfs_dir_arena(struct arena *arena); + /** * Open a directory. * + * @param dir + * The allocated directory. * @param at_fd * The base directory for path resolution. * @param at_path * The path of the directory to open, relative to at_fd. Pass NULL to * open at_fd itself. * @return - * The opened directory, or NULL on failure. + * 0 on success, or -1 on failure. */ -struct bfs_dir *bfs_opendir(int at_fd, const char *at_path); +int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path); /** * Get the file descriptor for a directory. @@ -110,7 +129,7 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de); int bfs_closedir(struct bfs_dir *dir); /** - * Free a directory, keeping an open file descriptor to it. + * Extract the file descriptor from an open directory. * * @param dir * The directory to free. @@ -122,6 +141,6 @@ int bfs_closedir(struct bfs_dir *dir); * On success, a file descriptor for the directory is returned. On * failure, -1 is returned, and the directory remains open. */ -int bfs_freedir(struct bfs_dir *dir, bool same_fd); +int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd); #endif // BFS_DIR_H diff --git a/src/eval.c b/src/eval.c index f16821e..5f27681 100644 --- a/src/eval.c +++ b/src/eval.c @@ -421,10 +421,15 @@ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { const struct BFTW *ftwbuf = state->ftwbuf; if (ftwbuf->type == BFS_DIR) { - struct bfs_dir *dir = bfs_opendir(ftwbuf->at_fd, ftwbuf->at_path); + struct bfs_dir *dir = bfs_allocdir(); if (!dir) { eval_report_error(state); - goto done; + return ret; + } + + if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path) != 0) { + eval_report_error(state); + return ret; } int did_read = bfs_readdir(dir, NULL); @@ -435,6 +440,7 @@ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { } bfs_closedir(dir); + free(dir); } else if (ftwbuf->type == BFS_REG) { const struct bfs_stat *statbuf = eval_stat(state); if (statbuf) { @@ -442,7 +448,6 @@ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { } } -done: return ret; } @@ -1495,20 +1500,25 @@ static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) { // Check /proc/self/fd for the current number of open fds, if possible // (we may have inherited more than just the standard ones) - struct bfs_dir *dir = bfs_opendir(AT_FDCWD, "/proc/self/fd"); + struct bfs_dir *dir = bfs_allocdir(); if (!dir) { - dir = bfs_opendir(AT_FDCWD, "/dev/fd"); + goto done; } - if (dir) { - // Account for 'dir' itself - nopen = -1; - while (bfs_readdir(dir, NULL) > 0) { - ++nopen; - } + if (bfs_opendir(dir, AT_FDCWD, "/proc/self/fd") != 0 + && bfs_opendir(dir, AT_FDCWD, "/dev/fd") != 0) { + goto done; + } - bfs_closedir(dir); + // Account for 'dir' itself + nopen = -1; + + while (bfs_readdir(dir, NULL) > 0) { + ++nopen; } + bfs_closedir(dir); +done: + free(dir); int ret = limit - nopen; ret -= ctx->expr->persistent_fds; diff --git a/src/ioq.c b/src/ioq.c index 33316fa..47b082a 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -20,9 +20,11 @@ * An I/O queue request. */ struct ioq_req { + /** Directory allocation. */ + struct bfs_dir *dir; /** Base file descriptor for openat(). */ int dfd; - /** Relative path to dfd. */ + /** Path to open, relative to dfd. */ const char *path; /** Arbitrary user data. */ @@ -295,10 +297,12 @@ static void *ioq_work(void *ptr) { struct ioq_res *res = &cmd->res; res->ptr = req.ptr; - res->dir = bfs_opendir(req.dfd, req.path); - res->error = errno; - if (res->dir) { + res->dir = req.dir; + res->error = 0; + if (bfs_opendir(req.dir, req.dfd, req.path) == 0) { bfs_polldir(res->dir); + } else { + res->error = errno; } ioqq_push(ioq->ready, cmd); @@ -344,7 +348,7 @@ fail: return NULL; } -int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { if (ioq->size >= ioq->depth) { return -1; } @@ -355,6 +359,7 @@ int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr) { } struct ioq_req *req = &cmd->req; + req->dir = dir; req->dfd = dfd; req->path = path; req->ptr = ptr; diff --git a/src/ioq.h b/src/ioq.h index 0af5779..50e02b1 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -45,6 +45,8 @@ struct ioq *ioq_create(size_t depth, size_t nthreads); * * @param ioq * The I/O queue. + * @param dir + * The allocated directory. * @param dfd * The base file descriptor. * @param path @@ -54,7 +56,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads); * @return * 0 on success, or -1 on failure. */ -int ioq_opendir(struct ioq *ioq, int dfd, const char *path, void *ptr); +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr); /** * Pop a response from the queue. -- cgit v1.2.3 From 273b64322afa46c560dd74ea32f6c8ad26d93210 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 22 Jun 2023 15:06:51 -0400 Subject: diag: New bfs_loc type for source locations --- src/diag.c | 18 +++++++++++++++++- src/diag.h | 44 +++++++++++++++++++++++++++++++------------- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/src/diag.c b/src/diag.c index 9e6b7ae..99b487a 100644 --- a/src/diag.c +++ b/src/diag.c @@ -5,6 +5,7 @@ #include "bfstd.h" #include "ctx.h" #include "color.h" +#include "config.h" #include "dstring.h" #include "expr.h" #include @@ -12,11 +13,26 @@ #include #include -noreturn void bfs_abortf(const char *format, ...) { +noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { + const char *cmd = NULL; +#if __GLIBC__ + cmd = program_invocation_short_name; +#elif BSD + cmd = getprogname(); +#endif + if (!cmd) { + cmd = BFS_COMMAND; + } + + fprintf(stderr, "%s: %s@%s:%d: ", cmd, loc->func, loc->file, loc->line); + va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); + + fprintf(stderr, "\n"); + abort(); } diff --git a/src/diag.h b/src/diag.h index 71abb1a..8d02da6 100644 --- a/src/diag.h +++ b/src/diag.h @@ -22,20 +22,36 @@ # define BFS_STATIC_ASSERT(expr, msg, ...) _Static_assert(expr, msg) #endif +/** + * A source code location. + */ +struct bfs_loc { + const char *file; + int line; + const char *func; +}; + +#define BFS_LOC_INIT { .file = __FILE__, .line = __LINE__, .func = __func__ } + +/** + * Get the current source code location. + */ +#if __STDC_VERSION__ >= 202311L +# define bfs_location() (&(static const struct bfs_loc)BFS_LOC_INIT) +#else +# define bfs_location() (&(const struct bfs_loc)BFS_LOC_INIT) +#endif + /** * Print a message to standard error and abort. */ -BFS_FORMATTER(1, 2) -noreturn void bfs_abortf(const char *format, ...); +BFS_FORMATTER(2, 3) +noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); /** * Unconditional abort with a message. */ -#define bfs_abort(...) \ - BFS_ABORT(__VA_ARGS__, "\n") - -#define BFS_ABORT(format, ...) \ - bfs_abortf((format) ? "%s: %s:%d:%s(): " format "%s" : "", BFS_COMMAND, __FILE__, __LINE__, __func__, __VA_ARGS__) +#define bfs_abort(...) bfs_abortf(bfs_location(), __VA_ARGS__) /** * Abort in debug builds; no-op in release builds. @@ -46,18 +62,20 @@ noreturn void bfs_abortf(const char *format, ...); # define bfs_bug bfs_abort #endif + + /** * Unconditional assert. */ #define bfs_verify(...) \ - BFS_VERIFY(#__VA_ARGS__, __VA_ARGS__, "", "\n") + bfs_verify_(#__VA_ARGS__, __VA_ARGS__, "", "") -#define BFS_VERIFY(str, cond, format, ...) \ - ((cond) ? (void)0 : bfs_abortf( \ +#define bfs_verify_(str, cond, format, ...) \ + ((cond) ? (void)0 : bfs_abort( \ sizeof(format) > 1 \ - ? "%s: %s:%d: %s(): %.0s" format "%s%s" \ - : "%s: %s:%d: %s(): Assertion failed: `%s`%s", \ - BFS_COMMAND, __FILE__, __LINE__, __func__, str, __VA_ARGS__)) + ? "%.0s" format "%s%s" \ + : "Assertion failed: `%s`%s", \ + str, __VA_ARGS__)) /** * Assert in debug builds; no-op in release builds. -- cgit v1.2.3 From 93749993508eaa9035dbcb005f960bc0b64752f0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 22 Jun 2023 17:02:31 -0400 Subject: tests/gnu/printf_times: Correct %T@ to %A@ --- tests/gnu/printf_times.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/gnu/printf_times.sh b/tests/gnu/printf_times.sh index d952620..e4f5155 100644 --- a/tests/gnu/printf_times.sh +++ b/tests/gnu/printf_times.sh @@ -1 +1 @@ -bfs_diff times -type f -printf '%p | %a %AY-%Am-%Ad %AH:%AI:%AS %T@ | %t %TY-%Tm-%Td %TH:%TI:%TS %T@\n' +bfs_diff times -type f -printf '%p | %a %AY-%Am-%Ad %AH:%AI:%AS %A@ | %t %TY-%Tm-%Td %TH:%TI:%TS %T@\n' -- cgit v1.2.3 From 5c3572dc323527a5c168cc12a31b730e0749002d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 24 Jun 2023 12:00:22 -0400 Subject: Unify macro naming conventions In particular, macros that decide whether to use a particular API/ dependency should be spelled BFS_USE_*, and should be configurable. --- Makefile | 20 ++++++++++---------- docs/BUILDING.md | 18 +++++++++--------- src/config.h | 6 ++++-- src/diag.h | 6 ++---- src/dir.c | 28 ++++++++++++++-------------- src/mtab.c | 28 ++++++++++++++-------------- src/parse.c | 4 ++-- src/stat.c | 16 +++++++++------- src/trie.c | 2 +- src/xregex.c | 14 +++++++------- 10 files changed, 72 insertions(+), 70 deletions(-) diff --git a/Makefile b/Makefile index d38f581..f7af9cc 100644 --- a/Makefile +++ b/Makefile @@ -104,11 +104,11 @@ LOCAL_CFLAGS += -fno-sanitize-recover=all endif ifndef NOLIBS -WITH_ONIGURUMA := y +USE_ONIGURUMA := y endif -ifdef WITH_ONIGURUMA -LOCAL_CPPFLAGS += -DBFS_WITH_ONIGURUMA=1 +ifdef USE_ONIGURUMA +LOCAL_CPPFLAGS += -DBFS_USE_ONIGURUMA=1 ONIG_CONFIG := $(shell command -v onig-config 2>/dev/null) ifdef ONIG_CONFIG @@ -120,28 +120,28 @@ endif LOCAL_CFLAGS += $(ONIG_CFLAGS) LOCAL_LDLIBS += $(ONIG_LDLIBS) -endif # WITH_ONIGURUMA +endif # USE_ONIGURUMA ifeq ($(OS),Linux) ifndef NOLIBS -WITH_ACL := y -WITH_ATTR := y -WITH_LIBCAP := y +USE_ACL := y +USE_ATTR := y +USE_LIBCAP := y endif -ifdef WITH_ACL +ifdef USE_ACL LOCAL_LDLIBS += -lacl else LOCAL_CPPFLAGS += -DBFS_USE_SYS_ACL_H=0 endif -ifdef WITH_ATTR +ifdef USE_ATTR LOCAL_LDLIBS += -lattr else LOCAL_CPPFLAGS += -DBFS_USE_SYS_XATTR_H=0 endif -ifdef WITH_LIBCAP +ifdef USE_LIBCAP LOCAL_LDLIBS += -lcap else LOCAL_CPPFLAGS += -DBFS_USE_SYS_CAPABILITY_H=0 diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 5219160..d0cb1fc 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -56,7 +56,7 @@ Here are some of the common ones; check the [`Makefile`](/Makefile) for more. | `CC` | The C compiler to use, e.g. `make CC=clang` | | `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | | `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | -| `WITH_ACL`
`WITH_ATTR`
... | Enable/disable [optional dependencies] | +| `USE_ACL`
`USE_ATTR`
... | Enable/disable [optional dependencies] | | `TEST_FLAGS` | `tests.sh` flags for `make check` | | `BUILDDIR` | The build output directory (default: `.`) | | `DESTDIR` | The root directory for `make install` | @@ -68,14 +68,14 @@ Here are some of the common ones; check the [`Makefile`](/Makefile) for more. ### Dependencies `bfs` depends on some system libraries for some of its features. -These dependencies are optional, and can be turned off at build time if necessary by setting the appropriate variable to the empty string (e.g. `make WITH_ONIGURUMA=`). - -| Dependency | Platforms | `make` flag | -|-------------|------------|------------------| -| [acl] | Linux only | `WITH_ACL` | -| [attr] | Linux only | `WITH_ATTR` | -| [libcap] | Linux only | `WITH_LIBCAP` | -| [Oniguruma] | All | `WITH_ONIGURUMA` | +These dependencies are optional, and can be turned off at build time if necessary by setting the appropriate variable to the empty string (e.g. `make USE_ONIGURUMA=`). + +| Dependency | Platforms | `make` flag | +|-------------|------------|-----------------| +| [acl] | Linux only | `USE_ACL` | +| [attr] | Linux only | `USE_ATTR` | +| [libcap] | Linux only | `USE_LIBCAP` | +| [Oniguruma] | All | `USE_ONIGURUMA` | [acl]: https://savannah.nongnu.org/projects/acl [attr]: https://savannah.nongnu.org/projects/attr diff --git a/src/config.h b/src/config.h index 1671a0d..79bd931 100644 --- a/src/config.h +++ b/src/config.h @@ -189,8 +189,10 @@ /** * Check if function multiversioning via GNU indirect functions (ifunc) is supported. */ -#if !defined(BFS_TARGET_CLONES) && __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__ || __NetBSD__) -# define BFS_TARGET_CLONES true +#ifndef BFS_USE_TARGET_CLONES +# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__ || __NetBSD__) +# define BFS_USE_TARGET_CLONES true +# endif #endif /** diff --git a/src/diag.h b/src/diag.h index 8d02da6..c909da5 100644 --- a/src/diag.h +++ b/src/diag.h @@ -18,8 +18,8 @@ #if __STDC_VERSION__ >= 202311L # define bfs_static_assert static_assert #else -# define bfs_static_assert(...) BFS_STATIC_ASSERT(__VA_ARGS__, #__VA_ARGS__, ) -# define BFS_STATIC_ASSERT(expr, msg, ...) _Static_assert(expr, msg) +# define bfs_static_assert(...) bfs_static_assert_(__VA_ARGS__, #__VA_ARGS__, ) +# define bfs_static_assert_(expr, msg, ...) _Static_assert(expr, msg) #endif /** @@ -62,8 +62,6 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); # define bfs_bug bfs_abort #endif - - /** * Unconditional assert. */ diff --git a/src/dir.c b/src/dir.c index a24b572..685bac5 100644 --- a/src/dir.c +++ b/src/dir.c @@ -15,11 +15,11 @@ #include #include -#ifndef BFS_GETDENTS -# define BFS_GETDENTS (__linux__ || __FreeBSD__) +#ifndef BFS_USE_GETDENTS +# define BFS_USE_GETDENTS (__linux__ || __FreeBSD__) #endif -#if BFS_GETDENTS +#if BFS_USE_GETDENTS # if __linux__ # include # endif @@ -43,9 +43,9 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { return ret; } -#endif // BFS_GETDENTS +#endif // BFS_USE_GETDENTS -#if BFS_GETDENTS && __linux__ +#if BFS_USE_GETDENTS && __linux__ /** Directory entry type for bfs_getdents() */ typedef struct dirent64 sys_dirent; #else @@ -101,7 +101,7 @@ enum bfs_type bfs_mode_to_type(mode_t mode) { } struct bfs_dir { -#if BFS_GETDENTS +#if BFS_USE_GETDENTS alignas(sys_dirent) int fd; unsigned short pos; unsigned short size; @@ -114,7 +114,7 @@ struct bfs_dir { bool eof; }; -#if BFS_GETDENTS +#if BFS_USE_GETDENTS # define DIR_SIZE (64 << 10) # define BUF_SIZE (DIR_SIZE - sizeof(struct bfs_dir)) #else @@ -143,7 +143,7 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { return -1; } -#if BFS_GETDENTS +#if BFS_USE_GETDENTS dir->fd = fd; dir->pos = 0; dir->size = 0; @@ -163,7 +163,7 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { } int bfs_dirfd(const struct bfs_dir *dir) { -#if BFS_GETDENTS +#if BFS_USE_GETDENTS return dir->fd; #else return dirfd(dir->dir); @@ -171,7 +171,7 @@ int bfs_dirfd(const struct bfs_dir *dir) { } int bfs_polldir(struct bfs_dir *dir) { -#if BFS_GETDENTS +#if BFS_USE_GETDENTS if (dir->pos < dir->size) { return 1; } else if (dir->eof) { @@ -203,7 +203,7 @@ int bfs_polldir(struct bfs_dir *dir) { } return 1; -#else // !BFS_GETDENTS +#else // !BFS_USE_GETDENTS if (dir->de) { return 1; } else if (dir->eof) { @@ -227,7 +227,7 @@ int bfs_polldir(struct bfs_dir *dir) { static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { int ret = bfs_polldir(dir); if (ret > 0) { -#if BFS_GETDENTS +#if BFS_USE_GETDENTS char *buf = (char *)(dir + 1); *de = (const sys_dirent *)(buf + dir->pos); dir->pos += (*de)->d_reclen; @@ -283,7 +283,7 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) { } int bfs_closedir(struct bfs_dir *dir) { -#if BFS_GETDENTS +#if BFS_USE_GETDENTS int ret = xclose(dir->fd); #else int ret = closedir(dir->dir); @@ -297,7 +297,7 @@ int bfs_closedir(struct bfs_dir *dir) { } int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd) { -#if BFS_GETDENTS +#if BFS_USE_GETDENTS int ret = dir->fd; #elif __FreeBSD__ int ret = fdclosedir(dir->dir); diff --git a/src/mtab.c b/src/mtab.c index e5c25ba..384fdfc 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -14,22 +14,22 @@ #include #include -#if BFS_USE_MNTENT_H -# define BFS_MNTENT 1 -#elif BSD -# define BFS_MNTINFO 1 -#elif __SVR4 -# define BFS_MNTTAB 1 +#if !defined(BFS_USE_MNTENT) && BFS_USE_MNTENT_H +# define BFS_USE_MNTENT true +#elif !defined(BFS_USE_MNTINFO) && BSD +# define BFS_USE_MNTINFO true +#elif !defined(BFS_USE_MNTTAB) && __SVR4 +# define BFS_USE_MNTTAB true #endif -#if BFS_MNTENT +#if BFS_USE_MNTENT # include # include # include -#elif BFS_MNTINFO +#elif BFS_USE_MNTINFO # include # include -#elif BFS_MNTTAB +#elif BFS_USE_MNTTAB # include # include #endif @@ -45,7 +45,7 @@ struct bfs_mtab_entry { }; struct bfs_mtab { - /** The list of mount points. */ + /** The array of mount points. */ struct bfs_mtab_entry *entries; /** The basenames of every mount point. */ struct trie names; @@ -59,7 +59,7 @@ struct bfs_mtab { /** * Add an entry to the mount table. */ -static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { +static inline int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { struct bfs_mtab_entry entry = { .path = strdup(path), .type = strdup(type), @@ -98,7 +98,7 @@ struct bfs_mtab *bfs_mtab_parse(void) { int error = 0; -#if BFS_MNTENT +#if BFS_USE_MNTENT FILE *file = setmntent(_PATH_MOUNTED, "r"); if (!file) { @@ -121,7 +121,7 @@ struct bfs_mtab *bfs_mtab_parse(void) { endmntent(file); -#elif BFS_MNTINFO +#elif BFS_USE_MNTINFO #if __NetBSD__ typedef struct statvfs bfs_statfs; @@ -143,7 +143,7 @@ struct bfs_mtab *bfs_mtab_parse(void) { } } -#elif BFS_MNTTAB +#elif BFS_USE_MNTTAB FILE *file = xfopen(MNTTAB, O_RDONLY | O_CLOEXEC); if (!file) { diff --git a/src/parse.c b/src/parse.c index cf4f696..1321270 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2296,7 +2296,7 @@ static struct bfs_expr *parse_regextype(struct parser_state *state, int arg1, in state->regex_type = BFS_REGEX_POSIX_BASIC; } else if (strcmp(type, "posix-extended") == 0) { state->regex_type = BFS_REGEX_POSIX_EXTENDED; -#if BFS_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA } else if (strcmp(type, "emacs") == 0) { state->regex_type = BFS_REGEX_EMACS; } else if (strcmp(type, "grep") == 0) { @@ -2318,7 +2318,7 @@ list_types: 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_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA cfprintf(cfile, " ${bld}emacs${rs}: Like ${grn}emacs${rs}\n"); cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n"); #endif diff --git a/src/stat.c b/src/stat.c index e3e5aaa..7a413cd 100644 --- a/src/stat.c +++ b/src/stat.c @@ -14,15 +14,17 @@ #include #if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30) -# define BFS_LIBC_STATX true +# define BFS_HAS_LIBC_STATX true #elif __linux__ # include # include # include #endif -#if BFS_LIBC_STATX || defined(SYS_statx) -# define BFS_STATX true +#ifndef BFS_USE_STATX +# if BFS_HAS_LIBC_STATX || defined(SYS_statx) +# define BFS_USE_STATX true +# endif #endif const char *bfs_stat_field_name(enum bfs_stat_field field) { @@ -128,13 +130,13 @@ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bf return ret; } -#if BFS_STATX +#if BFS_USE_STATX /** * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { -#if BFS_LIBC_STATX +#if BFS_HAS_LIBC_STATX int ret = statx(at_fd, at_path, at_flags, mask, buf); #else int ret = syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf); @@ -242,13 +244,13 @@ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct b return ret; } -#endif // BFS_STATX +#endif // BFS_USE_STATX /** * Calls the stat() implementation with explicit flags. */ static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x_flags, struct bfs_stat *buf) { -#if BFS_STATX +#if BFS_USE_STATX static atomic bool has_statx = true; if (load(&has_statx, relaxed)) { diff --git a/src/trie.c b/src/trie.c index 992d42b..0206509 100644 --- a/src/trie.c +++ b/src/trie.c @@ -94,7 +94,7 @@ bfs_static_assert(CHAR_WIDTH == 8); -#if BFS_TARGET_CLONES && (__i386__ || __x86_64__) +#if BFS_USE_TARGET_CLONES && (__i386__ || __x86_64__) # define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) #else # define TARGET_CLONES_POPCNT diff --git a/src/xregex.c b/src/xregex.c index ab5f793..88df082 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -11,7 +11,7 @@ #include #include -#if BFS_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA # include # include #else @@ -19,7 +19,7 @@ #endif struct bfs_regex { -#if BFS_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA unsigned char *pattern; OnigRegex impl; int err; @@ -30,7 +30,7 @@ struct bfs_regex { #endif }; -#if BFS_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA static int bfs_onig_status; static OnigEncoding bfs_onig_enc; @@ -121,7 +121,7 @@ int bfs_regcomp(struct bfs_regex **preg, const char *pattern, enum bfs_regex_typ return -1; } -#if BFS_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA // onig_error_code_to_str() says // // don't call this after the pattern argument of onig_new() is freed @@ -204,7 +204,7 @@ fail: int bfs_regexec(struct bfs_regex *regex, const char *str, enum bfs_regexec_flags flags) { size_t len = strlen(str); -#if BFS_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA const unsigned char *ustr = (const unsigned char *)str; const unsigned char *end = ustr + len; @@ -263,7 +263,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_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA onig_free(regex->impl); free(regex->pattern); #else @@ -278,7 +278,7 @@ char *bfs_regerror(const struct bfs_regex *regex) { return strdup(strerror(ENOMEM)); } -#if BFS_WITH_ONIGURUMA +#if BFS_USE_ONIGURUMA unsigned char *str = malloc(ONIG_MAX_ERROR_MESSAGE_LEN); if (str) { onig_error_code_to_str(str, regex->err, ®ex->einfo); -- cgit v1.2.3 From f0df110ba42a6d23cb222069e4c2a4712d48d9f1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 24 Jun 2023 12:47:09 -0400 Subject: bfstd: Add a getprogname() wrapper --- src/bfstd.c | 15 +++++++++++++++ src/bfstd.h | 8 ++++++++ src/diag.c | 12 +----------- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 0e8ba5f..1f4bbb2 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -143,6 +143,21 @@ char *xgetdelim(FILE *file, char delim) { } } +const char *xgetprogname(void) { + const char *cmd = NULL; +#if __GLIBC__ + cmd = program_invocation_short_name; +#elif BSD + cmd = getprogname(); +#endif + + if (!cmd) { + cmd = BFS_COMMAND; + } + + return cmd; +} + /** 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 cafe28f..ebbcdb9 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -105,6 +105,14 @@ char *xgetdelim(FILE *file, char delim); // #include +/** + * Wrapper for getprogname() or equivalent functionality. + * + * @return + * The basename of the currently running program. + */ +const char *xgetprogname(void); + /** * Process a yes/no prompt. * diff --git a/src/diag.c b/src/diag.c index 99b487a..acea9ad 100644 --- a/src/diag.c +++ b/src/diag.c @@ -14,17 +14,7 @@ #include noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { - const char *cmd = NULL; -#if __GLIBC__ - cmd = program_invocation_short_name; -#elif BSD - cmd = getprogname(); -#endif - if (!cmd) { - cmd = BFS_COMMAND; - } - - fprintf(stderr, "%s: %s@%s:%d: ", cmd, loc->func, loc->file, loc->line); + fprintf(stderr, "%s: %s@%s:%d: ", xgetprogname(), loc->func, loc->file, loc->line); va_list args; va_start(args, format); -- cgit v1.2.3 From 616392360afc3cfac55a24cc710e3607d8c4ee1d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 24 Jun 2023 13:22:38 -0400 Subject: build/tsan: Fix target_clones override --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f7af9cc..5d7461e 100644 --- a/Makefile +++ b/Makefile @@ -89,7 +89,7 @@ ifdef TSAN # tsan needs all code instrumented NOLIBS := y # https://github.com/google/sanitizers/issues/342 -LOCAL_CPPFLAGS += -DBFS_TARGET_CLONES=false +LOCAL_CPPFLAGS += -DBFS_USE_TARGET_CLONES=0 LOCAL_CFLAGS += -fsanitize=thread SANITIZE := y endif -- cgit v1.2.3 From 6b96d7b0ad73e6ed63cf5e32fd2544121e2b0284 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 26 Jun 2023 11:35:23 -0400 Subject: ioq: Don't check NDEBUG manually in ioqq_trypop() --- src/ioq.c | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 47b082a..617bd5f 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -248,12 +248,9 @@ static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { prev &= ~IOQ_BLOCKED; if (prev) { -#ifdef NDEBUG - store(&ioqq->tail, i + IOQ_STRIDE, relaxed); -#else - size_t j = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); + size_t j = exchange(&ioqq->tail, i + IOQ_STRIDE, relaxed); bfs_assert(j == i, "ioqq_trypop() only supports a single consumer"); -#endif + (void)j; } return (union ioq_cmd *)prev; -- cgit v1.2.3 From abd29143d805fa16c65489d5b1d79428943d0187 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 26 Jun 2023 11:47:41 -0400 Subject: ioq: New ioq_cancel() function --- src/bftw.c | 4 ++++ src/ioq.c | 27 ++++++++++++++++++++------- src/ioq.h | 5 +++++ 3 files changed, 29 insertions(+), 7 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 69e41a2..2bdf12d 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1147,6 +1147,10 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); + if (state->ioq) { + ioq_cancel(state->ioq); + } + SLIST_EXTEND(&state->files, &state->batch); do { bftw_gc(state, BFTW_VISIT_NONE); diff --git a/src/ioq.c b/src/ioq.c index 617bd5f..457ead7 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -264,6 +264,8 @@ struct ioq { size_t depth; /** The current size of the queue. */ size_t size; + /** Cancellation flag. */ + atomic bool cancel; /** ioq_cmd command arena. */ struct arena cmds; @@ -289,17 +291,22 @@ static void *ioq_work(void *ptr) { break; } + bool cancel = load(&ioq->cancel, relaxed); + struct ioq_req req = cmd->req; sanitize_uninit(cmd); struct ioq_res *res = &cmd->res; res->ptr = req.ptr; res->dir = req.dir; - res->error = 0; - if (bfs_opendir(req.dir, req.dfd, req.path) == 0) { - bfs_polldir(res->dir); - } else { + + if (cancel) { + res->error = EINTR; + } else if (bfs_opendir(req.dir, req.dfd, req.path) != 0) { res->error = errno; + } else { + res->error = 0; + bfs_polldir(res->dir); } ioqq_push(ioq->ready, cmd); @@ -394,14 +401,20 @@ void ioq_free(struct ioq *ioq, struct ioq_res *res) { arena_free(&ioq->cmds, (union ioq_cmd *)res); } +void ioq_cancel(struct ioq *ioq) { + if (!exchange(&ioq->cancel, true, relaxed)) { + for (size_t i = 0; i < ioq->nthreads; ++i) { + ioqq_push(ioq->pending, &IOQ_STOP); + } + } +} + void ioq_destroy(struct ioq *ioq) { if (!ioq) { return; } - for (size_t i = 0; i < ioq->nthreads; ++i) { - ioqq_push(ioq->pending, &IOQ_STOP); - } + ioq_cancel(ioq); for (size_t i = 0; i < ioq->nthreads; ++i) { if (pthread_join(ioq->threads[i], NULL) != 0) { diff --git a/src/ioq.h b/src/ioq.h index 50e02b1..064e2e2 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -88,6 +88,11 @@ struct ioq_res *ioq_trypop(struct ioq *ioq); */ void ioq_free(struct ioq *ioq, struct ioq_res *res); +/** + * Cancel any pending I/O operations. + */ +void ioq_cancel(struct ioq *ioq); + /** * Stop and destroy an I/O queue. */ -- cgit v1.2.3 From 1313875b02c690ca5a40e585d24fdec240bb419d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 26 Jun 2023 15:04:13 -0400 Subject: thread: Wrap more pthread APIs --- src/ioq.c | 9 ++---- src/lock.h | 85 ------------------------------------------------ src/main.c | 2 +- src/thread.h | 103 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/xregex.c | 5 ++- 5 files changed, 109 insertions(+), 95 deletions(-) delete mode 100644 src/lock.h create mode 100644 src/thread.h diff --git a/src/ioq.c b/src/ioq.c index 457ead7..1160b34 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -9,7 +9,7 @@ #include "config.h" #include "diag.h" #include "dir.h" -#include "lock.h" +#include "thread.h" #include "sanity.h" #include #include @@ -335,8 +335,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { } for (size_t i = 0; i < nthreads; ++i) { - errno = pthread_create(&ioq->threads[i], NULL, ioq_work, ioq); - if (errno != 0) { + if (thread_create(&ioq->threads[i], NULL, ioq_work, ioq) != 0) { goto fail; } ++ioq->nthreads; @@ -417,9 +416,7 @@ void ioq_destroy(struct ioq *ioq) { ioq_cancel(ioq); for (size_t i = 0; i < ioq->nthreads; ++i) { - if (pthread_join(ioq->threads[i], NULL) != 0) { - abort(); - } + thread_join(ioq->threads[i], NULL); } ioqq_destroy(ioq->ready); diff --git a/src/lock.h b/src/lock.h deleted file mode 100644 index 2b5f951..0000000 --- a/src/lock.h +++ /dev/null @@ -1,85 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -/** - * Wrappers for POSIX synchronization primitives. - */ - -#ifndef BFS_LOCK_H -#define BFS_LOCK_H - -#include "diag.h" -#include -#include -#include - -#define lock_verify(expr, cond) \ - bfs_verify((errno = (expr), (cond)), "%s: %s", #expr, strerror(errno)) - -/** - * Wrapper for pthread_mutex_init(). - * - * @return - * 0 on success, -1 on error. - */ -#define mutex_init(mutex, attr) \ - ((errno = pthread_mutex_init(mutex, attr)) ? -1 : 0) - -/** - * Wrapper for pthread_mutex_lock(). - */ -#define mutex_lock(mutex) \ - lock_verify(pthread_mutex_lock(mutex), errno == 0) - -/** - * Wrapper for pthread_mutex_trylock(). - * - * @return - * Whether the mutex was locked. - */ -#define mutex_trylock(mutex) \ - (lock_verify(pthread_mutex_trylock(mutex), errno == 0 || errno == EBUSY), errno == 0) - -/** - * Wrapper for pthread_mutex_unlock(). - */ -#define mutex_unlock(mutex) \ - lock_verify(pthread_mutex_unlock(mutex), errno == 0) - -/** - * Wrapper for pthread_mutex_destroy(). - */ -#define mutex_destroy(mutex) \ - lock_verify(pthread_mutex_destroy(mutex), errno == 0) - -/** - * Wrapper for pthread_cond_init(). - */ -#define cond_init(cond, attr) \ - ((errno = pthread_cond_init(cond, attr)) ? -1 : 0) - -/** - * Wrapper for pthread_cond_wait(). - */ -#define cond_wait(cond, mutex) \ - lock_verify(pthread_cond_wait(cond, mutex), errno == 0) - -/** - * Wrapper for pthread_cond_signal(). - */ -#define cond_signal(cond) \ - lock_verify(pthread_cond_signal(cond), errno == 0) - -/** - * Wrapper for pthread_cond_broadcast(). - */ -#define cond_broadcast(cond) \ - lock_verify(pthread_cond_broadcast(cond), errno == 0) - -/** - * Wrapper for pthread_cond_destroy(). - */ -#define cond_destroy(cond) \ - lock_verify(pthread_cond_destroy(cond), errno == 0) - -#endif // BFS_LOCK_H diff --git a/src/main.c b/src/main.c index b7a08c1..b26be85 100644 --- a/src/main.c +++ b/src/main.c @@ -34,11 +34,11 @@ * - fsade.[ch] (a facade over non-standard filesystem features) * - ioq.[ch] (an async I/O queue) * - list.h (linked list macros) - * - lock.h (mutexes, condition variables, etc.) * - mtab.[ch] (parses the system's mount table) * - pwcache.[ch] (a cache for the user/group tables) * - sanity.h (sanitizer interfaces) * - stat.[ch] (wraps stat(), or statx() on Linux) + * - thread.h (multi-threading) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) * - xregex.[ch] (regular expression support) diff --git a/src/thread.h b/src/thread.h new file mode 100644 index 0000000..b2edf17 --- /dev/null +++ b/src/thread.h @@ -0,0 +1,103 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Wrappers for POSIX threading APIs. + */ + +#ifndef BFS_THREAD_H +#define BFS_THREAD_H + +#include "diag.h" +#include +#include +#include + +#define thread_verify(expr, cond) \ + bfs_verify((errno = (expr), (cond)), "%s: %s", #expr, strerror(errno)) + +/** + * Wrapper for pthread_create(). + * + * @return + * 0 on success, -1 on error. + */ +#define thread_create(thread, attr, fn, arg) \ + ((errno = pthread_create(thread, attr, fn, arg)) ? -1 : 0) + +/** + * Wrapper for pthread_join(). + */ +#define thread_join(thread, ret) \ + thread_verify(pthread_join(thread, ret), errno == 0) + +/** + * Wrapper for pthread_mutex_init(). + */ +#define mutex_init(mutex, attr) \ + ((errno = pthread_mutex_init(mutex, attr)) ? -1 : 0) + +/** + * Wrapper for pthread_mutex_lock(). + */ +#define mutex_lock(mutex) \ + thread_verify(pthread_mutex_lock(mutex), errno == 0) + +/** + * Wrapper for pthread_mutex_trylock(). + * + * @return + * Whether the mutex was locked. + */ +#define mutex_trylock(mutex) \ + (thread_verify(pthread_mutex_trylock(mutex), errno == 0 || errno == EBUSY), errno == 0) + +/** + * Wrapper for pthread_mutex_unlock(). + */ +#define mutex_unlock(mutex) \ + thread_verify(pthread_mutex_unlock(mutex), errno == 0) + +/** + * Wrapper for pthread_mutex_destroy(). + */ +#define mutex_destroy(mutex) \ + thread_verify(pthread_mutex_destroy(mutex), errno == 0) + +/** + * Wrapper for pthread_cond_init(). + */ +#define cond_init(cond, attr) \ + ((errno = pthread_cond_init(cond, attr)) ? -1 : 0) + +/** + * Wrapper for pthread_cond_wait(). + */ +#define cond_wait(cond, mutex) \ + thread_verify(pthread_cond_wait(cond, mutex), errno == 0) + +/** + * Wrapper for pthread_cond_signal(). + */ +#define cond_signal(cond) \ + thread_verify(pthread_cond_signal(cond), errno == 0) + +/** + * Wrapper for pthread_cond_broadcast(). + */ +#define cond_broadcast(cond) \ + thread_verify(pthread_cond_broadcast(cond), errno == 0) + +/** + * Wrapper for pthread_cond_destroy(). + */ +#define cond_destroy(cond) \ + thread_verify(pthread_cond_destroy(cond), errno == 0) + +/** + * Wrapper for pthread_once(). + */ +#define call_once(once, fn) \ + thread_verify(pthread_once(once, fn), errno == 0) + +#endif // BFS_THREAD_H diff --git a/src/xregex.c b/src/xregex.c index 88df082..beb6676 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -5,6 +5,7 @@ #include "alloc.h" #include "config.h" #include "diag.h" +#include "thread.h" #include "sanity.h" #include #include @@ -106,9 +107,7 @@ static void bfs_onig_once(void) { /** Initialize Oniguruma. */ static int bfs_onig_initialize(OnigEncoding *enc) { static pthread_once_t once = PTHREAD_ONCE_INIT; - if (pthread_once(&once, bfs_onig_once) != 0) { - return ONIGERR_FAIL_TO_INITIALIZE; - } + call_once(&once, bfs_onig_once); *enc = bfs_onig_enc; return bfs_onig_status; -- cgit v1.2.3 From d9bcbaf71b0173fe44d7e07c05d110c49c07b6f2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 27 Jun 2023 11:52:24 -0400 Subject: tests: Get more tests passing with --bfs=tests/find-color.sh --- tests/ls-color.sh | 37 ++++++++++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/tests/ls-color.sh b/tests/ls-color.sh index 6d33f53..9fdd59c 100755 --- a/tests/ls-color.sh +++ b/tests/ls-color.sh @@ -7,17 +7,44 @@ set -e +function parse_ls_colors() { + for key; do + local -n var="$key" + if [[ "$LS_COLORS" =~ (^|:)$key=(([^:]|\\:)*) ]]; then + var="${BASH_REMATCH[2]}" + # Interpret escapes + var=$(printf "$var" | sed $'s/\^\[/\033/g; s/\\\\:/:/g') + fi + done +} + +function re_escape() { + # https://stackoverflow.com/a/29613573/502399 + sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$1" +} + +rs=0 +lc=$'\033[' +rc=m +ec= +no= + +parse_ls_colors rs lc rc ec no +: "${ec:=$lc$rs$rc}" + +strip="(($(re_escape "$lc$no$rc"))?($(re_escape "$ec")|$(re_escape "$lc$rc")))+" + +function ls_color() { + # Strip the leading reset sequence from the ls output + ls -1d --color "$@" | sed -E "s/^$strip([a-z].*)$strip/\4/; s/^$strip//" +} + L= if [ "$1" = "-L" ]; then L="$1" shift fi -function ls_color() { - # Strip the leading reset sequence from the ls output - ls -1d --color "$@" | sed $'s/^\033\\[0m//' -} - DIR="${1%/*}" if [ "$DIR" = "$1" ]; then ls_color "$1" -- cgit v1.2.3 From ec50b98d5584b0bb291a463a0c39905ce05acfe7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 27 Jun 2023 12:07:14 -0400 Subject: tests/bfs/color: Add mixed-case extensions --- tests/bfs/color.out | 11 ++++++++--- tests/bfs/color_L.out | 11 ++++++++--- tests/bfs/color_L_ln_target.out | 11 ++++++++--- tests/bfs/color_L_no_stat.out | 11 ++++++++--- tests/bfs/color_cd0_no.out | 11 ++++++++--- tests/bfs/color_escapes.out | 11 ++++++++--- tests/bfs/color_ext.out | 11 ++++++++--- tests/bfs/color_ext0.out | 11 ++++++++--- tests/bfs/color_ext_override.out | 11 ++++++++--- tests/bfs/color_ext_underride.out | 11 ++++++++--- tests/bfs/color_fi0_no.out | 11 ++++++++--- tests/bfs/color_fi_no.out | 11 ++++++++--- tests/bfs/color_ln_target.out | 11 ++++++++--- tests/bfs/color_mh.out | 11 ++++++++--- tests/bfs/color_mh0.out | 11 ++++++++--- tests/bfs/color_mi.out | 11 ++++++++--- tests/bfs/color_missing_colon.out | 11 ++++++++--- tests/bfs/color_no.out | 11 ++++++++--- tests/bfs/color_no_stat.out | 11 ++++++++--- tests/bfs/color_or.out | 11 ++++++++--- tests/bfs/color_or0_mi.out | 11 ++++++++--- tests/bfs/color_or0_mi0.out | 11 ++++++++--- tests/bfs/color_or_mi.out | 11 ++++++++--- tests/bfs/color_or_mi0.out | 11 ++++++++--- tests/bfs/color_rs_lc_rc_ec.out | 11 ++++++++--- tests/bfs/color_st0_tw0_ow.out | 11 ++++++++--- tests/bfs/color_st0_tw0_ow0.out | 11 ++++++++--- tests/bfs/color_st0_tw_ow.out | 11 ++++++++--- tests/bfs/color_st0_tw_ow0.out | 11 ++++++++--- tests/bfs/color_st_tw0_ow.out | 11 ++++++++--- tests/bfs/color_st_tw0_ow0.out | 11 ++++++++--- tests/bfs/color_st_tw_ow0.out | 11 ++++++++--- tests/bfs/color_star.out | 11 ++++++++--- tests/bfs/color_su0_sg.out | 11 ++++++++--- tests/bfs/color_su0_sg0.out | 11 ++++++++--- tests/bfs/color_su_sg0.out | 11 ++++++++--- tests/bfs/nocolor.out | 11 ++++++++--- tests/bfs/printf_color.out | 11 ++++++++--- tests/gnu/empty_special.out | 11 ++++++++--- tests/tests.sh | 4 +++- 40 files changed, 315 insertions(+), 118 deletions(-) diff --git a/tests/bfs/color.out b/tests/bfs/color.out index 77fc8a8..34c7153 100644 --- a/tests/bfs/color.out +++ b/tests/bfs/color.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_L.out b/tests/bfs/color_L.out index b60dd4a..89f9410 100644 --- a/tests/bfs/color_L.out +++ b/tests/bfs/color_L.out @@ -13,8 +13,13 @@ rainbow/file.dat rainbow/file.txt rainbow/link.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_L_ln_target.out b/tests/bfs/color_L_ln_target.out index cd4ec5e..2562c98 100644 --- a/tests/bfs/color_L_ln_target.out +++ b/tests/bfs/color_L_ln_target.out @@ -13,8 +13,13 @@ rainbow/file.dat rainbow/file.txt rainbow/link.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_L_no_stat.out b/tests/bfs/color_L_no_stat.out index c0bb1be..4fe99c0 100644 --- a/tests/bfs/color_L_no_stat.out +++ b/tests/bfs/color_L_no_stat.out @@ -10,11 +10,16 @@ rainbow/pipe rainbow/exec.sh rainbow/file.dat +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 rainbow/sgid -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz rainbow/sugid rainbow/suid +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_cd0_no.out b/tests/bfs/color_cd0_no.out index d5c98a9..30ad97f 100644 --- a/tests/bfs/color_cd0_no.out +++ b/tests/bfs/color_cd0_no.out @@ -5,11 +5,16 @@ rainbow/file.dat rainbow/file.txt rainbow/link.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ rainbow/sticky_ow rainbow/sgid rainbow/pipe diff --git a/tests/bfs/color_escapes.out b/tests/bfs/color_escapes.out index b71e138..808585e 100644 --- a/tests/bfs/color_escapes.out +++ b/tests/bfs/color_escapes.out @@ -13,8 +13,13 @@ :rainbow/:sticky :rainbow/file.dat :rainbow/file.txt +:rainbow/lower.gz +:rainbow/lower.tar +:rainbow/lower.tar.gz +:rainbow/lu.tar.GZ :rainbow/mh1 :rainbow/mh2 -:rainbow/star.gz -:rainbow/star.tar -:rainbow/star.tar.gz +:rainbow/ul.TAR.gz +:rainbow/upper.GZ +:rainbow/upper.TAR +:rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_ext.out b/tests/bfs/color_ext.out index cf26e73..be22b82 100644 --- a/tests/bfs/color_ext.out +++ b/tests/bfs/color_ext.out @@ -13,8 +13,13 @@ rainbow/suid rainbow/sticky rainbow/file.dat +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_ext0.out b/tests/bfs/color_ext0.out index e764a6b..d151319 100644 --- a/tests/bfs/color_ext0.out +++ b/tests/bfs/color_ext0.out @@ -13,8 +13,13 @@ rainbow/suid rainbow/sticky rainbow/file.dat +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_ext_override.out b/tests/bfs/color_ext_override.out index 1377b65..3a09cd2 100644 --- a/tests/bfs/color_ext_override.out +++ b/tests/bfs/color_ext_override.out @@ -1,8 +1,13 @@ rainbow rainbow/exec.sh -rainbow/star.tar -rainbow/star.gz -rainbow/star.tar.gz +rainbow/lower.tar +rainbow/upper.TAR +rainbow/lower.gz +rainbow/lower.tar.gz +rainbow/lu.tar.GZ +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR.GZ rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ext_underride.out b/tests/bfs/color_ext_underride.out index 787248a..073d176 100644 --- a/tests/bfs/color_ext_underride.out +++ b/tests/bfs/color_ext_underride.out @@ -1,8 +1,13 @@ rainbow -rainbow/star.tar.gz +rainbow/lower.tar.gz +rainbow/lu.tar.GZ +rainbow/ul.TAR.gz +rainbow/upper.TAR.GZ rainbow/exec.sh -rainbow/star.tar -rainbow/star.gz +rainbow/lower.tar +rainbow/upper.TAR +rainbow/lower.gz +rainbow/upper.GZ rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_fi0_no.out b/tests/bfs/color_fi0_no.out index 77fc8a8..34c7153 100644 --- a/tests/bfs/color_fi0_no.out +++ b/tests/bfs/color_fi0_no.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_fi_no.out b/tests/bfs/color_fi_no.out index 7162ded..8df9355 100644 --- a/tests/bfs/color_fi_no.out +++ b/tests/bfs/color_fi_no.out @@ -6,11 +6,16 @@ rainbow/link.txt rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ rainbow/sticky_ow rainbow/sgid rainbow/pipe diff --git a/tests/bfs/color_ln_target.out b/tests/bfs/color_ln_target.out index cd4ec5e..2562c98 100644 --- a/tests/bfs/color_ln_target.out +++ b/tests/bfs/color_ln_target.out @@ -13,8 +13,13 @@ rainbow/file.dat rainbow/file.txt rainbow/link.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_mh.out b/tests/bfs/color_mh.out index 757a6a1..7521b31 100644 --- a/tests/bfs/color_mh.out +++ b/tests/bfs/color_mh.out @@ -15,6 +15,11 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_mh0.out b/tests/bfs/color_mh0.out index 77fc8a8..34c7153 100644 --- a/tests/bfs/color_mh0.out +++ b/tests/bfs/color_mh0.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_mi.out b/tests/bfs/color_mi.out index 77fc8a8..34c7153 100644 --- a/tests/bfs/color_mi.out +++ b/tests/bfs/color_mi.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_missing_colon.out b/tests/bfs/color_missing_colon.out index cf26e73..be22b82 100644 --- a/tests/bfs/color_missing_colon.out +++ b/tests/bfs/color_missing_colon.out @@ -13,8 +13,13 @@ rainbow/suid rainbow/sticky rainbow/file.dat +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_no.out b/tests/bfs/color_no.out index 9f0dd66..b70e47d 100644 --- a/tests/bfs/color_no.out +++ b/tests/bfs/color_no.out @@ -6,11 +6,16 @@ rainbow/link.txt rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ rainbow/sticky_ow rainbow/sgid rainbow/pipe diff --git a/tests/bfs/color_no_stat.out b/tests/bfs/color_no_stat.out index 1fc5324..7d7d767 100644 --- a/tests/bfs/color_no_stat.out +++ b/tests/bfs/color_no_stat.out @@ -10,11 +10,16 @@ rainbow/pipe rainbow/exec.sh rainbow/file.dat +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 rainbow/sgid -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz rainbow/sugid rainbow/suid +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_or.out b/tests/bfs/color_or.out index 9e1fe5c..98efb63 100644 --- a/tests/bfs/color_or.out +++ b/tests/bfs/color_or.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_or0_mi.out b/tests/bfs/color_or0_mi.out index 77fc8a8..34c7153 100644 --- a/tests/bfs/color_or0_mi.out +++ b/tests/bfs/color_or0_mi.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_or0_mi0.out b/tests/bfs/color_or0_mi0.out index 77fc8a8..34c7153 100644 --- a/tests/bfs/color_or0_mi0.out +++ b/tests/bfs/color_or0_mi0.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_or_mi.out b/tests/bfs/color_or_mi.out index 5667f56..a9dc229 100644 --- a/tests/bfs/color_or_mi.out +++ b/tests/bfs/color_or_mi.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_or_mi0.out b/tests/bfs/color_or_mi0.out index 5667f56..a9dc229 100644 --- a/tests/bfs/color_or_mi0.out +++ b/tests/bfs/color_or_mi0.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_rs_lc_rc_ec.out b/tests/bfs/color_rs_lc_rc_ec.out index d39bbe7..82d94ec 100644 --- a/tests/bfs/color_rs_lc_rc_ec.out +++ b/tests/bfs/color_rs_lc_rc_ec.out @@ -12,9 +12,14 @@ LC01;34RCrainbow/ECLC37;41RCsuidEC LC01;34RCrainbow/ECLC37;44RCstickyEC LC01;34RCrainbow/ECfile.dat LC01;34RCrainbow/ECfile.txt +LC01;34RCrainbow/EClower.gz +LC01;34RCrainbow/EClower.tar +LC01;34RCrainbow/EClower.tar.gz +LC01;34RCrainbow/EClu.tar.GZ LC01;34RCrainbow/ECmh1 LC01;34RCrainbow/ECmh2 -LC01;34RCrainbow/ECstar.gz -LC01;34RCrainbow/ECstar.tar -LC01;34RCrainbow/ECstar.tar.gz +LC01;34RCrainbow/ECul.TAR.gz +LC01;34RCrainbow/ECupper.GZ +LC01;34RCrainbow/ECupper.TAR +LC01;34RCrainbow/ECupper.TAR.GZ LC01;34RCrainbowEC diff --git a/tests/bfs/color_st0_tw0_ow.out b/tests/bfs/color_st0_tw0_ow.out index 9a47ef2..bdc5942 100644 --- a/tests/bfs/color_st0_tw0_ow.out +++ b/tests/bfs/color_st0_tw0_ow.out @@ -13,8 +13,13 @@ rainbow/suid rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_st0_tw0_ow0.out b/tests/bfs/color_st0_tw0_ow0.out index 2b86fe4..f13b7f3 100644 --- a/tests/bfs/color_st0_tw0_ow0.out +++ b/tests/bfs/color_st0_tw0_ow0.out @@ -13,8 +13,13 @@ rainbow/suid rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_st0_tw_ow.out b/tests/bfs/color_st0_tw_ow.out index 42549a1..2d7f682 100644 --- a/tests/bfs/color_st0_tw_ow.out +++ b/tests/bfs/color_st0_tw_ow.out @@ -13,8 +13,13 @@ rainbow/sticky_ow rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_st0_tw_ow0.out b/tests/bfs/color_st0_tw_ow0.out index 535b8ae..7e343b8 100644 --- a/tests/bfs/color_st0_tw_ow0.out +++ b/tests/bfs/color_st0_tw_ow0.out @@ -13,8 +13,13 @@ rainbow/sticky_ow rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_st_tw0_ow.out b/tests/bfs/color_st_tw0_ow.out index c9a86f4..c61a327 100644 --- a/tests/bfs/color_st_tw0_ow.out +++ b/tests/bfs/color_st_tw0_ow.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_st_tw0_ow0.out b/tests/bfs/color_st_tw0_ow0.out index 2d94f3a..929a993 100644 --- a/tests/bfs/color_st_tw0_ow0.out +++ b/tests/bfs/color_st_tw0_ow0.out @@ -13,8 +13,13 @@ rainbow/sticky_ow rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_st_tw_ow0.out b/tests/bfs/color_st_tw_ow0.out index 317ef90..7092f5a 100644 --- a/tests/bfs/color_st_tw_ow0.out +++ b/tests/bfs/color_st_tw_ow0.out @@ -13,8 +13,13 @@ rainbow/sticky_ow rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_star.out b/tests/bfs/color_star.out index 77fc8a8..34c7153 100644 --- a/tests/bfs/color_star.out +++ b/tests/bfs/color_star.out @@ -13,8 +13,13 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_su0_sg.out b/tests/bfs/color_su0_sg.out index 8b8c8b8..2bce534 100644 --- a/tests/bfs/color_su0_sg.out +++ b/tests/bfs/color_su0_sg.out @@ -12,9 +12,14 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz rainbow/suid +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_su0_sg0.out b/tests/bfs/color_su0_sg0.out index 0cd5f9a..0c3d757 100644 --- a/tests/bfs/color_su0_sg0.out +++ b/tests/bfs/color_su0_sg0.out @@ -10,11 +10,16 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 rainbow/sgid -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz rainbow/sugid rainbow/suid +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_su_sg0.out b/tests/bfs/color_su_sg0.out index a9e8c5d..7a70598 100644 --- a/tests/bfs/color_su_sg0.out +++ b/tests/bfs/color_su_sg0.out @@ -12,9 +12,14 @@ rainbow/sticky rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 rainbow/sgid -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/nocolor.out b/tests/bfs/nocolor.out index b53fe03..29e7de8 100644 --- a/tests/bfs/nocolor.out +++ b/tests/bfs/nocolor.out @@ -5,16 +5,21 @@ rainbow/exec.sh rainbow/file.dat rainbow/file.txt rainbow/link.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 rainbow/ow rainbow/pipe rainbow/sgid rainbow/socket -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz rainbow/sticky rainbow/sticky_ow rainbow/sugid rainbow/suid +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/printf_color.out b/tests/bfs/printf_color.out index d9cd1a4..0468f7d 100644 --- a/tests/bfs/printf_color.out +++ b/tests/bfs/printf_color.out @@ -13,8 +13,13 @@ . ./rainbow sticky ./rainbow/sticky rainbow/sticky . ./rainbow file.dat ./rainbow/file.dat rainbow/file.dat . ./rainbow file.txt ./rainbow/file.txt rainbow/file.txt +. ./rainbow lower.gz ./rainbow/lower.gz rainbow/lower.gz +. ./rainbow lower.tar ./rainbow/lower.tar rainbow/lower.tar +. ./rainbow lower.tar.gz ./rainbow/lower.tar.gz rainbow/lower.tar.gz +. ./rainbow lu.tar.GZ ./rainbow/lu.tar.GZ rainbow/lu.tar.GZ . ./rainbow mh1 ./rainbow/mh1 rainbow/mh1 . ./rainbow mh2 ./rainbow/mh2 rainbow/mh2 -. ./rainbow star.gz ./rainbow/star.gz rainbow/star.gz -. ./rainbow star.tar ./rainbow/star.tar rainbow/star.tar -. ./rainbow star.tar.gz ./rainbow/star.tar.gz rainbow/star.tar.gz +. ./rainbow ul.TAR.gz ./rainbow/ul.TAR.gz rainbow/ul.TAR.gz +. ./rainbow upper.GZ ./rainbow/upper.GZ rainbow/upper.GZ +. ./rainbow upper.TAR ./rainbow/upper.TAR rainbow/upper.TAR +. ./rainbow upper.TAR.GZ ./rainbow/upper.TAR.GZ rainbow/upper.TAR.GZ diff --git a/tests/gnu/empty_special.out b/tests/gnu/empty_special.out index 3927f2b..3aa57d2 100644 --- a/tests/gnu/empty_special.out +++ b/tests/gnu/empty_special.out @@ -1,14 +1,19 @@ rainbow/exec.sh rainbow/file.dat rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ rainbow/mh1 rainbow/mh2 rainbow/ow rainbow/sgid -rainbow/star.gz -rainbow/star.tar -rainbow/star.tar.gz rainbow/sticky rainbow/sticky_ow rainbow/sugid rainbow/suid +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/tests.sh b/tests/tests.sh index 9265481..46e3e33 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -413,7 +413,9 @@ make_deep "$TMP/deep" function make_rainbow() { "$XTOUCH" -p "$1/file.txt" "$XTOUCH" -p "$1/file.dat" - "$XTOUCH" -p "$1/star".{gz,tar,tar.gz} + "$XTOUCH" -p "$1/lower".{gz,tar,tar.gz} + "$XTOUCH" -p "$1/upper".{GZ,TAR,TAR.GZ} + "$XTOUCH" -p "$1/lu.tar.GZ" "$1/ul.TAR.gz" ln -s file.txt "$1/link.txt" "$XTOUCH" -p "$1/mh1" ln "$1/mh1" "$1/mh2" -- cgit v1.2.3 From 4ad724391daeff5b86ad420f1e1a8b35d65fd7e0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Jun 2023 13:52:39 -0400 Subject: dstring: Allow dstreserve(NULL, n) --- src/dstring.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/dstring.c b/src/dstring.c index 7ca74d0..f019ba9 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -3,6 +3,7 @@ #include "dstring.h" #include "alloc.h" +#include "bit.h" #include "diag.h" #include #include @@ -67,10 +68,15 @@ size_t dstrlen(const char *dstr) { } int dstreserve(char **dstr, size_t capacity) { + if (!*dstr) { + *dstr = dstralloc(capacity); + return *dstr ? 0 : -1; + } + struct dstring *header = dstrheader(*dstr); if (capacity > header->capacity) { - capacity *= 2; + capacity = bit_ceil(capacity + 1) - 1; header = realloc(header, dstrsize(capacity)); if (!header) { -- cgit v1.2.3 From 174a2027ff0db579e18d5efacb17c3addf6473f6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Jun 2023 13:53:03 -0400 Subject: dstring: Add some exact-size utility functions --- src/dstring.c | 57 ++++++++++++++++++++++++---------- src/dstring.h | 99 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 135 insertions(+), 21 deletions(-) diff --git a/src/dstring.c b/src/dstring.c index f019ba9..dada70b 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -54,12 +54,18 @@ char *dstralloc(size_t capacity) { } char *dstrdup(const char *str) { - size_t len = strlen(str); - return dstralloc_impl(len, len, str); + return dstrxdup(str, strlen(str)); } char *dstrndup(const char *str, size_t n) { - size_t len = strnlen(str, n); + return dstrxdup(str, strnlen(str, n)); +} + +char *dstrddup(const char *dstr) { + return dstrxdup(dstr, dstrlen(dstr)); +} + +char *dstrxdup(const char *str, size_t len) { return dstralloc_impl(len, len, str); } @@ -98,37 +104,56 @@ int dstresize(char **dstr, size_t length) { struct dstring *header = dstrheader(*dstr); header->length = length; header->data[length] = '\0'; - return 0; } -/** Common implementation of dstr{cat,ncat,app}. */ -static int dstrcat_impl(char **dest, const char *src, size_t srclen) { +int dstrcat(char **dest, const char *src) { + return dstrxcat(dest, src, strlen(src)); +} + +int dstrncat(char **dest, const char *src, size_t n) { + return dstrxcat(dest, src, strnlen(src, n)); +} + +int dstrdcat(char **dest, const char *src) { + return dstrxcat(dest, src, dstrlen(src)); +} + +int dstrxcat(char **dest, const char *src, size_t len) { size_t oldlen = dstrlen(*dest); - size_t newlen = oldlen + srclen; + size_t newlen = oldlen + len; if (dstresize(dest, newlen) != 0) { return -1; } - memcpy(*dest + oldlen, src, srclen); + memcpy(*dest + oldlen, src, len); return 0; } -int dstrcat(char **dest, const char *src) { - return dstrcat_impl(dest, src, strlen(src)); +int dstrapp(char **str, char c) { + return dstrxcat(str, &c, 1); } -int dstrncat(char **dest, const char *src, size_t n) { - return dstrcat_impl(dest, src, strnlen(src, n)); +int dstrcpy(char **dest, const char *src) { + return dstrxcpy(dest, src, strlen(src)); } -int dstrdcat(char **dest, const char *src) { - return dstrcat_impl(dest, src, dstrlen(src)); +int dstrncpy(char **dest, const char *src, size_t n) { + return dstrxcpy(dest, src, strnlen(src, n)); } -int dstrapp(char **str, char c) { - return dstrcat_impl(str, &c, 1); +int dstrdcpy(char **dest, const char *src) { + return dstrxcpy(dest, src, dstrlen(src)); +} + +int dstrxcpy(char **dest, const char *src, size_t len) { + if (dstresize(dest, len) != 0) { + return -1; + } + + memcpy(*dest, src, len); + return 0; } char *dstrprintf(const char *format, ...) { diff --git a/src/dstring.h b/src/dstring.h index ee3b345..2673f1b 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -38,12 +38,31 @@ char *dstrdup(const char *str); */ char *dstrndup(const char *str, size_t n); +/** + * Create a dynamic copy of a dynamic string. + * + * @param dstr + * The dynamic string to copy. + */ +char *dstrddup(const char *dstr); + +/** + * Create an exact-sized dynamic copy of a string. + * + * @param str + * The string to copy. + * @param len + * The length of the string, which may include internal NUL bytes. + */ +char *dstrxdup(const char *str, size_t len); + /** * Get a dynamic string's length. * * @param dstr * The string to measure. - * @return The length of dstr. + * @return + * The length of dstr. */ size_t dstrlen(const char *dstr); @@ -54,7 +73,8 @@ size_t dstrlen(const char *dstr); * The dynamic string to preallocate. * @param capacity * The new capacity for the string. - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int dstreserve(char **dstr, size_t capacity); @@ -65,7 +85,8 @@ int dstreserve(char **dstr, size_t capacity); * The dynamic string to resize. * @param length * The new length for the dynamic string. - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int dstresize(char **dstr, size_t length); @@ -89,7 +110,8 @@ int dstrcat(char **dest, const char *src); * The string to append. * @param n * The maximum number of characters to take from src. - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int dstrncat(char **dest, const char *src, size_t n); @@ -105,6 +127,20 @@ int dstrncat(char **dest, const char *src, size_t n); */ int dstrdcat(char **dest, const char *src); +/** + * Append to a dynamic string. + * + * @param dest + * The destination dynamic string. + * @param src + * The string to append. + * @param len + * The exact number of characters to take from src. + * @return + * 0 on success, -1 on failure. + */ +int dstrxcat(char **dest, const char *src, size_t len); + /** * Append a single character to a dynamic string. * @@ -112,10 +148,63 @@ int dstrdcat(char **dest, const char *src); * The string to append to. * @param c * The character to append. - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int dstrapp(char **str, char c); +/** + * Copy a string into a dynamic string. + * + * @param dest + * The destination dynamic string. + * @param src + * The string to copy. + * @returns + * 0 on success, -1 on failure. + */ +int dstrcpy(char **dest, const char *str); + +/** + * Copy a dynamic string into another one. + * + * @param dest + * The destination dynamic string. + * @param src + * The dynamic string to copy. + * @returns + * 0 on success, -1 on failure. + */ +int dstrdcpy(char **dest, const char *str); + +/** + * Copy a string into a dynamic string. + * + * @param dest + * The destination dynamic string. + * @param src + * The dynamic string to copy. + * @param n + * The maximum number of characters to take from src. + * @returns + * 0 on success, -1 on failure. + */ +int dstrncpy(char **dest, const char *str, size_t n); + +/** + * Copy a string into a dynamic string. + * + * @param dest + * The destination dynamic string. + * @param src + * The dynamic string to copy. + * @param len + * The exact number of characters to take from src. + * @returns + * 0 on success, -1 on failure. + */ +int dstrxcpy(char **dest, const char *str, size_t len); + /** * Create a dynamic string from a format string. * -- cgit v1.2.3 From 32cd0cbacfc6a4637915b40b7781976731599435 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Jun 2023 13:53:48 -0400 Subject: parse: Warn about errors parsing $LS_COLORS --- src/parse.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/parse.c b/src/parse.c index 1321270..623a528 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1126,7 +1126,7 @@ static struct bfs_expr *parse_color(struct parser_state *state, int color, int a if (color) { if (!colors) { - parse_expr_error(state, expr, "%s.\n", strerror(ctx->colors_error)); + parse_expr_error(state, expr, "Error parsing $$LS_COLORS: %s.\n", strerror(ctx->colors_error)); bfs_expr_free(expr); return NULL; } @@ -3725,6 +3725,10 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } } + if (state.use_color == COLOR_AUTO && !ctx->colors) { + bfs_warning(ctx, "Error parsing $$LS_COLORS: %s.\n\n", strerror(ctx->colors_error)); + } + if (bfs_optimize(ctx) != 0) { goto fail; } -- cgit v1.2.3 From 27dc7a126d6c00b7a41e0559254928555200ee42 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Jun 2023 15:17:27 -0400 Subject: color: Implement smart casing Since coreutils 9.2, ls does case-sensitive extension matching if the same extension is capitalized differently in $LS_COLORS. Implement the same logic. Link: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=33123 Link: https://debbugs.gnu.org/cgi/bugreport.cgi?bug=9086 --- src/color.c | 591 +++++++++++++++++++++++++++---------------- tests/bfs/color_ext_case.out | 25 ++ tests/bfs/color_ext_case.sh | 1 + tests/bfs/color_nul.out | Bin 20 -> 908 bytes tests/bfs/color_nul.sh | 3 +- tests/bfs/color_star.out | 25 -- tests/bfs/color_star.sh | 2 +- 7 files changed, 401 insertions(+), 246 deletions(-) create mode 100644 tests/bfs/color_ext_case.out create mode 100644 tests/bfs/color_ext_case.sh delete mode 100644 tests/bfs/color_star.out diff --git a/src/color.c b/src/color.c index b54ad53..82ff763 100644 --- a/src/color.c +++ b/src/color.c @@ -22,69 +22,122 @@ #include #include +/** + * An escape sequence, which may contain embedded NUL bytes. + */ +struct esc_seq { + /** The length of the escape sequence. */ + size_t len; + /** The escape sequence iteself, without a terminating NUL. */ + char seq[]; +}; + +/** + * A colored file extension, like `*.tar=01;31`. + */ +struct ext_color { + /** Priority, to disambiguate case-sensitive and insensitive matches. */ + size_t priority; + /** The escape sequence associated with this extension. */ + struct esc_seq *esc; + /** The length of the extension to match. */ + size_t len; + /** Whether the comparison should be case-insensitive. */ + bool icase; + /** The extension to match (NUL-terminated). */ + char ext[]; +}; + struct colors { - char *reset; - char *leftcode; - char *rightcode; - char *endcode; - char *clear_to_eol; - - char *bold; - char *gray; - char *red; - char *green; - char *yellow; - char *blue; - char *magenta; - char *cyan; - char *white; - - char *warning; - char *error; - - char *normal; - - char *file; - char *multi_hard; - char *executable; - char *capable; - char *setgid; - char *setuid; - - char *directory; - char *sticky; - char *other_writable; - char *sticky_other_writable; - - char *link; - char *orphan; - char *missing; + /** esc_seq allocator. */ + struct varena esc_arena; + /** ext_color allocator. */ + struct varena ext_arena; + + // Known dircolors keys + + struct esc_seq *reset; + struct esc_seq *leftcode; + struct esc_seq *rightcode; + struct esc_seq *endcode; + struct esc_seq *clear_to_eol; + + struct esc_seq *bold; + struct esc_seq *gray; + struct esc_seq *red; + struct esc_seq *green; + struct esc_seq *yellow; + struct esc_seq *blue; + struct esc_seq *magenta; + struct esc_seq *cyan; + struct esc_seq *white; + + struct esc_seq *warning; + struct esc_seq *error; + + struct esc_seq *normal; + + struct esc_seq *file; + struct esc_seq *multi_hard; + struct esc_seq *executable; + struct esc_seq *capable; + struct esc_seq *setgid; + struct esc_seq *setuid; + + struct esc_seq *directory; + struct esc_seq *sticky; + struct esc_seq *other_writable; + struct esc_seq *sticky_other_writable; + + struct esc_seq *link; + struct esc_seq *orphan; + struct esc_seq *missing; bool link_as_target; - char *blockdev; - char *chardev; - char *door; - char *pipe; - char *socket; + struct esc_seq *blockdev; + struct esc_seq *chardev; + struct esc_seq *door; + struct esc_seq *pipe; + struct esc_seq *socket; /** A mapping from color names (fi, di, ln, etc.) to struct fields. */ struct trie names; - /** A mapping from file extensions to colors. */ - struct trie ext_colors; + /** Number of extensions. */ + size_t ext_count; + /** Case-sensitive extension trie. */ + struct trie ext_trie; + /** Case-insensitive extension trie. */ + struct trie iext_trie; }; +/** Allocate an escape sequence. */ +static struct esc_seq *new_esc(struct colors *colors, const char *seq, size_t len) { + struct esc_seq *esc = varena_alloc(&colors->esc_arena, len); + if (esc) { + esc->len = len; + memcpy(esc->seq, seq, len); + } + return esc; +} + +/** Free an escape sequence. */ +static void free_esc(struct colors *colors, struct esc_seq *seq) { + varena_free(&colors->esc_arena, seq, seq->len); +} + /** Initialize a color in the table. */ -static int init_color(struct colors *colors, const char *name, const char *value, char **field) { +static int init_esc(struct colors *colors, const char *name, const char *value, struct esc_seq **field) { + struct esc_seq *esc = NULL; if (value) { - *field = dstrdup(value); - if (!*field) { + esc = new_esc(colors, value, strlen(value)); + if (!esc) { return -1; } - } else { - *field = NULL; } + *field = esc; + struct trie_leaf *leaf = trie_insert_str(&colors->names, name); if (leaf) { leaf->value = field; @@ -94,100 +147,182 @@ static int init_color(struct colors *colors, const char *name, const char *value } } -/** Get a color from the table. */ -static char **get_color(const struct colors *colors, const char *name) { +/** Get an escape sequence from the table. */ +static struct esc_seq **get_esc(const struct colors *colors, const char *name) { const struct trie_leaf *leaf = trie_find_str(&colors->names, name); - if (leaf) { - return (char **)leaf->value; - } else { - return NULL; - } + return leaf ? leaf->value : NULL; } -/** Set the value of a color. */ -static int set_color(struct colors *colors, const char *name, char *value) { - char **color = get_color(colors, name); - if (color) { - dstrfree(*color); - *color = value; +/** Set a named escape sequence. */ +static int set_esc(struct colors *colors, const char *name, char *value) { + struct esc_seq **field = get_esc(colors, name); + if (!field) { return 0; - } else { - return -1; } + + if (*field) { + free_esc(colors, *field); + *field = NULL; + } + + if (value) { + *field = new_esc(colors, value, dstrlen(value)); + if (!*field) { + return -1; + } + } + + return 0; } -/** - * Transform a file extension for fast lookups, by reversing and lowercasing it. - */ -static void extxfrm(char *ext, size_t len) { - for (size_t i = 0; i < len - i; ++i) { - char a = ext[i]; - char b = ext[len - i - 1]; +/** Reverse a string, to turn suffix matches into prefix matches. */ +static void ext_reverse(char *ext, size_t len) { + for (size_t i = 0, j = len - 1; len && i < j; ++i, --j) { + char c = ext[i]; + ext[i] = ext[j]; + ext[j] = c; + } +} + +/** Convert a string to lowercase for case-insensitive matching. */ +static void ext_tolower(char *ext, size_t len) { + for (size_t i = 0; i < len; ++i) { + char c = ext[i]; // What's internationalization? Doesn't matter, this is what // GNU ls does. Luckily, since there's no standard C way to // casefold. Not using tolower() here since it respects the // current locale, which GNU ls doesn't do. - if (a >= 'A' && a <= 'Z') { - a += 'a' - 'A'; - } - if (b >= 'A' && b <= 'Z') { - b += 'a' - 'A'; + if (c >= 'A' && c <= 'Z') { + c += 'a' - 'A'; } - ext[i] = b; - ext[len - i - 1] = a; + ext[i] = c; } } /** Maximum supported extension length. */ #define EXT_MAX 255 -/** - * Set the color for an extension. - */ -static int set_ext_color(struct colors *colors, char *key, char *value) { +/** Set the color for an extension. */ +static int set_ext(struct colors *colors, char *key, char *value) { size_t len = dstrlen(key); - if (len > EXT_MAX) { + struct ext_color *ext = varena_alloc(&colors->ext_arena, len + 1); + if (!ext) { return -1; } - extxfrm(key, len); + ext->priority = colors->ext_count++; + ext->len = len; + ext->icase = true; + ext->esc = new_esc(colors, value, dstrlen(value)); + if (!ext->esc) { + goto fail; + } + + key = memcpy(ext->ext, key, len + 1); + + // Reverse the extension (`*.y.x` -> `x.y.*`) so we can use trie_find_prefix() + ext_reverse(key, len); + + // Find any pre-existing exact match + struct ext_color *prev = NULL; + struct trie_leaf *leaf = trie_find_str(&colors->ext_trie, key); + if (leaf) { + prev = leaf->value; + trie_remove(&colors->ext_trie, leaf); + } // A later *.x should override any earlier *.x, *.y.x, etc. - struct trie_leaf *match; - while ((match = trie_find_postfix(&colors->ext_colors, key))) { - dstrfree(match->value); - trie_remove(&colors->ext_colors, match); + while ((leaf = trie_find_postfix(&colors->ext_trie, key))) { + trie_remove(&colors->ext_trie, leaf); } - struct trie_leaf *leaf = trie_insert_str(&colors->ext_colors, key); - if (leaf) { - leaf->value = value; - return 0; - } else { - return -1; + // Insert the extension into the case-sensitive trie + leaf = trie_insert_str(&colors->ext_trie, key); + if (!leaf) { + goto fail; } + leaf->value = ext; + + // "Smart case": if the same extension is given with two different + // capitalizations (e.g. `*.y.x=31:*.Y.Z=32:`), make it case-sensitive + ext_tolower(key, len); + leaf = trie_insert_str(&colors->iext_trie, key); + if (!leaf) { + goto fail; + } + + // If a match for the lowercased extension exists and is different from + // the exact match, or is already case-sensitive, mark this one too + struct ext_color *iprev = leaf->value; + if (iprev && (iprev != prev || !iprev->icase)) { + iprev->icase = false; + ext->icase = false; + } + leaf->value = ext; + + return 0; + +fail: + if (ext->esc) { + free_esc(colors, ext->esc); + } + varena_free(&colors->ext_arena, ext, len + 1); + return -1; +} + +/** Rebuild the case-insensitive trie after all extensions have been parsed. */ +static int build_iext_trie(struct colors *colors) { + trie_destroy(&colors->iext_trie); + trie_init(&colors->iext_trie); + + TRIE_FOR_EACH(&colors->ext_trie, leaf) { + struct ext_color *ext = leaf->value; + if (!ext->icase) { + continue; + } + + // set_ext() already reversed and lowercased the extension + struct trie_leaf *ileaf; + while ((ileaf = trie_find_postfix(&colors->iext_trie, ext->ext))) { + trie_remove(&colors->ext_trie, ileaf); + } + + ileaf = trie_insert_str(&colors->iext_trie, ext->ext); + if (!ileaf) { + return -1; + } + ileaf->value = ext; + } + + return 0; } /** * Find a color by an extension. */ -static const char *get_ext_color(const struct colors *colors, const char *filename) { +static const struct esc_seq *get_ext(const struct colors *colors, const char *filename) { size_t name_len = strlen(filename); size_t ext_len = name_len < EXT_MAX ? name_len : EXT_MAX; - const char *ext = filename + name_len - ext_len; + const char *suffix = filename + name_len - ext_len; char xfrm[ext_len + 1]; - memcpy(xfrm, ext, sizeof(xfrm)); - extxfrm(xfrm, ext_len); + memcpy(xfrm, suffix, sizeof(xfrm)); - const struct trie_leaf *leaf = trie_find_prefix(&colors->ext_colors, xfrm); - if (leaf) { - return leaf->value; - } else { - return NULL; + ext_reverse(xfrm, ext_len); + const struct trie_leaf *leaf = trie_find_prefix(&colors->ext_trie, xfrm); + const struct ext_color *ext = leaf ? leaf->value : NULL; + + ext_tolower(xfrm, ext_len); + const struct trie_leaf *ileaf = trie_find_prefix(&colors->iext_trie, xfrm); + const struct ext_color *iext = ileaf ? ileaf->value : NULL; + + if (iext && (!ext || ext->priority < iext->priority)) { + ext = iext; } + + return ext ? ext->esc : NULL; } /** @@ -211,6 +346,8 @@ static const char *get_ext_color(const struct colors *colors, const char *filena * * See man dir_colors. * + * @param str + * A dstring to fill with the unescaped chunk. * @param value * The value to parse. * @param end @@ -218,16 +355,18 @@ static const char *get_ext_color(const struct colors *colors, const char *filena * @param[out] next * Will be set to the next chunk. * @return - * The parsed chunk as a dstring. + * 0 on success, -1 on failure. */ -static char *unescape(const char *value, char end, const char **next) { +static int unescape(char **str, const char *value, char end, const char **next) { + *next = NULL; + if (!value) { - goto fail; + errno = EINVAL; + return -1; } - char *str = dstralloc(0); - if (!str) { - goto fail_str; + if (dstresize(str, 0) != 0) { + return -1; } const char *i; @@ -304,7 +443,8 @@ static char *unescape(const char *value, char end, const char **next) { break; case '\0': - goto fail_str; + errno = EINVAL; + return -1; default: c = *i; @@ -318,7 +458,8 @@ static char *unescape(const char *value, char end, const char **next) { c = '\177'; break; case '\0': - goto fail_str; + errno = EINVAL; + return -1; default: // CTRL masks bits 6 and 7 c = *i & 0x1F; @@ -331,77 +472,70 @@ static char *unescape(const char *value, char end, const char **next) { break; } - if (dstrapp(&str, c) != 0) { - goto fail_str; + if (dstrapp(str, c) != 0) { + return -1; } } if (*i) { *next = i + 1; - } else { - *next = NULL; } - return str; - -fail_str: - dstrfree(str); -fail: - *next = NULL; - return NULL; + return 0; } /** Parse the GNU $LS_COLORS format. */ -static void parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) { +static int parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) { + int ret = -1; + char *key = NULL; + char *value = NULL; + for (const char *chunk = ls_colors, *next; chunk; chunk = next) { if (chunk[0] == '*') { - char *key = unescape(chunk + 1, '=', &next); - if (!key) { - continue; + if (unescape(&key, chunk + 1, '=', &next) != 0) { + goto fail; } - - char *value = unescape(next, ':', &next); - if (value) { - if (set_ext_color(colors, key, value) != 0) { - dstrfree(value); - } + if (unescape(&value, next, ':', &next) != 0) { + goto fail; + } + if (set_ext(colors, key, value) != 0) { + goto fail; } - - dstrfree(key); } else { const char *equals = strchr(chunk, '='); if (!equals) { break; } - char *value = unescape(equals + 1, ':', &next); - if (!value) { - continue; + if (dstrncpy(&key, chunk, equals - chunk) != 0) { + goto fail; } - - char *key = strndup(chunk, equals - chunk); - if (!key) { - dstrfree(value); - continue; + if (unescape(&value, equals + 1, ':', &next) != 0) { + goto fail; } // All-zero values should be treated like NULL, to fall // back on any other relevant coloring for that file + char *esc = value; if (strspn(value, "0") == strlen(value) && strcmp(key, "rs") != 0 && strcmp(key, "lc") != 0 && strcmp(key, "rc") != 0 && strcmp(key, "ec") != 0) { - dstrfree(value); - value = NULL; + esc = NULL; } - if (set_color(colors, key, value) != 0) { - dstrfree(value); + if (set_esc(colors, key, esc) != 0) { + goto fail; } - free(key); } } + + ret = 0; +fail: + dstrfree(value); + dstrfree(key); + return ret; } struct colors *parse_colors(void) { @@ -410,91 +544,105 @@ struct colors *parse_colors(void) { return NULL; } + VARENA_INIT(&colors->esc_arena, struct esc_seq, seq); + VARENA_INIT(&colors->ext_arena, struct ext_color, ext); trie_init(&colors->names); - trie_init(&colors->ext_colors); + colors->ext_count = 0; + trie_init(&colors->ext_trie); + trie_init(&colors->iext_trie); int ret = 0; // From man console_codes - ret |= init_color(colors, "rs", "0", &colors->reset); - ret |= init_color(colors, "lc", "\033[", &colors->leftcode); - ret |= init_color(colors, "rc", "m", &colors->rightcode); - ret |= init_color(colors, "ec", NULL, &colors->endcode); - ret |= init_color(colors, "cl", "\033[K", &colors->clear_to_eol); - - ret |= init_color(colors, "bld", "01;39", &colors->bold); - ret |= init_color(colors, "gry", "01;30", &colors->gray); - ret |= init_color(colors, "red", "01;31", &colors->red); - ret |= init_color(colors, "grn", "01;32", &colors->green); - ret |= init_color(colors, "ylw", "01;33", &colors->yellow); - ret |= init_color(colors, "blu", "01;34", &colors->blue); - ret |= init_color(colors, "mag", "01;35", &colors->magenta); - ret |= init_color(colors, "cyn", "01;36", &colors->cyan); - ret |= init_color(colors, "wht", "01;37", &colors->white); - - ret |= init_color(colors, "wrn", "01;33", &colors->warning); - ret |= init_color(colors, "err", "01;31", &colors->error); + ret |= init_esc(colors, "rs", "0", &colors->reset); + ret |= init_esc(colors, "lc", "\033[", &colors->leftcode); + ret |= init_esc(colors, "rc", "m", &colors->rightcode); + ret |= init_esc(colors, "ec", NULL, &colors->endcode); + ret |= init_esc(colors, "cl", "\033[K", &colors->clear_to_eol); + + ret |= init_esc(colors, "bld", "01;39", &colors->bold); + ret |= init_esc(colors, "gry", "01;30", &colors->gray); + ret |= init_esc(colors, "red", "01;31", &colors->red); + ret |= init_esc(colors, "grn", "01;32", &colors->green); + ret |= init_esc(colors, "ylw", "01;33", &colors->yellow); + ret |= init_esc(colors, "blu", "01;34", &colors->blue); + ret |= init_esc(colors, "mag", "01;35", &colors->magenta); + ret |= init_esc(colors, "cyn", "01;36", &colors->cyan); + ret |= init_esc(colors, "wht", "01;37", &colors->white); + + ret |= init_esc(colors, "wrn", "01;33", &colors->warning); + ret |= init_esc(colors, "err", "01;31", &colors->error); // Defaults from man dir_colors // "" means fall back to ->normal - ret |= init_color(colors, "no", NULL, &colors->normal); + ret |= init_esc(colors, "no", NULL, &colors->normal); - ret |= init_color(colors, "fi", "", &colors->file); - ret |= init_color(colors, "mh", NULL, &colors->multi_hard); - ret |= init_color(colors, "ex", "01;32", &colors->executable); - ret |= init_color(colors, "ca", NULL, &colors->capable); - ret |= init_color(colors, "sg", "30;43", &colors->setgid); - ret |= init_color(colors, "su", "37;41", &colors->setuid); + ret |= init_esc(colors, "fi", "", &colors->file); + ret |= init_esc(colors, "mh", NULL, &colors->multi_hard); + ret |= init_esc(colors, "ex", "01;32", &colors->executable); + ret |= init_esc(colors, "ca", NULL, &colors->capable); + ret |= init_esc(colors, "sg", "30;43", &colors->setgid); + ret |= init_esc(colors, "su", "37;41", &colors->setuid); - ret |= init_color(colors, "di", "01;34", &colors->directory); - ret |= init_color(colors, "st", "37;44", &colors->sticky); - ret |= init_color(colors, "ow", "34;42", &colors->other_writable); - ret |= init_color(colors, "tw", "30;42", &colors->sticky_other_writable); + ret |= init_esc(colors, "di", "01;34", &colors->directory); + ret |= init_esc(colors, "st", "37;44", &colors->sticky); + ret |= init_esc(colors, "ow", "34;42", &colors->other_writable); + ret |= init_esc(colors, "tw", "30;42", &colors->sticky_other_writable); - ret |= init_color(colors, "ln", "01;36", &colors->link); - ret |= init_color(colors, "or", NULL, &colors->orphan); - ret |= init_color(colors, "mi", NULL, &colors->missing); + ret |= init_esc(colors, "ln", "01;36", &colors->link); + ret |= init_esc(colors, "or", NULL, &colors->orphan); + ret |= init_esc(colors, "mi", NULL, &colors->missing); colors->link_as_target = false; - ret |= init_color(colors, "bd", "01;33", &colors->blockdev); - ret |= init_color(colors, "cd", "01;33", &colors->chardev); - ret |= init_color(colors, "do", "01;35", &colors->door); - ret |= init_color(colors, "pi", "33", &colors->pipe); - ret |= init_color(colors, "so", "01;35", &colors->socket); + ret |= init_esc(colors, "bd", "01;33", &colors->blockdev); + ret |= init_esc(colors, "cd", "01;33", &colors->chardev); + ret |= init_esc(colors, "do", "01;35", &colors->door); + ret |= init_esc(colors, "pi", "33", &colors->pipe); + ret |= init_esc(colors, "so", "01;35", &colors->socket); - if (ret) { - free_colors(colors); - return NULL; + if (ret != 0) { + goto fail; } - parse_gnu_ls_colors(colors, getenv("LS_COLORS")); - parse_gnu_ls_colors(colors, getenv("BFS_COLORS")); + if (parse_gnu_ls_colors(colors, getenv("LS_COLORS")) != 0) { + goto fail; + } + if (parse_gnu_ls_colors(colors, getenv("BFS_COLORS")) != 0) { + goto fail; + } + if (build_iext_trie(colors) != 0) { + goto fail; + } - if (colors->link && strcmp(colors->link, "target") == 0) { - colors->link_as_target = true; - dstresize(&colors->link, 0); + if (colors->link) { + size_t len = strlen("target"); + if (colors->link->len == len && memcmp(colors->link->seq, "target", len) == 0) { + colors->link_as_target = true; + colors->link->len = 0; + } } return colors; + +fail: + free_colors(colors); + return NULL; } void free_colors(struct colors *colors) { - if (colors) { - TRIE_FOR_EACH(&colors->ext_colors, leaf) { - dstrfree(leaf->value); - } - trie_destroy(&colors->ext_colors); + if (!colors) { + return; + } - TRIE_FOR_EACH(&colors->names, leaf) { - char **field = leaf->value; - dstrfree(*field); - } - trie_destroy(&colors->names); + trie_destroy(&colors->iext_trie); + trie_destroy(&colors->ext_trie); + trie_destroy(&colors->names); + varena_destroy(&colors->ext_arena); + varena_destroy(&colors->esc_arena); - free(colors); - } + free(colors); } CFILE *cfwrap(FILE *file, const struct colors *colors, bool close) { @@ -547,14 +695,14 @@ static bool is_link_broken(const struct BFTW *ftwbuf) { } /** Get the color for a file. */ -static const char *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { +static const struct esc_seq *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { enum bfs_type type = bftw_type(ftwbuf, flags); if (type == BFS_ERROR) { goto error; } const struct bfs_stat *statbuf = NULL; - const char *color = NULL; + const struct esc_seq *color = NULL; switch (type) { case BFS_REG: @@ -578,7 +726,7 @@ static const char *file_color(const struct colors *colors, const char *filename, } if (!color) { - color = get_ext_color(colors, filename); + color = get_ext(colors, filename); } if (!color) { @@ -635,7 +783,7 @@ static const char *file_color(const struct colors *colors, const char *filename, break; } - if (color && !color[0]) { + if (color && color->len == 0) { color = colors->normal; } @@ -649,17 +797,22 @@ error: } } +/** Print an escape sequence chunk. */ +static int print_esc_chunk(CFILE *cfile, const struct esc_seq *esc) { + return dstrxcat(&cfile->buffer, esc->seq, esc->len); +} + /** Print an ANSI escape sequence. */ -static int print_esc(CFILE *cfile, const char *esc) { +static int print_esc(CFILE *cfile, const struct esc_seq *esc) { const struct colors *colors = cfile->colors; - if (dstrdcat(&cfile->buffer, colors->leftcode) != 0) { + if (print_esc_chunk(cfile, colors->leftcode) != 0) { return -1; } - if (dstrdcat(&cfile->buffer, esc) != 0) { + if (print_esc_chunk(cfile, esc) != 0) { return -1; } - if (dstrdcat(&cfile->buffer, colors->rightcode) != 0) { + if (print_esc_chunk(cfile, colors->rightcode) != 0) { return -1; } @@ -671,20 +824,20 @@ static int print_reset(CFILE *cfile) { const struct colors *colors = cfile->colors; if (colors->endcode) { - return dstrdcat(&cfile->buffer, colors->endcode); + return dstrxcat(&cfile->buffer, colors->endcode->seq, colors->endcode->len); } else { return print_esc(cfile, colors->reset); } } /** Print a string with an optional color. */ -static int print_colored(CFILE *cfile, const char *esc, const char *str, size_t len) { +static int print_colored(CFILE *cfile, const struct esc_seq *esc, const char *str, size_t len) { if (esc) { if (print_esc(cfile, esc) != 0) { return -1; } } - if (dstrncat(&cfile->buffer, str, len) != 0) { + if (dstrxcat(&cfile->buffer, str, len) != 0) { return -1; } if (esc) { @@ -772,7 +925,7 @@ static int print_dirs_colored(CFILE *cfile, const char *path, const struct BFTW } if ((size_t)broken < nameoff) { - const char *color = colors->missing; + const struct esc_seq *color = colors->missing; if (!color) { color = colors->orphan; } @@ -786,8 +939,8 @@ static int print_dirs_colored(CFILE *cfile, const char *path, const struct BFTW /** Print a file name with colors. */ static int print_name_colored(CFILE *cfile, const char *name, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { - const char *color = file_color(cfile->colors, name, ftwbuf, flags); - return print_colored(cfile, color, name, strlen(name)); + const struct esc_seq *esc = file_color(cfile->colors, name, ftwbuf, flags); + return print_colored(cfile, esc, name, strlen(name)); } /** Print a path with colors. */ @@ -1077,7 +1230,7 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { memcpy(name, i, len); name[len] = '\0'; - char **esc = get_color(colors, name); + struct esc_seq **esc = get_esc(colors, name); if (!esc) { goto invalid; } diff --git a/tests/bfs/color_ext_case.out b/tests/bfs/color_ext_case.out new file mode 100644 index 0000000..c3ded3a --- /dev/null +++ b/tests/bfs/color_ext_case.out @@ -0,0 +1,25 @@ +rainbow +rainbow/lower.gz +rainbow/lower.tar.gz +rainbow/exec.sh +rainbow/upper.GZ +rainbow/upper.TAR.GZ +rainbow/lower.tar +rainbow/upper.TAR +rainbow/ul.TAR.gz +rainbow/lu.tar.GZ +rainbow/socket +rainbow/broken +rainbow/chardev_link +rainbow/link.txt +rainbow/sticky_ow +rainbow/sgid +rainbow/pipe +rainbow/ow +rainbow/sugid +rainbow/suid +rainbow/sticky +rainbow/file.dat +rainbow/file.txt +rainbow/mh1 +rainbow/mh2 diff --git a/tests/bfs/color_ext_case.sh b/tests/bfs/color_ext_case.sh new file mode 100644 index 0000000..91f1f59 --- /dev/null +++ b/tests/bfs/color_ext_case.sh @@ -0,0 +1 @@ +LS_COLORS="*.gz=01;31:*.GZ=01;32:*.tAr=01;33:*.TAR.gz=01;34:*.tar.GZ=01;35:" bfs_diff rainbow -color diff --git a/tests/bfs/color_nul.out b/tests/bfs/color_nul.out index c328f82..4ea4d85 100644 Binary files a/tests/bfs/color_nul.out and b/tests/bfs/color_nul.out differ diff --git a/tests/bfs/color_nul.sh b/tests/bfs/color_nul.sh index 4979569..cb662d6 100644 --- a/tests/bfs/color_nul.sh +++ b/tests/bfs/color_nul.sh @@ -1,2 +1,3 @@ -LS_COLORS="ec=\33[m\0:" invoke_bfs rainbow -color -maxdepth 0 >"$OUT" +LS_COLORS="ec=\33[\0m:*.gz=\0\61;31:" invoke_bfs rainbow -color | tr '\0' '0' >"$OUT" +sort_output diff_output diff --git a/tests/bfs/color_star.out b/tests/bfs/color_star.out deleted file mode 100644 index 34c7153..0000000 --- a/tests/bfs/color_star.out +++ /dev/null @@ -1,25 +0,0 @@ -rainbow -rainbow/exec.sh -rainbow/socket -rainbow/broken -rainbow/chardev_link -rainbow/link.txt -rainbow/sticky_ow -rainbow/sgid -rainbow/pipe -rainbow/ow -rainbow/sugid -rainbow/suid -rainbow/sticky -rainbow/file.dat -rainbow/file.txt -rainbow/lower.gz -rainbow/lower.tar -rainbow/lower.tar.gz -rainbow/lu.tar.GZ -rainbow/mh1 -rainbow/mh2 -rainbow/ul.TAR.gz -rainbow/upper.GZ -rainbow/upper.TAR -rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_star.sh b/tests/bfs/color_star.sh index 3ada4fd..6d5312e 100644 --- a/tests/bfs/color_star.sh +++ b/tests/bfs/color_star.sh @@ -1,2 +1,2 @@ # Regression test: don't segfault on LS_COLORS="*" -LS_COLORS="*" bfs_diff rainbow -color +! LS_COLORS="*" invoke_bfs rainbow -color -- cgit v1.2.3 From 2ea6a1d7b99dbb389ec81054d136e5030717d52a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 30 Jun 2023 10:25:42 -0400 Subject: color: Compare values too when deciding to smart-case --- src/color.c | 81 +++++++++++++++++++++++++++++++++++--------- tests/bfs/color_ext_case.out | 2 +- tests/bfs/color_ext_case.sh | 7 +++- 3 files changed, 72 insertions(+), 18 deletions(-) diff --git a/src/color.c b/src/color.c index 82ff763..ec24d98 100644 --- a/src/color.c +++ b/src/color.c @@ -42,8 +42,8 @@ struct ext_color { struct esc_seq *esc; /** The length of the extension to match. */ size_t len; - /** Whether the comparison should be case-insensitive. */ - bool icase; + /** Whether the comparison should be case-sensitive. */ + bool case_sensitive; /** The extension to match (NUL-terminated). */ char ext[]; }; @@ -147,6 +147,11 @@ static int init_esc(struct colors *colors, const char *name, const char *value, } } +/** Check if an escape sequence is equal to a string. */ +static bool esc_eq(const struct esc_seq *esc, const char *str, size_t len) { + return esc->len == len && memcmp(esc->seq, str, len) == 0; +} + /** Get an escape sequence from the table. */ static struct esc_seq **get_esc(const struct colors *colors, const char *name) { const struct trie_leaf *leaf = trie_find_str(&colors->names, name); @@ -204,6 +209,55 @@ static void ext_tolower(char *ext, size_t len) { /** Maximum supported extension length. */ #define EXT_MAX 255 +/** + * The "smart case" algorithm. + * + * @param ext + * The current extension being added. + * @param prev + * The previous case-sensitive match, if any, for the same extension. + * @param iprev + * The previous case-insensitive match, if any, for the same extension. + * @return + * Whether this extension should become case-sensitive. + */ +static bool ext_case_sensitive(struct ext_color *ext, struct ext_color *prev, struct ext_color *iprev) { + // This is the first case-insensitive occurrence of this extension, e.g. + // + // *.gz=01;31:*.tar.gz=01;33 + if (!iprev) { + bfs_assert(!prev); + return false; + } + + // If the last version of this extension is already case-sensitive, + // this one should be too, e.g. + // + // *.tar.gz=01;31:*.TAR.GZ=01;32:*.TAR.GZ=01;33 + if (iprev->case_sensitive) { + return true; + } + + // The case matches the last occurrence exactly, e.g. + // + // *.tar.gz=01;31:*.tar.gz=01;33 + if (iprev == prev) { + return false; + } + + // Different case, but same value, e.g. + // + // *.tar.gz=01;31:*.TAR.GZ=01;31 + if (esc_eq(iprev->esc, ext->esc->seq, ext->esc->len)) { + return false; + } + + // Different case, different value, e.g. + // + // *.tar.gz=01;31:*.TAR.GZ=01;33 + return true; +} + /** Set the color for an extension. */ static int set_ext(struct colors *colors, char *key, char *value) { size_t len = dstrlen(key); @@ -214,7 +268,7 @@ static int set_ext(struct colors *colors, char *key, char *value) { ext->priority = colors->ext_count++; ext->len = len; - ext->icase = true; + ext->case_sensitive = false; ext->esc = new_esc(colors, value, dstrlen(value)); if (!ext->esc) { goto fail; @@ -253,12 +307,10 @@ static int set_ext(struct colors *colors, char *key, char *value) { goto fail; } - // If a match for the lowercased extension exists and is different from - // the exact match, or is already case-sensitive, mark this one too struct ext_color *iprev = leaf->value; - if (iprev && (iprev != prev || !iprev->icase)) { - iprev->icase = false; - ext->icase = false; + if (ext_case_sensitive(ext, prev, iprev)) { + iprev->case_sensitive = true; + ext->case_sensitive = true; } leaf->value = ext; @@ -279,14 +331,14 @@ static int build_iext_trie(struct colors *colors) { TRIE_FOR_EACH(&colors->ext_trie, leaf) { struct ext_color *ext = leaf->value; - if (!ext->icase) { + if (ext->case_sensitive) { continue; } // set_ext() already reversed and lowercased the extension struct trie_leaf *ileaf; while ((ileaf = trie_find_postfix(&colors->iext_trie, ext->ext))) { - trie_remove(&colors->ext_trie, ileaf); + trie_remove(&colors->iext_trie, ileaf); } ileaf = trie_insert_str(&colors->iext_trie, ext->ext); @@ -616,12 +668,9 @@ struct colors *parse_colors(void) { goto fail; } - if (colors->link) { - size_t len = strlen("target"); - if (colors->link->len == len && memcmp(colors->link->seq, "target", len) == 0) { - colors->link_as_target = true; - colors->link->len = 0; - } + if (colors->link && esc_eq(colors->link, "target", strlen("target"))) { + colors->link_as_target = true; + colors->link->len = 0; } return colors; diff --git a/tests/bfs/color_ext_case.out b/tests/bfs/color_ext_case.out index c3ded3a..4e7258d 100644 --- a/tests/bfs/color_ext_case.out +++ b/tests/bfs/color_ext_case.out @@ -16,10 +16,10 @@ rainbow/sgid rainbow/pipe rainbow/ow +rainbow/file.txt rainbow/sugid rainbow/suid rainbow/sticky rainbow/file.dat -rainbow/file.txt rainbow/mh1 rainbow/mh2 diff --git a/tests/bfs/color_ext_case.sh b/tests/bfs/color_ext_case.sh index 91f1f59..4adba69 100644 --- a/tests/bfs/color_ext_case.sh +++ b/tests/bfs/color_ext_case.sh @@ -1 +1,6 @@ -LS_COLORS="*.gz=01;31:*.GZ=01;32:*.tAr=01;33:*.TAR.gz=01;34:*.tar.GZ=01;35:" bfs_diff rainbow -color +# *.gz=01;31:*.GZ=01;32 -- case sensitive +# *.tAr=01;33:*.TaR=01;33 -- case-insensitive +# *.TAR.gz=01;34:*.tar.GZ=01;35 -- case-sensitive +# *.txt=35:*TXT=36 -- case-insensitive +export LS_COLORS="*.gz=01;31:*.GZ=01;32:*.tAr=01;33:*.TaR=01;33:*.TAR.gz=01;34:*.tar.GZ=01;35:*.txt=35:*TXT=36" +bfs_diff rainbow -color -- cgit v1.2.3 From 75b6e1b49e37c6eecd2225c9602ac7aa266c4023 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 30 Jun 2023 11:20:43 -0400 Subject: docs: Start preparing for the 3.0 release --- Makefile | 2 +- docs/CHANGELOG.md | 39 +++++++++++++++++++++++++++++++++++++++ src/config.h | 2 +- 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 5d7461e..beaf59d 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 2.6.3 +VERSION := 3.0 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index f67e68a..933ddc3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,3 +1,42 @@ +3.* +=== + +3.0 +--- + +**Coming soon** + +### New features + +- `bfs` now reads directories asynchronously and in parallel ([#101]). + Performance is significantly improved as a result. + Parallelism is controlled by the new `-j` flag, e.g. `-j1`, `-j2`, etc. + +[#101]: https://github.com/tavianator/bfs/issues/101 + +### Changes + +- `bfs` now uses the [C17] standard version, up from C11 + +- Due to [#101], `bfs` now requires some additional C and POSIX features: + - [Standard C atomics] (``) + - [POSIX threads] (``) + +- `$LS_COLORS` extensions written in different cases (e.g. `*.jpg=35:*.JPG=01;35`) are now matched case-sensitively, to match the new behaviour of GNU ls since coreutils version 9.2 + +- Added a warning/error if `$LS_COLORS` can't be parsed, depending on whether `-color` is requested explicitly + +- Build flags like `WITH_ONIGURUMA` have been renamed to `USE_ONIGURUMA` + +[C17]: https://en.cppreference.com/w/c/17 +[Standard C atomics]: https://en.cppreference.com/w/c/atomic +[POSIX threads]: https://pubs.opengroup.org/onlinepubs/9699919799/idx/threads.html + +### Bug fixes + +- Fixed handling of the "normal text" color (`no` in `$LS_COLORS`) to match GNU ls + + 2.* === diff --git a/src/config.h b/src/config.h index 79bd931..59d2671 100644 --- a/src/config.h +++ b/src/config.h @@ -22,7 +22,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "2.6.3" +# define BFS_VERSION "3.0" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From 1864ca8d8e9e93beb3593b316e447c6d817ddfd9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 30 Jun 2023 11:46:22 -0400 Subject: color: Don't print unnecessary reset sequences --- src/color.c | 67 +++++++++++++++++++++++++++++++++++++++---------------------- src/color.h | 2 ++ 2 files changed, 45 insertions(+), 24 deletions(-) diff --git a/src/color.c b/src/color.c index ec24d98..ec14303 100644 --- a/src/color.c +++ b/src/color.c @@ -707,6 +707,7 @@ CFILE *cfwrap(FILE *file, const struct colors *colors, bool close) { } cfile->file = file; + cfile->need_reset = false; cfile->close = close; if (isatty(fileno(file))) { @@ -853,15 +854,22 @@ static int print_esc_chunk(CFILE *cfile, const struct esc_seq *esc) { /** Print an ANSI escape sequence. */ static int print_esc(CFILE *cfile, const struct esc_seq *esc) { + if (!esc) { + return 0; + } + const struct colors *colors = cfile->colors; + if (esc != colors->reset) { + cfile->need_reset = true; + } - if (print_esc_chunk(cfile, colors->leftcode) != 0) { + if (print_esc_chunk(cfile, cfile->colors->leftcode) != 0) { return -1; } if (print_esc_chunk(cfile, esc) != 0) { return -1; } - if (print_esc_chunk(cfile, colors->rightcode) != 0) { + if (print_esc_chunk(cfile, cfile->colors->rightcode) != 0) { return -1; } @@ -870,10 +878,14 @@ static int print_esc(CFILE *cfile, const struct esc_seq *esc) { /** Reset after an ANSI escape sequence. */ static int print_reset(CFILE *cfile) { - const struct colors *colors = cfile->colors; + if (!cfile->need_reset) { + return 0; + } + cfile->need_reset = false; + const struct colors *colors = cfile->colors; if (colors->endcode) { - return dstrxcat(&cfile->buffer, colors->endcode->seq, colors->endcode->len); + return print_esc_chunk(cfile, colors->endcode); } else { return print_esc(cfile, colors->reset); } @@ -881,18 +893,14 @@ static int print_reset(CFILE *cfile) { /** Print a string with an optional color. */ static int print_colored(CFILE *cfile, const struct esc_seq *esc, const char *str, size_t len) { - if (esc) { - if (print_esc(cfile, esc) != 0) { - return -1; - } + if (print_esc(cfile, esc) != 0) { + return -1; } if (dstrxcat(&cfile->buffer, str, len) != 0) { return -1; } - if (esc) { - if (print_reset(cfile) != 0) { - return -1; - } + if (print_reset(cfile) != 0) { + return -1; } return 0; @@ -1151,13 +1159,19 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { const struct colors *colors = cfile->colors; int error = errno; + // Color specifier (e.g. ${blu}) state + struct esc_seq **esc; + const char *end; + size_t len; + char name[4]; + for (const char *i = format; *i; ++i) { size_t verbatim = strcspn(i, "%$"); if (dstrncat(&cfile->buffer, i, verbatim) != 0) { return -1; } - i += verbatim; + switch (*i) { case '%': switch (*++i) { @@ -1263,9 +1277,9 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { } break; - case '{': { + case '{': ++i; - const char *end = strchr(i, '}'); + end = strchr(i, '}'); if (!end) { goto invalid; } @@ -1274,16 +1288,22 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { break; } - size_t len = end - i; - char name[len + 1]; + len = end - i; + if (len >= sizeof(name)) { + goto invalid; + } memcpy(name, i, len); name[len] = '\0'; - struct esc_seq **esc = get_esc(colors, name); - if (!esc) { - goto invalid; - } - if (*esc) { + if (strcmp(name, "rs") == 0) { + if (print_reset(cfile) != 0) { + return -1; + } + } else { + esc = get_esc(colors, name); + if (!esc) { + goto invalid; + } if (print_esc(cfile, *esc) != 0) { return -1; } @@ -1291,7 +1311,6 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { i = end; break; - } default: goto invalid; @@ -1306,7 +1325,7 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { return 0; invalid: - bfs_bug("Invalid format string"); + bfs_bug("Invalid format string '%s'", format); errno = EINVAL; return -1; } diff --git a/src/color.h b/src/color.h index aca6a34..932d551 100644 --- a/src/color.h +++ b/src/color.h @@ -42,6 +42,8 @@ typedef struct CFILE { const struct colors *colors; /** A buffer for colored formatting. */ char *buffer; + /** Whether the next ${rs} is actually necessary. */ + bool need_reset; /** Whether to close the underlying stream. */ bool close; } CFILE; -- cgit v1.2.3 From 4a6e24764b4248a1a97a338138fa71c2c8c4d1d0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 13:30:10 -0400 Subject: list: Fix some parameter docs --- src/list.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/list.h b/src/list.h index a284c25..3b53fab 100644 --- a/src/list.h +++ b/src/list.h @@ -109,7 +109,7 @@ * Insert an item into a singly-linked list. * * @param list - * The list to remove from. + * The list to modify. * @param cursor * A pointer to the item to insert after, e.g. &list->head or list->tail. * @param item @@ -246,7 +246,7 @@ * Remove an item from a singly-linked list. * * @param list - * The list to remove from. + * The list to modify. * @param cursor * A pointer to the item to remove, either &list->head or &prev->next. * @param node (optional) @@ -277,7 +277,7 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * Pop the head off a singly-linked list. * * @param list - * The list to pop from. + * The list to modify. * @param node (optional) * If specified, use head->node.next rather than head->next. * @return @@ -334,7 +334,7 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * Insert into a doubly-linked list after the given cursor. * * @param list - * The list to initialize. + * The list to modify. * @param cursor * Insert after this element. * @param item -- cgit v1.2.3 From 58a36277cfaf95cba8609c1ddc823df2bc9fb60e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 13:31:54 -0400 Subject: ioq: Don't write to an empty slot in ioqq_trypop() --- src/ioq.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 1160b34..10451ac 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -240,18 +240,22 @@ static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { size_t i = load(&ioqq->tail, relaxed); atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; - uintptr_t prev = exchange(slot, 0, acquire); + uintptr_t prev = load(slot, relaxed); + if (!(prev & ~IOQ_BLOCKED)) { + return NULL; + } + if (!compare_exchange_weak(slot, &prev, 0, acquire, relaxed)) { + return NULL; + } if (prev & IOQ_BLOCKED) { ioqq_wake(ioqq, i); } prev &= ~IOQ_BLOCKED; - if (prev) { - size_t j = exchange(&ioqq->tail, i + IOQ_STRIDE, relaxed); - bfs_assert(j == i, "ioqq_trypop() only supports a single consumer"); - (void)j; - } + size_t j = exchange(&ioqq->tail, i + IOQ_STRIDE, relaxed); + bfs_assert(j == i, "ioqq_trypop() only supports a single consumer"); + (void)j; return (union ioq_cmd *)prev; } -- cgit v1.2.3 From 66264f1518d1e19123cca23ec92eace4c7982731 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 6 Jul 2023 08:59:37 -0400 Subject: diag: Shell-escape expression arguments --- src/color.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/color.c b/src/color.c index ec14303..2e039f4 100644 --- a/src/color.c +++ b/src/color.c @@ -1102,17 +1102,17 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { lhs = expr->lhs; rhs = expr->rhs; - if (cbuff(cfile, "${red}%s${rs}", expr->argv[0]) < 0) { + if (cbuff(cfile, "${red}%pq${rs}", expr->argv[0]) < 0) { return -1; } } else { - if (cbuff(cfile, "${blu}%s${rs}", expr->argv[0]) < 0) { + if (cbuff(cfile, "${blu}%pq${rs}", expr->argv[0]) < 0) { return -1; } } for (size_t i = 1; i < expr->argc; ++i) { - if (cbuff(cfile, " ${bld}%s${rs}", expr->argv[i]) < 0) { + if (cbuff(cfile, " ${bld}%pq${rs}", expr->argv[i]) < 0) { return -1; } } -- cgit v1.2.3 From a215ab6de0ae34db7311136404d0f9feab34ef04 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 5 Jul 2023 10:45:56 -0400 Subject: Use strcmp() instead of fnmatch() if possible --- src/eval.c | 21 +++++++++++++++++---- src/expr.h | 10 ++++++++++ src/parse.c | 17 ++++++++++++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/eval.c b/src/eval.c index 5f27681..56753af 100644 --- a/src/eval.c +++ b/src/eval.c @@ -547,6 +547,20 @@ bool eval_links(const struct bfs_expr *expr, struct bfs_eval *state) { return bfs_expr_cmp(expr, statbuf->nlink); } +/** Common code for fnmatch() tests. */ +static bool eval_fnmatch(const struct bfs_expr *expr, const char *str) { + if (expr->literal) { +#ifdef FNM_CASEFOLD + if (expr->fnm_flags & FNM_CASEFOLD) { + return strcasecmp(expr->pattern, str) == 0; + } +#endif + return strcmp(expr->pattern, str) == 0; + } else { + return fnmatch(expr->pattern, str, expr->fnm_flags) == 0; + } +} + /** * -i?lname test. */ @@ -568,7 +582,7 @@ bool eval_lname(const struct bfs_expr *expr, struct bfs_eval *state) { goto done; } - ret = fnmatch(expr->argv[1], name, expr->num) == 0; + ret = eval_fnmatch(expr, name); done: free(name); @@ -589,7 +603,7 @@ bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) { name = copy = xbasename(name); } - bool ret = fnmatch(expr->argv[1], name, expr->num) == 0; + bool ret = eval_fnmatch(expr, name); free(copy); return ret; } @@ -598,8 +612,7 @@ bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) { * -i?path test. */ bool eval_path(const struct bfs_expr *expr, struct bfs_eval *state) { - const struct BFTW *ftwbuf = state->ftwbuf; - return fnmatch(expr->argv[1], ftwbuf->path, expr->num) == 0; + return eval_fnmatch(expr, state->ftwbuf->path); } /** diff --git a/src/expr.h b/src/expr.h index 356689d..95118b9 100644 --- a/src/expr.h +++ b/src/expr.h @@ -143,6 +143,16 @@ struct bfs_expr { }; }; + /** String comparisons. */ + struct { + /** String pattern. */ + const char *pattern; + /** fnmatch() flags. */ + int fnm_flags; + /** Whether strcmp() can be used instead of fnmatch(). */ + bool literal; + }; + /** Printing actions. */ struct { /** The output stream. */ diff --git a/src/parse.c b/src/parse.c index 623a528..c225a5b 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1676,16 +1676,18 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b return NULL; } + expr->pattern = expr->argv[1]; + if (casefold) { #ifdef FNM_CASEFOLD - expr->num = FNM_CASEFOLD; + expr->fnm_flags = FNM_CASEFOLD; #else parse_expr_error(state, expr, "Missing platform support.\n"); bfs_expr_free(expr); return NULL; #endif } else { - expr->num = 0; + expr->fnm_flags = 0; } // POSIX says, about fnmatch(): @@ -1694,10 +1696,9 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b // return a non-zero value (indicating either no match or an error). // // But not all implementations obey this, so check for it ourselves. - const char *pattern = expr->argv[1]; - size_t i, len = strlen(pattern); + size_t i, len = strlen(expr->pattern); for (i = 0; i < len; ++i) { - if (pattern[len - i - 1] != '\\') { + if (expr->pattern[len - i - 1] != '\\') { break; } } @@ -1707,6 +1708,12 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b return expr; } + // strcmp() can be much faster than fnmatch() since it doesn't have to + // parse the pattern, so special-case patterns with no wildcards. + // + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 + expr->literal = strcspn(expr->pattern, "?*\\[") == len; + return expr; } -- cgit v1.2.3 From 187ef092b6ea0f92dac53fbd2deb71379400446e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 6 Jul 2023 14:16:20 -0400 Subject: wordesc: Also escape non-printable chars --- src/bfstd.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++------------- src/bfstd.h | 32 ++++++++++++++ 2 files changed, 147 insertions(+), 30 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 1f4bbb2..e5f9a31 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -17,6 +17,7 @@ #include #include #include +#include #if BFS_USE_SYS_SYSMACROS_H # include @@ -253,6 +254,24 @@ void *xmemdup(const void *src, size_t size) { return ret; } +char *xstpecpy(char *dest, char *end, const char *src) { + return xstpencpy(dest, end, src, SIZE_MAX); +} + +char *xstpencpy(char *dest, char *end, const char *src, size_t n) { + size_t space = end - dest; + n = space < n ? space : n; + n = strnlen(src, n); + memcpy(dest, src, n); + if (n < space) { + dest[n] = '\0'; + return dest + n; + } else { + end[-1] = '\0'; + return end; + } +} + void xstrmode(mode_t mode, char str[11]) { strcpy(str, "----------"); @@ -555,48 +574,114 @@ size_t xstrwidth(const char *str) { return ret; } -char *wordesc(const char *str) { - size_t len = strlen(str); +/** Get the length of the longest printable prefix of a string. */ +static size_t printable_len(const char *str, size_t len) { + mbstate_t mb; + memset(&mb, 0, sizeof(mb)); - if (strcspn(str, "|&;<>()$`\\\"' \t\n*?[#˜=%") == len) { - // Whole string is safe - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 - if (len > 0) { - return strdup(str); - } else { - return strdup("\"\""); + const char *cur = str; + while (len > 0) { + wchar_t wc; + size_t mblen = mbrtowc(&wc, cur, len, &mb); + if (mblen == (size_t)-1 || mblen == (size_t)-2 || !iswprint(wc)) { + break; } - } else if (strcspn(str, "`$\\\"") == len) { - // Safe to double-quote the whole string - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 - char *ret = malloc(len + 3); - if (!ret) { - return NULL; + cur += mblen; + len -= mblen; + } + + return cur - str; +} + +/** Get the length of the longest unprintable prefix of a string. */ +static size_t unprintable_len(const char *str, size_t len) { + mbstate_t mb; + memset(&mb, 0, sizeof(mb)); + + const char *cur = str; + while (len > 0) { + wchar_t wc; + size_t mblen = mbrtowc(&wc, cur, len, &mb); + if (mblen == (size_t)-1) { + // Invalid byte sequence, try again from the next byte + mblen = 1; + } else if (mblen == (size_t)-2) { + // Incomplete byte sequence, the rest is unprintable + mblen = len; + } else if (iswprint(wc)) { + break; } - char *cur = stpcpy(ret, "\""); - cur = stpcpy(cur, str); - cur = stpcpy(cur, "\""); - return ret; + cur += mblen; + len -= mblen; } - // Every ' is replaced with '\'', so at most a 3x growth - char *ret = malloc(3 * len + 3); + return cur - str; +} + +char *wordesc(const char *str) { + size_t len = strlen(str); + + // Worst case: every char is replaced with $'\xXX', so at most a 7x growth + size_t max_size = 7 * len + 3; + char *ret = malloc(max_size); if (!ret) { return NULL; } + char *cur = ret; + char *end = ret + max_size; - char *cur = stpcpy(ret, "'"); - while (*str) { - size_t chunk = strcspn(str, "'"); - cur = stpncpy(cur, str, chunk); - str += chunk; - if (*str) { - cur = stpcpy(cur, "'\\''"); - ++str; + while (len > 0) { + size_t plen = printable_len(str, len); + if (strcspn(str, "|&;<>()$`\\\"' *?[#˜=%") >= plen) { + // Whole chunk is safe + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 + cur = xstpencpy(cur, end, str, plen); + } else if (strcspn(str, "`$\\\"") >= plen) { + // Safe to double-quote the whole chunk + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 + cur = xstpecpy(cur, end, "\""); + cur = xstpencpy(cur, end, str, plen); + cur = xstpecpy(cur, end, "\""); + } else { + // Single-quote the whole chunk, convert ' into '\'' + cur = xstpecpy(cur, end, "'"); + for (size_t i = 0; i < plen; ++i) { + if (str[i] == '\'') { + cur = xstpecpy(cur, end, "'\\''"); + } else { + cur = xstpencpy(cur, end, &str[i], 1); + } + } + cur = xstpecpy(cur, end, "'"); + } + + str += plen; + len -= plen; + if (len == 0) { + break; } + + // Non-printable characters, write them as $'\xXX\xXX...' + cur = xstpecpy(cur, end, "$'"); + size_t uplen = unprintable_len(str, len); + for (size_t i = 0; i < uplen; ++i) { + static const char *hex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; + unsigned char byte = str[i]; + cur = xstpecpy(cur, end, "\\x"); + cur = xstpecpy(cur, end, hex[byte / 0x10]); + cur = xstpecpy(cur, end, hex[byte % 0x10]); + } + cur = xstpecpy(cur, end, "'"); + + str += uplen; + len -= uplen; + } + + if (cur == ret) { + cur = xstpecpy(cur, end, "\"\""); } - cur = stpcpy(cur, "'"); + bfs_assert(cur != end, "Result truncated!"); return ret; } diff --git a/src/bfstd.h b/src/bfstd.h index ebbcdb9..832db66 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -134,6 +134,38 @@ int ynprompt(void); */ void *xmemdup(const void *src, size_t size); +/** + * A nice string copying function. + * + * @param dest + * The NUL terminator of the destination string, or `end` if it is + * already truncated. + * @param end + * The end of the destination buffer. + * @param src + * The string to copy from. + * @return + * The new NUL terminator of the destination, or `end` on truncation. + */ +char *xstpecpy(char *dest, char *end, const char *src); + +/** + * A nice string copying function. + * + * @param dest + * The NUL terminator of the destination string, or `end` if it is + * already truncated. + * @param end + * The end of the destination buffer. + * @param src + * The string to copy from. + * @param n + * The maximum number of characters to copy. + * @return + * The new NUL terminator of the destination, or `end` on truncation. + */ +char *xstpencpy(char *dest, char *end, const char *src, size_t n); + /** * Format a mode like ls -l (e.g. -rw-r--r--). * -- cgit v1.2.3 From 379dee8a47480938c067fca0acd01dea9b5afa33 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 15:01:18 -0400 Subject: ioq: New ioq_capacity() function --- src/ioq.c | 9 +++++++-- src/ioq.h | 5 +++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 10451ac..5673c77 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -355,6 +355,10 @@ fail: return NULL; } +size_t ioq_capacity(const struct ioq *ioq) { + return ioq->depth - ioq->size; +} + int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { if (ioq->size >= ioq->depth) { return -1; @@ -382,7 +386,6 @@ struct ioq_res *ioq_pop(struct ioq *ioq) { } union ioq_cmd *cmd = ioqq_pop(ioq->ready); - --ioq->size; return &cmd->res; } @@ -396,11 +399,13 @@ struct ioq_res *ioq_trypop(struct ioq *ioq) { return NULL; } - --ioq->size; return &cmd->res; } void ioq_free(struct ioq *ioq, struct ioq_res *res) { + bfs_assert(ioq->size > 0); + --ioq->size; + arena_free(&ioq->cmds, (union ioq_cmd *)res); } diff --git a/src/ioq.h b/src/ioq.h index 064e2e2..9901293 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -40,6 +40,11 @@ struct ioq_res { */ struct ioq *ioq_create(size_t depth, size_t nthreads); +/** + * Check the remaining capacity of a queue. + */ +size_t ioq_capacity(const struct ioq *ioq); + /** * Asynchronous bfs_opendir(). * -- cgit v1.2.3 From 35d357793c35ed0263d57a439323027c80a4a5b7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 15:03:37 -0400 Subject: bftw: If the ioq is full, try to pop before ioq_opendir() --- src/bftw.c | 131 ++++++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 82 insertions(+), 49 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 2bdf12d..ec67817 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -461,8 +461,12 @@ struct bftw_state { /** The cache of open directories. */ struct bftw_cache cache; + /** The async I/O queue. */ struct ioq *ioq; + /** The number of I/O threads. */ + size_t nthreads; + /** The queue of directories to read. */ struct bftw_list dirs; /** The queue of files to visit. */ @@ -536,6 +540,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg return -1; } } + state->nthreads = nthreads; SLIST_INIT(&state->dirs); SLIST_INIT(&state->files); @@ -889,13 +894,81 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam } } +/** Pop a response from the I/O queue. */ +static int bftw_ioq_pop(struct bftw_state *state, bool block) { + struct ioq *ioq = state->ioq; + if (!ioq) { + return -1; + } + + struct ioq_res *res = block ? ioq_pop(ioq) : ioq_trypop(ioq); + if (!res) { + return -1; + } + + struct bftw_cache *cache = &state->cache; + ++cache->capacity; + + struct bftw_file *file = res->ptr; + file->ioqueued = false; + + if (file->parent) { + bftw_cache_unpin(cache, file->parent); + } + + if (res->error) { + arena_free(&cache->dirs, res->dir); + } else { + bftw_file_set_dir(cache, file, res->dir); + } + + ioq_free(ioq, res); + + if (!(state->flags & BFTW_SORT)) { + SLIST_PREPEND(&state->dirs, file); + } + + return 0; +} + +/** Try to reserve space in the I/O queue. */ +static int bftw_ioq_reserve(struct bftw_state *state) { + struct ioq *ioq = state->ioq; + if (!ioq) { + return -1; + } + + if (ioq_capacity(ioq) > 0) { + return 0; + } + + // With more than two threads, it is faster to wait for an I/O operation + // to complete than it is to do it ourselves + bool block = state->nthreads > 2; + if (bftw_ioq_pop(state, block) < 0) { + return -1; + } + + return 0; +} + +/** Try to reserve space in the cache. */ +static int bftw_cache_reserve(struct bftw_state *state) { + struct bftw_cache *cache = &state->cache; + if (cache->capacity > 0) { + return 0; + } + + return bftw_cache_pop(cache); +} + /** Push a directory onto the queue. */ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bfs_assert(file->type == BFS_DIR); struct bftw_cache *cache = &state->cache; - if (!state->ioq) { + if (bftw_ioq_reserve(state) != 0) { goto append; } @@ -908,13 +981,11 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bftw_cache_pin(cache, file->parent); } - if (cache->capacity == 0) { - if (bftw_cache_pop(cache) != 0) { - goto unpin; - } + if (bftw_cache_reserve(state) != 0) { + goto unpin; } - struct bfs_dir *dir = arena_alloc(&state->cache.dirs); + struct bfs_dir *dir = arena_alloc(&cache->dirs); if (!dir) { goto unpin; } @@ -927,13 +998,17 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { --cache->capacity; if (state->flags & BFTW_SORT) { + // When sorting, dirs are always kept in order in state->dirs, + // and we wait for the ioq to open them before popping goto append; } else { + // When not sorting, dirs are not added to state->dirs until + // popped from the ioq return; } free: - arena_free(&state->cache.dirs, dir); + arena_free(&cache->dirs, dir); unpin: if (file->parent) { bftw_cache_unpin(cache, file->parent); @@ -942,48 +1017,6 @@ append: SLIST_APPEND(&state->dirs, file); } -/** Pop a response from the I/O queue. */ -static int bftw_ioq_pop(struct bftw_state *state, bool block) { - if (!state->ioq) { - return -1; - } - - struct ioq_res *res; - if (block) { - res = ioq_pop(state->ioq); - } else { - res = ioq_trypop(state->ioq); - } - - if (!res) { - return -1; - } - - struct bftw_cache *cache = &state->cache; - ++cache->capacity; - - struct bftw_file *file = res->ptr; - file->ioqueued = false; - - if (file->parent) { - bftw_cache_unpin(cache, file->parent); - } - - if (res->error) { - arena_free(&state->cache.dirs, res->dir); - } else { - bftw_file_set_dir(cache, file, res->dir); - } - - ioq_free(state->ioq, res); - - if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->dirs, file); - } - - return 0; -} - /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); -- cgit v1.2.3 From 222ac5ba4fbab0ab880e36423d0f1338e39b02c7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 15:17:23 -0400 Subject: ioq: Implement async close() and closedir() --- src/bftw.c | 41 ++++++++------- src/ioq.c | 175 ++++++++++++++++++++++++++++++++++++------------------------- src/ioq.h | 81 ++++++++++++++++++++++++---- 3 files changed, 197 insertions(+), 100 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index ec67817..64f221b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -901,34 +901,39 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return -1; } - struct ioq_res *res = block ? ioq_pop(ioq) : ioq_trypop(ioq); - if (!res) { + struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq); + if (!ent) { return -1; } struct bftw_cache *cache = &state->cache; - ++cache->capacity; - - struct bftw_file *file = res->ptr; - file->ioqueued = false; + struct bftw_file *file; + struct bfs_dir *dir; - if (file->parent) { - bftw_cache_unpin(cache, file->parent); - } + enum ioq_op op = ent->op; + if (op == IOQ_OPENDIR) { + file = ent->ptr; + file->ioqueued = false; - if (res->error) { - arena_free(&cache->dirs, res->dir); - } else { - bftw_file_set_dir(cache, file, res->dir); - } + ++cache->capacity; + if (file->parent) { + bftw_cache_unpin(cache, file->parent); + } - ioq_free(ioq, res); + dir = ent->opendir.dir; + if (ent->ret == 0) { + bftw_file_set_dir(cache, file, dir); + } else { + arena_free(&cache->dirs, dir); + } - if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->dirs, file); + if (!(state->flags & BFTW_SORT)) { + SLIST_PREPEND(&state->dirs, file); + } } - return 0; + ioq_free(ioq, ent); + return op; } /** Try to reserve space in the I/O queue. */ diff --git a/src/ioq.c b/src/ioq.c index 5673c77..0544044 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -16,29 +16,6 @@ #include #include -/** - * An I/O queue request. - */ -struct ioq_req { - /** Directory allocation. */ - struct bfs_dir *dir; - /** Base file descriptor for openat(). */ - int dfd; - /** Path to open, relative to dfd. */ - const char *path; - - /** Arbitrary user data. */ - void *ptr; -}; - -/** - * An I/O queue command. - */ -union ioq_cmd { - struct ioq_req req; - struct ioq_res res; -}; - /** * A monitor for an I/O queue slot. */ @@ -101,7 +78,7 @@ bfs_static_assert(IOQ_STRIDE % 2 == 1); /** Slot flag bit to indicate waiters. */ #define IOQ_BLOCKED ((uintptr_t)1) -bfs_static_assert(alignof(union ioq_cmd) > 1); +bfs_static_assert(alignof(struct ioq_ent) > 1); /** Destroy an I/O command queue. */ static void ioqq_destroy(struct ioqq *ioqq) { @@ -189,12 +166,12 @@ static void ioqq_wake(struct ioqq *ioqq, size_t i) { cond_broadcast(&monitor->cond); } -/** Push a command onto the queue. */ -static void ioqq_push(struct ioqq *ioqq, union ioq_cmd *cmd) { +/** Push an entry onto the queue. */ +static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; - uintptr_t addr = (uintptr_t)cmd; + uintptr_t addr = (uintptr_t)ent; bfs_assert(!(addr & IOQ_BLOCKED)); uintptr_t prev = load(slot, relaxed); @@ -212,8 +189,8 @@ static void ioqq_push(struct ioqq *ioqq, union ioq_cmd *cmd) { } } -/** Pop a command from a queue. */ -static union ioq_cmd *ioqq_pop(struct ioqq *ioqq) { +/** Pop an entry from the queue. */ +static struct ioq_ent *ioqq_pop(struct ioqq *ioqq) { size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; @@ -232,11 +209,11 @@ static union ioq_cmd *ioqq_pop(struct ioqq *ioqq) { } prev &= ~IOQ_BLOCKED; - return (union ioq_cmd *)prev; + return (struct ioq_ent *)prev; } -/** Pop a command from a queue if one is available. */ -static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { +/** Pop an entry from the queue if one is available. */ +static struct ioq_ent *ioqq_trypop(struct ioqq *ioqq) { size_t i = load(&ioqq->tail, relaxed); atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; @@ -257,11 +234,11 @@ static union ioq_cmd *ioqq_trypop(struct ioqq *ioqq) { bfs_assert(j == i, "ioqq_trypop() only supports a single consumer"); (void)j; - return (union ioq_cmd *)prev; + return (struct ioq_ent *)prev; } /** Sentinel stop command. */ -static union ioq_cmd IOQ_STOP; +static struct ioq_ent IOQ_STOP; struct ioq { /** The depth of the queue. */ @@ -271,8 +248,8 @@ struct ioq { /** Cancellation flag. */ atomic bool cancel; - /** ioq_cmd command arena. */ - struct arena cmds; + /** ioq_ent arena. */ + struct arena ents; /** Pending I/O requests. */ struct ioqq *pending; @@ -290,30 +267,50 @@ static void *ioq_work(void *ptr) { struct ioq *ioq = ptr; while (true) { - union ioq_cmd *cmd = ioqq_pop(ioq->pending); - if (cmd == &IOQ_STOP) { + struct ioq_ent *ent = ioqq_pop(ioq->pending); + if (ent == &IOQ_STOP) { break; } bool cancel = load(&ioq->cancel, relaxed); - struct ioq_req req = cmd->req; - sanitize_uninit(cmd); + ent->ret = -1; + + switch (ent->op) { + case IOQ_CLOSE: + // Always close(), even if we're cancelled, just like a real EINTR + ent->ret = xclose(ent->close.fd); + break; + + case IOQ_OPENDIR: + if (!cancel) { + struct ioq_opendir *args = &ent->opendir; + ent->ret = bfs_opendir(args->dir, args->dfd, args->path); + if (ent->ret == 0) { + bfs_polldir(args->dir); + } + } + break; + + case IOQ_CLOSEDIR: + ent->ret = bfs_closedir(ent->closedir.dir); + break; - struct ioq_res *res = &cmd->res; - res->ptr = req.ptr; - res->dir = req.dir; + default: + bfs_bug("Unknown ioq_op %d", (int)ent->op); + errno = ENOSYS; + break; + } if (cancel) { - res->error = EINTR; - } else if (bfs_opendir(req.dir, req.dfd, req.path) != 0) { - res->error = errno; + ent->error = EINTR; + } else if (ent->ret < 0) { + ent->error = errno; } else { - res->error = 0; - bfs_polldir(res->dir); + ent->error = 0; } - ioqq_push(ioq->ready, cmd); + ioqq_push(ioq->ready, ent); } return NULL; @@ -326,7 +323,7 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { } ioq->depth = depth; - ARENA_INIT(&ioq->cmds, union ioq_cmd); + ARENA_INIT(&ioq->ents, struct ioq_ent); ioq->pending = ioqq_create(depth); if (!ioq->pending) { @@ -359,54 +356,88 @@ size_t ioq_capacity(const struct ioq *ioq) { return ioq->depth - ioq->size; } -int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { +static struct ioq_ent *ioq_request(struct ioq *ioq, enum ioq_op op, void *ptr) { + if (load(&ioq->cancel, relaxed)) { + errno = EINTR; + return NULL; + } + if (ioq->size >= ioq->depth) { + errno = EAGAIN; + return NULL; + } + + struct ioq_ent *ent = arena_alloc(&ioq->ents); + if (!ent) { + return NULL; + } + + ent->op = op; + ent->ptr = ptr; + ++ioq->size; + return ent; +} + +int ioq_close(struct ioq *ioq, int fd, void *ptr) { + struct ioq_ent *ent = ioq_request(ioq, IOQ_CLOSE, ptr); + if (!ent) { return -1; } - union ioq_cmd *cmd = arena_alloc(&ioq->cmds); - if (!cmd) { + ent->close.fd = fd; + + ioqq_push(ioq->pending, ent); + return 0; +} + +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { + struct ioq_ent *ent = ioq_request(ioq, IOQ_OPENDIR, ptr); + if (!ent) { return -1; } - struct ioq_req *req = &cmd->req; - req->dir = dir; - req->dfd = dfd; - req->path = path; - req->ptr = ptr; + struct ioq_opendir *args = &ent->opendir; + args->dir = dir; + args->dfd = dfd; + args->path = path; - ioqq_push(ioq->pending, cmd); - ++ioq->size; + ioqq_push(ioq->pending, ent); return 0; } -struct ioq_res *ioq_pop(struct ioq *ioq) { - if (ioq->size == 0) { - return NULL; +int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr) { + struct ioq_ent *ent = ioq_request(ioq, IOQ_CLOSEDIR, ptr); + if (!ent) { + return -1; } - union ioq_cmd *cmd = ioqq_pop(ioq->ready); - return &cmd->res; + ent->closedir.dir = dir; + + ioqq_push(ioq->pending, ent); + return 0; } -struct ioq_res *ioq_trypop(struct ioq *ioq) { +struct ioq_ent *ioq_pop(struct ioq *ioq) { if (ioq->size == 0) { return NULL; } - union ioq_cmd *cmd = ioqq_trypop(ioq->ready); - if (!cmd) { + return ioqq_pop(ioq->ready); +} + +struct ioq_ent *ioq_trypop(struct ioq *ioq) { + if (ioq->size == 0) { return NULL; } - return &cmd->res; + return ioqq_trypop(ioq->ready); } -void ioq_free(struct ioq *ioq, struct ioq_res *res) { +void ioq_free(struct ioq *ioq, struct ioq_ent *ent) { bfs_assert(ioq->size > 0); --ioq->size; - arena_free(&ioq->cmds, (union ioq_cmd *)res); + arena_free(&ioq->ents, ent); } void ioq_cancel(struct ioq *ioq) { @@ -431,7 +462,7 @@ void ioq_destroy(struct ioq *ioq) { ioqq_destroy(ioq->ready); ioqq_destroy(ioq->pending); - arena_destroy(&ioq->cmds); + arena_destroy(&ioq->ents); free(ioq); } diff --git a/src/ioq.h b/src/ioq.h index 9901293..99c18c2 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -16,16 +16,49 @@ struct ioq; /** - * An I/O queue response. + * I/O queue operations. */ -struct ioq_res { - /** The opened directory. */ - struct bfs_dir *dir; +enum ioq_op { + /** ioq_close(). */ + IOQ_CLOSE, + /** ioq_opendir(). */ + IOQ_OPENDIR, + /** ioq_closedir(). */ + IOQ_CLOSEDIR, +}; + +/** + * An I/O queue entry. + */ +struct ioq_ent { + /** The I/O operation. */ + enum ioq_op op; + + /** The return value of the operation. */ + int ret; /** The error code, if the operation failed. */ int error; /** Arbitrary user data. */ void *ptr; + + /** Operation-specific arguments. */ + union { + /** ioq_close() args. */ + struct ioq_close { + int fd; + } close; + /** ioq_opendir() args. */ + struct ioq_opendir { + struct bfs_dir *dir; + int dfd; + const char *path; + } opendir; + /** ioq_closedir() args. */ + struct ioq_closedir { + struct bfs_dir *dir; + } closedir; + }; }; /** @@ -45,6 +78,20 @@ struct ioq *ioq_create(size_t depth, size_t nthreads); */ size_t ioq_capacity(const struct ioq *ioq); +/** + * Asynchronous close(). + * + * @param ioq + * The I/O queue. + * @param fd + * The fd to close. + * @param ptr + * An arbitrary pointer to associate with the request. + * @return + * 0 on success, or -1 on failure. + */ +int ioq_close(struct ioq *ioq, int fd, void *ptr); + /** * Asynchronous bfs_opendir(). * @@ -63,6 +110,20 @@ size_t ioq_capacity(const struct ioq *ioq); */ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr); +/** + * Asynchronous bfs_closedir(). + * + * @param ioq + * The I/O queue. + * @param dir + * The directory to close. + * @param ptr + * An arbitrary pointer to associate with the request. + * @return + * 0 on success, or -1 on failure. + */ +int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr); + /** * Pop a response from the queue. * @@ -71,7 +132,7 @@ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, * @return * The next response, or NULL. */ -struct ioq_res *ioq_pop(struct ioq *ioq); +struct ioq_ent *ioq_pop(struct ioq *ioq); /** * Pop a response from the queue, without blocking. @@ -81,17 +142,17 @@ struct ioq_res *ioq_pop(struct ioq *ioq); * @return * The next response, or NULL. */ -struct ioq_res *ioq_trypop(struct ioq *ioq); +struct ioq_ent *ioq_trypop(struct ioq *ioq); /** - * Free a response. + * Free a queue entry. * * @param ioq * The I/O queue. - * @param res - * The response to free. + * @param ent + * The entry to free. */ -void ioq_free(struct ioq *ioq, struct ioq_res *res); +void ioq_free(struct ioq *ioq, struct ioq_ent *ent); /** * Cancel any pending I/O operations. -- cgit v1.2.3 From 739317470873422c1023eab22c570c374a10f498 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 4 Jul 2023 15:37:54 -0400 Subject: bftw: Try to close files asynchronously --- src/bftw.c | 175 ++++++++++++++++++++++++++++++++++++++++++++++++------------- src/dir.c | 20 ++----- src/dir.h | 30 +++++++---- 3 files changed, 163 insertions(+), 62 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 64f221b..aec804b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -149,29 +149,6 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { bftw_cache_remove(cache, file); } -/** Free an open directory. */ -static void bftw_file_freedir(struct bftw_cache *cache, struct bftw_file *file) { - if (!file->dir) { - return; - } - - // Try to keep an open fd if any children exist - bool reffed = file->refcount > 1; - // Keep the fd the same if it's pinned - bool pinned = file->pincount > 0; - - if (reffed || pinned) { - int fd = bfs_fdclosedir(file->dir, pinned); - if (fd >= 0) { - file->fd = fd; - arena_free(&cache->dirs, file->dir); - file->dir = NULL; - } - } else { - bftw_file_close(cache, file); - } -} - /** Pop the least recently used directory from the cache. */ static int bftw_cache_pop(struct bftw_cache *cache) { struct bftw_file *file = cache->tail; @@ -228,7 +205,6 @@ static void bftw_cache_unpin(struct bftw_cache *cache, struct bftw_file *file) { if (--file->pincount == 0) { bftw_lru_add(cache, file); - bftw_file_freedir(cache, file); } } @@ -911,7 +887,18 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct bfs_dir *dir; enum ioq_op op = ent->op; - if (op == IOQ_OPENDIR) { + switch (op) { + case IOQ_CLOSE: + ++cache->capacity; + break; + + case IOQ_CLOSEDIR: + ++cache->capacity; + dir = ent->closedir.dir; + arena_free(&cache->dirs, dir); + break; + + case IOQ_OPENDIR: file = ent->ptr; file->ioqueued = false; @@ -930,6 +917,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { if (!(state->flags & BFTW_SORT)) { SLIST_PREPEND(&state->dirs, file); } + break; } ioq_free(ioq, ent); @@ -964,6 +952,12 @@ static int bftw_cache_reserve(struct bftw_state *state) { return 0; } + while (bftw_ioq_pop(state, false) >= 0) { + if (cache->capacity > 0) { + return 0; + } + } + return bftw_cache_pop(cache); } @@ -1022,13 +1016,106 @@ append: SLIST_APPEND(&state->dirs, file); } +/** Close a directory, asynchronously if possible. */ +static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { + if (bftw_ioq_reserve(state) == 0) { + if (ioq_closedir(state->ioq, dir, NULL) == 0) { + return 0; + } + } + + struct bftw_cache *cache = &state->cache; + int ret = bfs_closedir(dir); + arena_free(&cache->dirs, dir); + ++cache->capacity; + return ret; +} + +/** Close a file descriptor, asynchronously if possible. */ +static int bftw_ioq_close(struct bftw_state *state, int fd) { + if (bftw_ioq_reserve(state) == 0) { + if (ioq_close(state->ioq, fd, NULL) == 0) { + return 0; + } + } + + struct bftw_cache *cache = &state->cache; + int ret = xclose(fd); + ++cache->capacity; + return ret; +} + +/** Close a file, asynchronously if possible. */ +static int bftw_close(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + bfs_assert(file->pincount == 0); + + struct bfs_dir *dir = file->dir; + int fd = file->fd; + + bftw_lru_remove(&state->cache, file); + file->dir = NULL; + file->fd = -1; + + if (dir) { + return bftw_ioq_closedir(state, dir); + } else { + return bftw_ioq_close(state, fd); + } +} + +/** Free an open directory. */ +static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { + struct bfs_dir *dir = file->dir; + if (!dir) { + return 0; + } + + struct bftw_cache *cache = &state->cache; + + // Try to keep an open fd if any children exist + bool reffed = file->refcount > 1; + // Keep the fd the same if it's pinned + bool pinned = file->pincount > 0; + +#if BFS_USE_UNWRAPDIR + if (reffed || pinned) { + bfs_unwrapdir(dir); + arena_free(&cache->dirs, dir); + file->dir = NULL; + return 0; + } +#else + if (pinned) { + return -1; + } +#endif + + if (!reffed) { + return bftw_close(state, file); + } + + if (bftw_cache_reserve(state) != 0) { + return -1; + } + + int fd = dup_cloexec(file->fd); + if (fd < 0) { + return -1; + } + --cache->capacity; + + file->dir = NULL; + file->fd = fd; + return bftw_ioq_closedir(state, dir); +} + /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - bool have_dirs = state->dirs.head; + struct bftw_file *dir = state->dirs.head; bool have_files = state->files.head; - bool have_room = state->cache.capacity > 0; if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting @@ -1036,16 +1123,25 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } } else { - // Block if we have no other files/dirs to visit, or no room in the cache - bool block = !(have_dirs || have_files) || !have_room; - bftw_ioq_pop(state, block); + while (!dir || !dir->dir) { + // Block if we have no other files/dirs to visit, or no room in the cache + bool have_room = state->cache.capacity > 0; + bool block = !(dir || have_files) || !have_room; + + if (bftw_ioq_pop(state, block) < 0) { + break; + } + dir = state->dirs.head; + } } - struct bftw_file *dir = state->file = SLIST_POP(&state->dirs); if (!dir) { return false; } + SLIST_POP(&state->dirs); + state->file = dir; + while (dir->ioqueued) { bftw_ioq_pop(state, true); } @@ -1132,7 +1228,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { if (state->dir) { bftw_cache_unpin(&state->cache, state->file); - bftw_file_freedir(&state->cache, state->file); + bftw_unwrapdir(state, state->file); } state->dir = NULL; state->de = NULL; @@ -1169,8 +1265,12 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { if (state->previous == file) { state->previous = parent; } - bftw_file_free(&state->cache, file); state->file = parent; + + if (file->fd >= 0) { + bftw_close(state, file); + } + bftw_file_free(&state->cache, file); } return ret; @@ -1185,8 +1285,11 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { static int bftw_state_destroy(struct bftw_state *state) { dstrfree(state->path); - if (state->ioq) { - ioq_cancel(state->ioq); + struct ioq *ioq = state->ioq; + if (ioq) { + ioq_cancel(ioq); + while (bftw_ioq_pop(state, true) >= 0); + state->ioq = NULL; } SLIST_EXTEND(&state->files, &state->batch); @@ -1194,7 +1297,7 @@ static int bftw_state_destroy(struct bftw_state *state) { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); - ioq_destroy(state->ioq); + ioq_destroy(ioq); bftw_cache_destroy(&state->cache); diff --git a/src/dir.c b/src/dir.c index 685bac5..0304674 100644 --- a/src/dir.c +++ b/src/dir.c @@ -15,10 +15,6 @@ #include #include -#ifndef BFS_USE_GETDENTS -# define BFS_USE_GETDENTS (__linux__ || __FreeBSD__) -#endif - #if BFS_USE_GETDENTS # if __linux__ # include @@ -296,25 +292,15 @@ int bfs_closedir(struct bfs_dir *dir) { return ret; } -int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd) { +#if BFS_USE_UNWRAPDIR +int bfs_unwrapdir(struct bfs_dir *dir) { #if BFS_USE_GETDENTS int ret = dir->fd; #elif __FreeBSD__ int ret = fdclosedir(dir->dir); -#else - if (same_fd) { - errno = ENOTSUP; - return -1; - } - - int ret = dup_cloexec(dirfd(dir->dir)); - if (ret < 0) { - return -1; - } - - bfs_closedir(dir); #endif sanitize_uninit(dir, DIR_SIZE); return ret; } +#endif diff --git a/src/dir.h b/src/dir.h index 16f592e..1137ff5 100644 --- a/src/dir.h +++ b/src/dir.h @@ -12,6 +12,14 @@ #include "config.h" #include +/** + * Whether the implementation uses the getdents() syscall directly, rather than + * libc's readdir(). + */ +#ifndef BFS_USE_GETDENTS +# define BFS_USE_GETDENTS (__linux__ || __FreeBSD__) +#endif + /** * A directory. */ @@ -129,18 +137,22 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de); int bfs_closedir(struct bfs_dir *dir); /** - * Extract the file descriptor from an open directory. + * Whether the bfs_unwrapdir() function is supported. + */ +#ifndef BFS_USE_UNWRAPDIR +# define BFS_USE_UNWRAPDIR (BFS_USE_GETDENTS || __FreeBSD__) +#endif + +#if BFS_USE_UNWRAPDIR +/** + * Detach the file descriptor from an open directory. * * @param dir - * The directory to free. - * @param same_fd - * If true, require that the returned file descriptor is the same one - * that bfs_dirfd() would have returned. Otherwise, it may be a new - * file descriptor for the same directory. + * The directory to detach. * @return - * On success, a file descriptor for the directory is returned. On - * failure, -1 is returned, and the directory remains open. + * The file descriptor of the directory. */ -int bfs_fdclosedir(struct bfs_dir *dir, bool same_fd); +int bfs_unwrapdir(struct bfs_dir *dir); +#endif #endif // BFS_DIR_H -- cgit v1.2.3 From 1c76857397100142295f15abebe84f7f26daa6f5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 11 Jul 2023 08:54:30 -0400 Subject: eval: Don't oversubscribe the CPU by default --- src/eval.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/eval.c b/src/eval.c index 56753af..0faf533 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1548,8 +1548,8 @@ done: static int infer_nproc(void) { long nproc = sysconf(_SC_NPROCESSORS_ONLN); - if (nproc < 0) { - nproc = 0; + if (nproc < 1) { + nproc = 1; } else if (nproc > 8) { // Not much speedup after 8 threads nproc = 8; @@ -1651,7 +1651,7 @@ int bfs_eval(const struct bfs_ctx *ctx) { if (ctx->threads > 0) { nthreads = ctx->threads - 1; } else { - nthreads = infer_nproc(); + nthreads = infer_nproc() - 1; } struct bftw_args bftw_args = { -- cgit v1.2.3 From dcb6cf524a1d7279457a3e1e7fecff5f46181ab4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 11 Jul 2023 10:21:26 -0400 Subject: ioq: Separate slot and queue operations --- src/ioq.c | 124 +++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 0544044..c66ebda 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -44,6 +44,28 @@ static void ioq_monitor_destroy(struct ioq_monitor *monitor) { mutex_destroy(&monitor->mutex); } +/** A single entry in a command queue. */ +typedef atomic uintptr_t ioq_slot; + +/** Slot flag bit to indicate waiters. */ +#define IOQ_BLOCKED ((uintptr_t)1) +bfs_static_assert(alignof(struct ioq_ent) > 1); + +/** Check if a slot has waiters. */ +static bool ioq_slot_blocked(uintptr_t value) { + return value & IOQ_BLOCKED; +} + +/** Extract the pointer from a slot. */ +static struct ioq_ent *ioq_slot_ptr(uintptr_t value) { + return (struct ioq_ent *)(value & ~IOQ_BLOCKED); +} + +/** Check if a slot is empty. */ +static bool ioq_slot_empty(uintptr_t value) { + return !ioq_slot_ptr(value); +} + /** * An MPMC queue of I/O commands. */ @@ -62,7 +84,7 @@ struct ioqq { cache_align atomic size_t tail; /** The circular buffer itself. */ - cache_align atomic uintptr_t slots[]; + cache_align ioq_slot slots[]; }; // If we assign slots sequentially, threads will likely be operating on @@ -73,13 +95,9 @@ struct ioqq { // still use every available slot. Since the length is a power of two, that // means the stride must be odd. -#define IOQ_STRIDE ((FALSE_SHARING_SIZE / sizeof(atomic uintptr_t)) | 1) +#define IOQ_STRIDE ((FALSE_SHARING_SIZE / sizeof(ioq_slot)) | 1) bfs_static_assert(IOQ_STRIDE % 2 == 1); -/** Slot flag bit to indicate waiters. */ -#define IOQ_BLOCKED ((uintptr_t)1) -bfs_static_assert(alignof(struct ioq_ent) > 1); - /** Destroy an I/O command queue. */ static void ioqq_destroy(struct ioqq *ioqq) { for (size_t i = 0; i < ioqq->monitor_mask + 1; ++i) { @@ -129,9 +147,11 @@ static struct ioqq *ioqq_create(size_t size) { } /** Atomically wait for a slot to change. */ -static uintptr_t ioqq_wait(struct ioqq *ioqq, size_t i, uintptr_t value) { - atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; +static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) { + fetch_or(slot, IOQ_BLOCKED, relaxed); + value |= IOQ_BLOCKED; + size_t i = slot - ioqq->slots; struct ioq_monitor *monitor = &ioqq->monitors[i & ioqq->monitor_mask]; mutex_lock(&monitor->mutex); @@ -147,11 +167,12 @@ static uintptr_t ioqq_wait(struct ioqq *ioqq, size_t i, uintptr_t value) { } /** Wake up any threads waiting on a slot. */ -static void ioqq_wake(struct ioqq *ioqq, size_t i) { +static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { + size_t i = slot - ioqq->slots; struct ioq_monitor *monitor = &ioqq->monitors[i & ioqq->monitor_mask]; // The following implementation would clearly avoid the missed wakeup - // issue mentioned above in ioqq_wait(): + // issue mentioned above in ioq_slot_wait(): // // mutex_lock(&monitor->mutex); // cond_broadcast(&monitor->cond); @@ -166,75 +187,80 @@ static void ioqq_wake(struct ioqq *ioqq, size_t i) { cond_broadcast(&monitor->cond); } -/** Push an entry onto the queue. */ -static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { +/** Get the next slot for writing. */ +static ioq_slot *ioqq_write(struct ioqq *ioqq) { size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); - atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; + return &ioqq->slots[i & ioqq->slot_mask]; +} +/** Push an entry into a slot. */ +static void ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent) { uintptr_t addr = (uintptr_t)ent; - bfs_assert(!(addr & IOQ_BLOCKED)); + bfs_assert(!ioq_slot_blocked(addr)); uintptr_t prev = load(slot, relaxed); do { - while (prev & ~IOQ_BLOCKED) { - prev = fetch_or(slot, IOQ_BLOCKED, relaxed); - if (prev & ~IOQ_BLOCKED) { - prev = ioqq_wait(ioqq, i, prev | IOQ_BLOCKED); - } + while (!ioq_slot_empty(prev)) { + prev = ioq_slot_wait(ioqq, slot, prev); } } while (!compare_exchange_weak(slot, &prev, addr, release, relaxed)); - if (prev & IOQ_BLOCKED) { - ioqq_wake(ioqq, i); + if (ioq_slot_blocked(prev)) { + ioq_slot_wake(ioqq, slot); } } -/** Pop an entry from the queue. */ -static struct ioq_ent *ioqq_pop(struct ioqq *ioqq) { +/** Push an entry onto the queue. */ +static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { + ioq_slot *slot = ioqq_write(ioqq); + ioq_slot_push(ioqq, slot, ent); +} + +/** Get the next slot for reading. */ +static ioq_slot *ioqq_read(struct ioqq *ioqq) { size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); - atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; + return &ioqq->slots[i & ioqq->slot_mask]; +} +/** (Try to) pop an entry from a slot. */ +static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool block) { uintptr_t prev = load(slot, relaxed); do { - while (!(prev & ~IOQ_BLOCKED)) { - prev = fetch_or(slot, IOQ_BLOCKED, relaxed); - if (!(prev & ~IOQ_BLOCKED)) { - prev = ioqq_wait(ioqq, i, IOQ_BLOCKED); + while (ioq_slot_empty(prev)) { + if (block) { + prev = ioq_slot_wait(ioqq, slot, prev); + } else { + return NULL; } } } while (!compare_exchange_weak(slot, &prev, 0, acquire, relaxed)); - if (prev & IOQ_BLOCKED) { - ioqq_wake(ioqq, i); + if (ioq_slot_blocked(prev)) { + ioq_slot_wake(ioqq, slot); } - prev &= ~IOQ_BLOCKED; - return (struct ioq_ent *)prev; + return ioq_slot_ptr(prev); +} + +/** Pop an entry from the queue. */ +static struct ioq_ent *ioqq_pop(struct ioqq *ioqq) { + ioq_slot *slot = ioqq_read(ioqq); + return ioq_slot_pop(ioqq, slot, true); } /** Pop an entry from the queue if one is available. */ static struct ioq_ent *ioqq_trypop(struct ioqq *ioqq) { size_t i = load(&ioqq->tail, relaxed); - atomic uintptr_t *slot = &ioqq->slots[i & ioqq->slot_mask]; + ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; - uintptr_t prev = load(slot, relaxed); - if (!(prev & ~IOQ_BLOCKED)) { - return NULL; - } - if (!compare_exchange_weak(slot, &prev, 0, acquire, relaxed)) { - return NULL; + struct ioq_ent *ret = ioq_slot_pop(ioqq, slot, false); + if (ret) { + size_t j = exchange(&ioqq->tail, i + IOQ_STRIDE, relaxed); + bfs_assert(j == i, "Detected multiple consumers"); + (void)j; } - if (prev & IOQ_BLOCKED) { - ioqq_wake(ioqq, i); - } - prev &= ~IOQ_BLOCKED; - - size_t j = exchange(&ioqq->tail, i + IOQ_STRIDE, relaxed); - bfs_assert(j == i, "ioqq_trypop() only supports a single consumer"); - (void)j; - - return (struct ioq_ent *)prev; + return ret; } /** Sentinel stop command. */ -- cgit v1.2.3 From d9fb2e5e76e1e2de63af78f4de308c3de577c90b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 12 Jul 2023 13:12:27 -0400 Subject: ioq: Try harder to avoid setting IOQ_BLOCKED --- src/ioq.c | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index c66ebda..a394e07 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -148,20 +148,30 @@ static struct ioqq *ioqq_create(size_t size) { /** Atomically wait for a slot to change. */ static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) { - fetch_or(slot, IOQ_BLOCKED, relaxed); - value |= IOQ_BLOCKED; - size_t i = slot - ioqq->slots; struct ioq_monitor *monitor = &ioqq->monitors[i & ioqq->monitor_mask]; mutex_lock(&monitor->mutex); - uintptr_t ret; - while ((ret = load(slot, relaxed)) == value) { + uintptr_t ret = load(slot, relaxed); + if (ret != value) { + goto done; + } + + if (!(value & IOQ_BLOCKED)) { + value |= IOQ_BLOCKED; + if (!compare_exchange_strong(slot, &ret, value, relaxed, relaxed)) { + goto done; + } + } + + do { // To avoid missed wakeups, it is important that // cond_broadcast() is not called right here cond_wait(&monitor->cond, &monitor->mutex); - } + ret = load(slot, relaxed); + } while (ret == value); +done: mutex_unlock(&monitor->mutex); return ret; } -- cgit v1.2.3 From afdc5dca7ca378cb75fe2a2c9df3881a89ba17dd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 12 Jul 2023 14:10:29 -0400 Subject: tests/bfs: Add tests for -j --- tests/bfs/j1.out | 19 +++++++++++++++++++ tests/bfs/j1.sh | 1 + tests/bfs/j64.out | 19 +++++++++++++++++++ tests/bfs/j64.sh | 1 + tests/bfs/j_negative.sh | 1 + 5 files changed, 41 insertions(+) create mode 100644 tests/bfs/j1.out create mode 100644 tests/bfs/j1.sh create mode 100644 tests/bfs/j64.out create mode 100644 tests/bfs/j64.sh create mode 100644 tests/bfs/j_negative.sh diff --git a/tests/bfs/j1.out b/tests/bfs/j1.out new file mode 100644 index 0000000..a7ccfe4 --- /dev/null +++ b/tests/bfs/j1.out @@ -0,0 +1,19 @@ +basic +basic/a +basic/b +basic/c +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/bfs/j1.sh b/tests/bfs/j1.sh new file mode 100644 index 0000000..972ac1b --- /dev/null +++ b/tests/bfs/j1.sh @@ -0,0 +1 @@ +bfs_diff -j1 basic diff --git a/tests/bfs/j64.out b/tests/bfs/j64.out new file mode 100644 index 0000000..a7ccfe4 --- /dev/null +++ b/tests/bfs/j64.out @@ -0,0 +1,19 @@ +basic +basic/a +basic/b +basic/c +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/bfs/j64.sh b/tests/bfs/j64.sh new file mode 100644 index 0000000..c56788f --- /dev/null +++ b/tests/bfs/j64.sh @@ -0,0 +1 @@ +bfs_diff -j64 basic diff --git a/tests/bfs/j_negative.sh b/tests/bfs/j_negative.sh new file mode 100644 index 0000000..809c98c --- /dev/null +++ b/tests/bfs/j_negative.sh @@ -0,0 +1 @@ +! invoke_bfs -j-1 basic -- cgit v1.2.3 From 663a2b938707458de4e87be68cffb6a970e771c6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 12 Jul 2023 14:12:27 -0400 Subject: parse: Reject -j0 --- src/parse.c | 10 +++++++++- tests/bfs/j0.sh | 1 + 2 files changed, 10 insertions(+), 1 deletion(-) create mode 100644 tests/bfs/j0.sh diff --git a/src/parse.c b/src/parse.c index c225a5b..6b1eaa0 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1623,11 +1623,19 @@ static struct bfs_expr *parse_jobs(struct parser_state *state, int arg1, int arg return NULL; } - if (!parse_int(state, expr->argv, expr->argv[0] + 2, &state->ctx->threads, IF_INT | IF_UNSIGNED)) { + unsigned int n; + if (!parse_int(state, expr->argv, expr->argv[0] + 2, &n, IF_INT | IF_UNSIGNED)) { bfs_expr_free(expr); return NULL; } + if (n == 0) { + parse_expr_error(state, expr, "${bld}0${rs} is not enough threads.\n"); + bfs_expr_free(expr); + return NULL; + } + + state->ctx->threads = n; return expr; } diff --git a/tests/bfs/j0.sh b/tests/bfs/j0.sh new file mode 100644 index 0000000..97a7c5c --- /dev/null +++ b/tests/bfs/j0.sh @@ -0,0 +1 @@ +! invoke_bfs -j0 basic -- cgit v1.2.3 From 2cb1ae0b2ecc356706318948839cc80def53fb5e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 11:06:51 -0400 Subject: bfstd: Escape '!' in wordesc() --- src/bfstd.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index e5f9a31..e338831 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -633,11 +633,11 @@ char *wordesc(const char *str) { while (len > 0) { size_t plen = printable_len(str, len); - if (strcspn(str, "|&;<>()$`\\\"' *?[#˜=%") >= plen) { + if (strcspn(str, "|&;<>()$`\\\"' *?[#˜=%!") >= plen) { // Whole chunk is safe // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 cur = xstpencpy(cur, end, str, plen); - } else if (strcspn(str, "`$\\\"") >= plen) { + } else if (strcspn(str, "`$\\\"!") >= plen) { // Safe to double-quote the whole chunk // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 cur = xstpecpy(cur, end, "\""); -- cgit v1.2.3 From 36fed99f5929782b61018d2d2e5a2800fba5e530 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 11:07:05 -0400 Subject: bfstd: Use $'\n' etc. over $'\x0A' --- src/bfstd.c | 40 +++++++++++++++++++++++++++++++++++----- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index e338831..e125be6 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -619,6 +619,31 @@ static size_t unprintable_len(const char *str, size_t len) { return cur - str; } +/** Convert a special char into a well-known escape sequence like "\n". */ +static const char *c_esc(char c) { + // https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html + switch (c) { + case '\a': + return "\\a"; + case '\b': + return "\\b"; + case '\033': + return "\\e"; + case '\f': + return "\\f"; + case '\n': + return "\\n"; + case '\r': + return "\\r"; + case '\t': + return "\\t"; + case '\v': + return "\\v"; + default: + return NULL; + } +} + char *wordesc(const char *str) { size_t len = strlen(str); @@ -666,11 +691,16 @@ char *wordesc(const char *str) { cur = xstpecpy(cur, end, "$'"); size_t uplen = unprintable_len(str, len); for (size_t i = 0; i < uplen; ++i) { - static const char *hex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; - unsigned char byte = str[i]; - cur = xstpecpy(cur, end, "\\x"); - cur = xstpecpy(cur, end, hex[byte / 0x10]); - cur = xstpecpy(cur, end, hex[byte % 0x10]); + const char *esc = c_esc(str[i]); + if (esc) { + cur = xstpecpy(cur, end, esc); + } else { + static const char *hex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; + unsigned char byte = str[i]; + cur = xstpecpy(cur, end, "\\x"); + cur = xstpecpy(cur, end, hex[byte / 0x10]); + cur = xstpecpy(cur, end, hex[byte % 0x10]); + } } cur = xstpecpy(cur, end, "'"); -- cgit v1.2.3 From e79f0d038d3ce916e744fd111b70d687f699c0bd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 12:36:10 -0400 Subject: bfstd: Quote the whole string the same way in wordesc() --- src/bfstd.c | 178 ++++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 101 insertions(+), 77 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index e125be6..6f39f54 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -593,34 +593,8 @@ static size_t printable_len(const char *str, size_t len) { return cur - str; } -/** Get the length of the longest unprintable prefix of a string. */ -static size_t unprintable_len(const char *str, size_t len) { - mbstate_t mb; - memset(&mb, 0, sizeof(mb)); - - const char *cur = str; - while (len > 0) { - wchar_t wc; - size_t mblen = mbrtowc(&wc, cur, len, &mb); - if (mblen == (size_t)-1) { - // Invalid byte sequence, try again from the next byte - mblen = 1; - } else if (mblen == (size_t)-2) { - // Incomplete byte sequence, the rest is unprintable - mblen = len; - } else if (iswprint(wc)) { - break; - } - - cur += mblen; - len -= mblen; - } - - return cur - str; -} - /** Convert a special char into a well-known escape sequence like "\n". */ -static const char *c_esc(char c) { +static const char *dollar_esc(char c) { // https://www.gnu.org/software/bash/manual/html_node/ANSI_002dC-Quoting.html switch (c) { case '\a': @@ -639,73 +613,123 @@ static const char *c_esc(char c) { return "\\t"; case '\v': return "\\v"; + case '\'': + return "\\'"; + case '\\': + return "\\\\"; default: return NULL; } } -char *wordesc(const char *str) { - size_t len = strlen(str); +/** $'Quote' a string for the shell. */ +static char *dollar_quote(char *dest, char *end, const char *str, size_t len) { + static const char *hex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; - // Worst case: every char is replaced with $'\xXX', so at most a 7x growth - size_t max_size = 7 * len + 3; - char *ret = malloc(max_size); - if (!ret) { - return NULL; - } - char *cur = ret; - char *end = ret + max_size; + dest = xstpecpy(dest, end, "$'"); while (len > 0) { size_t plen = printable_len(str, len); - if (strcspn(str, "|&;<>()$`\\\"' *?[#˜=%!") >= plen) { - // Whole chunk is safe - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 - cur = xstpencpy(cur, end, str, plen); - } else if (strcspn(str, "`$\\\"!") >= plen) { - // Safe to double-quote the whole chunk - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 - cur = xstpecpy(cur, end, "\""); - cur = xstpencpy(cur, end, str, plen); - cur = xstpecpy(cur, end, "\""); + size_t elen = strcspn(str, "'\\"); + size_t min = plen < elen ? plen : elen; + dest = xstpencpy(dest, end, str, min); + str += min; + len -= min; + if (len == 0) { + break; + } + + unsigned char byte = *str; + ++str; + --len; + + const char *esc = dollar_esc(byte); + if (esc) { + dest = xstpecpy(dest, end, esc); } else { - // Single-quote the whole chunk, convert ' into '\'' - cur = xstpecpy(cur, end, "'"); - for (size_t i = 0; i < plen; ++i) { - if (str[i] == '\'') { - cur = xstpecpy(cur, end, "'\\''"); - } else { - cur = xstpencpy(cur, end, &str[i], 1); - } - } - cur = xstpecpy(cur, end, "'"); + dest = xstpecpy(dest, end, "\\x"); + dest = xstpecpy(dest, end, hex[byte / 0x10]); + dest = xstpecpy(dest, end, hex[byte % 0x10]); } + } - str += plen; - len -= plen; - if (len == 0) { - break; + return xstpecpy(dest, end, "'"); +} + +/** How much of this string is safe as a bare word? */ +static size_t bare_len(const char *str) { + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 + return strcspn(str, "|&;<>()$`\\\"' *?[#˜=%!"); +} + +/** How much of this string is safe to double-quote? */ +static size_t quotable_len(const char *str) { + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 + return strcspn(str, "`$\\\"!"); +} + +/** "Quote" a string for the shell. */ +static char *double_quote(char *dest, char *end, const char *str) { + dest = xstpecpy(dest, end, "\""); + dest = xstpecpy(dest, end, str); + return xstpecpy(dest, end, "\""); +} + +/** 'Quote' a string for the shell. */ +static char *single_quote(char *dest, char *end, const char *str) { + bool open = false; + + while (*str) { + size_t len = strcspn(str, "'"); + if (len > 0) { + if (!open) { + dest = xstpecpy(dest, end, "'"); + open = true; + } + dest = xstpencpy(dest, end, str, len); + str += len; } - // Non-printable characters, write them as $'\xXX\xXX...' - cur = xstpecpy(cur, end, "$'"); - size_t uplen = unprintable_len(str, len); - for (size_t i = 0; i < uplen; ++i) { - const char *esc = c_esc(str[i]); - if (esc) { - cur = xstpecpy(cur, end, esc); - } else { - static const char *hex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; - unsigned char byte = str[i]; - cur = xstpecpy(cur, end, "\\x"); - cur = xstpecpy(cur, end, hex[byte / 0x10]); - cur = xstpecpy(cur, end, hex[byte % 0x10]); + while (*str == '\'') { + if (open) { + dest = xstpecpy(dest, end, "'"); + open = false; } + dest = xstpecpy(dest, end, "\\'"); + ++str; } - cur = xstpecpy(cur, end, "'"); + } - str += uplen; - len -= uplen; + if (open) { + dest = xstpecpy(dest, end, "'"); + } + return dest; +} + +char *wordesc(const char *str) { + size_t len = strlen(str); + + // Worst case: every char is replaced with $'\xXX', so at most a 7x growth + size_t max_size = 7 * len + 3; + char *ret = malloc(max_size); + if (!ret) { + return NULL; + } + char *cur = ret; + char *end = ret + max_size; + + if (printable_len(str, len) < len) { + // String contains unprintable chars, use $'this\x7Fsyntax' + cur = dollar_quote(cur, end, str, len); + } else if (bare_len(str) == len) { + // Whole string is safe as a bare word + cur = xstpecpy(cur, end, str); + } else if (quotable_len(str) == len) { + // Whole string is safe to double-quote + cur = double_quote(cur, end, str); + } else { + // Single-quote the whole string + cur = single_quote(cur, end, str); } if (cur == ret) { -- cgit v1.2.3 From 2c396fce53100cad4e472f29851f07030a80ee50 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 13:30:16 -0400 Subject: bfstd: Support wordesc() without allocating --- src/bfstd.c | 92 +++++++++++++++++++++++++++++++++++------------------------ src/bfstd.h | 47 +++++++++++++++++++++++++++--- src/color.c | 20 +++++-------- src/diag.c | 29 ++++++++++--------- src/diag.h | 4 +-- src/dstring.c | 22 ++++++++++++++ src/dstring.h | 31 ++++++++++++++++++++ 7 files changed, 175 insertions(+), 70 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 6f39f54..97fa3b3 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include #include @@ -575,7 +576,7 @@ size_t xstrwidth(const char *str) { } /** Get the length of the longest printable prefix of a string. */ -static size_t printable_len(const char *str, size_t len) { +static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) { mbstate_t mb; memset(&mb, 0, sizeof(mb)); @@ -583,9 +584,23 @@ static size_t printable_len(const char *str, size_t len) { while (len > 0) { wchar_t wc; size_t mblen = mbrtowc(&wc, cur, len, &mb); - if (mblen == (size_t)-1 || mblen == (size_t)-2 || !iswprint(wc)) { + if (mblen == (size_t)-1 || mblen == (size_t)-2) { break; } + + bool safe = iswprint(wc); + + // Technically a literal newline is safe inside single quotes, + // but $'\n' is much nicer than ' + // ' + if (!(flags & WESC_SHELL) && iswspace(wc)) { + safe = true; + } + + if (!safe) { + break; + } + cur += mblen; len -= mblen; } @@ -623,13 +638,13 @@ static const char *dollar_esc(char c) { } /** $'Quote' a string for the shell. */ -static char *dollar_quote(char *dest, char *end, const char *str, size_t len) { +static char *dollar_quote(char *dest, char *end, const char *str, size_t len, enum wesc_flags flags) { static const char *hex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; dest = xstpecpy(dest, end, "$'"); while (len > 0) { - size_t plen = printable_len(str, len); + size_t plen = printable_len(str, len, flags); size_t elen = strcspn(str, "'\\"); size_t min = plen < elen ? plen : elen; dest = xstpencpy(dest, end, str, min); @@ -657,85 +672,86 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len) { } /** How much of this string is safe as a bare word? */ -static size_t bare_len(const char *str) { +static size_t bare_len(const char *str, size_t len) { // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 - return strcspn(str, "|&;<>()$`\\\"' *?[#˜=%!"); + size_t ret = strcspn(str, "|&;<>()$`\\\"' *?[#˜=%!"); + return ret < len ? ret : len; } /** How much of this string is safe to double-quote? */ -static size_t quotable_len(const char *str) { +static size_t quotable_len(const char *str, size_t len) { // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02_03 - return strcspn(str, "`$\\\"!"); + size_t ret = strcspn(str, "`$\\\"!"); + return ret < len ? ret : len; } /** "Quote" a string for the shell. */ -static char *double_quote(char *dest, char *end, const char *str) { +static char *double_quote(char *dest, char *end, const char *str, size_t len) { dest = xstpecpy(dest, end, "\""); - dest = xstpecpy(dest, end, str); + dest = xstpencpy(dest, end, str, len); return xstpecpy(dest, end, "\""); } /** 'Quote' a string for the shell. */ -static char *single_quote(char *dest, char *end, const char *str) { +static char *single_quote(char *dest, char *end, const char *str, size_t len) { bool open = false; - while (*str) { - size_t len = strcspn(str, "'"); - if (len > 0) { + while (len > 0) { + size_t chunk = strcspn(str, "'"); + chunk = chunk < len ? chunk : len; + if (chunk > 0) { if (!open) { dest = xstpecpy(dest, end, "'"); open = true; } - dest = xstpencpy(dest, end, str, len); - str += len; + dest = xstpencpy(dest, end, str, chunk); + str += chunk; + len -= chunk; } - while (*str == '\'') { + while (len > 0 && *str == '\'') { if (open) { dest = xstpecpy(dest, end, "'"); open = false; } dest = xstpecpy(dest, end, "\\'"); ++str; + --len; } } if (open) { dest = xstpecpy(dest, end, "'"); } + return dest; } -char *wordesc(const char *str) { - size_t len = strlen(str); +char *wordesc(char *dest, char *end, const char *str, enum wesc_flags flags) { + return wordnesc(dest, end, str, SIZE_MAX, flags); +} - // Worst case: every char is replaced with $'\xXX', so at most a 7x growth - size_t max_size = 7 * len + 3; - char *ret = malloc(max_size); - if (!ret) { - return NULL; - } - char *cur = ret; - char *end = ret + max_size; +char *wordnesc(char *dest, char *end, const char *str, size_t n, enum wesc_flags flags) { + size_t len = strnlen(str, n); + char *start = dest; - if (printable_len(str, len) < len) { + if (printable_len(str, len, flags) < len) { // String contains unprintable chars, use $'this\x7Fsyntax' - cur = dollar_quote(cur, end, str, len); - } else if (bare_len(str) == len) { + dest = dollar_quote(dest, end, str, len, flags); + } else if (!(flags & WESC_SHELL) || bare_len(str, len) == len) { // Whole string is safe as a bare word - cur = xstpecpy(cur, end, str); - } else if (quotable_len(str) == len) { + dest = xstpencpy(dest, end, str, len); + } else if (quotable_len(str, len) == len) { // Whole string is safe to double-quote - cur = double_quote(cur, end, str); + dest = double_quote(dest, end, str, len); } else { // Single-quote the whole string - cur = single_quote(cur, end, str); + dest = single_quote(dest, end, str, len); } - if (cur == ret) { - cur = xstpecpy(cur, end, "\"\""); + if (dest == start) { + dest = xstpecpy(dest, end, "\"\""); } - bfs_assert(cur != end, "Result truncated!"); - return ret; + return dest; } diff --git a/src/bfstd.h b/src/bfstd.h index 832db66..fb77399 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -319,15 +319,54 @@ size_t xstrwidth(const char *str); // #include +/** + * Flags for wordesc(). + */ +enum wesc_flags { + /** + * Escape special characters so that the shell will treat the escaped + * string as a single word. + */ + WESC_SHELL = 1 << 0, + /** + * Escape special characters so that the escaped string is safe to print + * to a TTY. + */ + WESC_TTY = 1 << 1, +}; + /** * Escape a string as a single shell word. * - * @param str + * @param dest + * The destination string to fill. + * @param end + * The end of the destination buffer. + * @param src + * The string to escape. + * @param flags + * Controls which characters to escape. + * @return + * The new NUL terminator of the destination, or `end` on truncation. + */ +char *wordesc(char *dest, char *end, const char *str, enum wesc_flags flags); + +/** + * Escape a string as a single shell word. + * + * @param dest + * The destination string to fill. + * @param end + * The end of the destination buffer. + * @param src * The string to escape. + * @param n + * The maximum length of the string. + * @param flags + * Controls which characters to escape. * @return - * A string that a shell would evaluate to str, dynamically allocated, - * or NULL on failure. + * The new NUL terminator of the destination, or `end` on truncation. */ -char *wordesc(const char *str); +char *wordnesc(char *dest, char *end, const char *str, size_t n, enum wesc_flags flags); #endif // BFS_BFSTD_H diff --git a/src/color.c b/src/color.c index 2e039f4..0f5829f 100644 --- a/src/color.c +++ b/src/color.c @@ -16,6 +16,7 @@ #include #include #include +#include #include #include #include @@ -891,6 +892,11 @@ static int print_reset(CFILE *cfile) { } } +/** Print a shell-escaped string. */ +static int print_wordesc(CFILE *cfile, const char *str, size_t n, enum wesc_flags flags) { + return dstrnescat(&cfile->buffer, str, n, flags); +} + /** Print a string with an optional color. */ static int print_colored(CFILE *cfile, const struct esc_seq *esc, const char *str, size_t len) { if (print_esc(cfile, esc) != 0) { @@ -1069,18 +1075,6 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { return ret; } -/** Print an shell-escaped string. */ -static int print_wordesc(CFILE *cfile, const char *str) { - char *esc = wordesc(str); - if (!esc) { - return -1; - } - - int ret = dstrcat(&cfile->buffer, esc); - free(esc); - return ret; -} - /** Format some colored output to the buffer. */ BFS_FORMATTER(2, 3) static int cbuff(CFILE *cfile, const char *format, ...); @@ -1224,7 +1218,7 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { case 'p': switch (*++i) { case 'q': - if (print_wordesc(cfile, va_arg(args, const char *)) != 0) { + if (print_wordesc(cfile, va_arg(args, const char *), SIZE_MAX, WESC_SHELL | WESC_TTY) != 0) { return -1; } break; diff --git a/src/diag.c b/src/diag.c index acea9ad..0590847 100644 --- a/src/diag.c +++ b/src/diag.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "diag.h" +#include "alloc.h" #include "bfstd.h" #include "ctx.h" #include "color.h" @@ -114,7 +115,7 @@ bool bfs_debug_prefix(const struct bfs_ctx *ctx, enum debug_flags flag) { } /** Recursive part of highlight_expr(). */ -static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs_expr *expr, bool *args) { +static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs_expr *expr, bool args[]) { if (!expr) { return false; } @@ -141,7 +142,7 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs } /** Highlight an expression in the command line. */ -static bool highlight_expr(const struct bfs_ctx *ctx, const struct bfs_expr *expr, bool *args) { +static bool highlight_expr(const struct bfs_ctx *ctx, const struct bfs_expr *expr, bool args[]) { for (size_t i = 0; i < ctx->argc; ++i) { args[i] = false; } @@ -150,21 +151,21 @@ static bool highlight_expr(const struct bfs_ctx *ctx, const struct bfs_expr *exp } /** Print a highlighted portion of the command line. */ -static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool *args, bool warning) { +static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool args[], bool warning) { if (warning) { bfs_warning_prefix(ctx); } else { bfs_error_prefix(ctx); } - char *argv[ctx->argc]; + char **argv = ZALLOC_ARRAY(char *, ctx->argc); + if (!argv) { + return; + } + for (size_t i = 0; i < ctx->argc; ++i) { - argv[i] = wordesc(ctx->argv[i]); - if (!argv[i]) { - for (size_t j = 0; j < i; ++j) { - free(argv[j]); - } - return; + if (dstrescat(&argv[i], ctx->argv[i], WESC_SHELL | WESC_TTY) != 0) { + goto done; } } @@ -223,12 +224,14 @@ static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool *args, bool warn cfprintf(ctx->cerr, "\n"); +done: for (size_t i = 0; i < ctx->argc; ++i) { - free(argv[i]); + dstrfree(argv[i]); } + free(argv); } -void bfs_argv_error(const struct bfs_ctx *ctx, const bool *args) { +void bfs_argv_error(const struct bfs_ctx *ctx, const bool args[]) { bfs_argv_diag(ctx, args, false); } @@ -239,7 +242,7 @@ void bfs_expr_error(const struct bfs_ctx *ctx, const struct bfs_expr *expr) { } } -bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool *args) { +bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool args[]) { if (!ctx->warn) { return false; } diff --git a/src/diag.h b/src/diag.h index c909da5..e019db0 100644 --- a/src/diag.h +++ b/src/diag.h @@ -149,7 +149,7 @@ bool bfs_debug_prefix(const struct bfs_ctx *ctx, enum debug_flags flag); /** * Highlight parts of the command line in an error message. */ -void bfs_argv_error(const struct bfs_ctx *ctx, const bool *args); +void bfs_argv_error(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in an error message. @@ -159,7 +159,7 @@ void bfs_expr_error(const struct bfs_ctx *ctx, const struct bfs_expr *expr); /** * Highlight parts of the command line in a warning message. */ -bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool *args); +bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in a warning message. diff --git a/src/dstring.c b/src/dstring.c index dada70b..60a7df9 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -232,6 +232,28 @@ fail: return -1; } +int dstrescat(char **dest, const char *str, enum wesc_flags flags) { + return dstrnescat(dest, str, SIZE_MAX, flags); +} + +int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags) { + size_t len = *dest ? dstrlen(*dest) : 0; + + // Worst case growth is `ccc...` => $'\xCC\xCC\xCC...' + n = strnlen(str, n); + size_t cap = len + 4 * n + 3; + if (dstreserve(dest, cap) != 0) { + return -1; + } + + char *cur = *dest + len; + char *end = *dest + cap + 1; + cur = wordnesc(cur, end, str, n, flags); + bfs_assert(cur != end, "wordesc() result truncated"); + + return dstresize(dest, cur - *dest); +} + void dstrfree(char *dstr) { if (dstr) { free(dstrheader(dstr)); diff --git a/src/dstring.h b/src/dstring.h index 2673f1b..88ca79f 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -8,6 +8,7 @@ #ifndef BFS_DSTRING_H #define BFS_DSTRING_H +#include "bfstd.h" #include "config.h" #include #include @@ -261,6 +262,36 @@ int dstrcatf(char **str, const char *format, ...); BFS_FORMATTER(2, 0) int dstrvcatf(char **str, const char *format, va_list args); +/** + * Concatenate while shell-escaping. + * + * @param dest + * The destination dynamic string. + * @param str + * The string to escape. + * @param flags + * Flags for wordesc(). + * @return + * 0 on success, -1 on failure. + */ +int dstrescat(char **dest, const char *str, enum wesc_flags flags); + +/** + * Concatenate while shell-escaping. + * + * @param dest + * The destination dynamic string. + * @param str + * The string to escape. + * @param n + * The maximum length of the string. + * @param flags + * Flags for wordesc(). + * @return + * 0 on success, -1 on failure. + */ +int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags); + /** * Free a dynamic string. * -- cgit v1.2.3 From b577bedfd37d93cb802c36beb93cf6581af51930 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 13:40:46 -0400 Subject: color: Get rid of EXT_MAX --- src/color.c | 40 ++++++++++++++++++++++++++++++---------- tests/bfs/color_deep.out | 16 ++++++++++++++++ tests/bfs/color_deep.sh | 7 +++++++ 3 files changed, 53 insertions(+), 10 deletions(-) create mode 100644 tests/bfs/color_deep.out create mode 100644 tests/bfs/color_deep.sh diff --git a/src/color.c b/src/color.c index 0f5829f..00d7920 100644 --- a/src/color.c +++ b/src/color.c @@ -106,6 +106,8 @@ struct colors { /** Number of extensions. */ size_t ext_count; + /** Longest extension. */ + size_t ext_len; /** Case-sensitive extension trie. */ struct trie ext_trie; /** Case-insensitive extension trie. */ @@ -207,9 +209,6 @@ static void ext_tolower(char *ext, size_t len) { } } -/** Maximum supported extension length. */ -#define EXT_MAX 255 - /** * The "smart case" algorithm. * @@ -331,6 +330,11 @@ static int build_iext_trie(struct colors *colors) { trie_init(&colors->iext_trie); TRIE_FOR_EACH(&colors->ext_trie, leaf) { + size_t len = leaf->length - 1; + if (colors->ext_len < len) { + colors->ext_len = len; + } + struct ext_color *ext = leaf->value; if (ext->case_sensitive) { continue; @@ -356,25 +360,40 @@ static int build_iext_trie(struct colors *colors) { * Find a color by an extension. */ static const struct esc_seq *get_ext(const struct colors *colors, const char *filename) { + size_t ext_len = colors->ext_len; size_t name_len = strlen(filename); - size_t ext_len = name_len < EXT_MAX ? name_len : EXT_MAX; + if (name_len < ext_len) { + ext_len = name_len; + } const char *suffix = filename + name_len - ext_len; - char xfrm[ext_len + 1]; - memcpy(xfrm, suffix, sizeof(xfrm)); + char buf[256]; + char *copy; + if (ext_len < sizeof(buf)) { + copy = memcpy(buf, suffix, ext_len + 1); + } else { + copy = strndup(suffix, ext_len); + if (!copy) { + return NULL; + } + } - ext_reverse(xfrm, ext_len); - const struct trie_leaf *leaf = trie_find_prefix(&colors->ext_trie, xfrm); + ext_reverse(copy, ext_len); + const struct trie_leaf *leaf = trie_find_prefix(&colors->ext_trie, copy); const struct ext_color *ext = leaf ? leaf->value : NULL; - ext_tolower(xfrm, ext_len); - const struct trie_leaf *ileaf = trie_find_prefix(&colors->iext_trie, xfrm); + ext_tolower(copy, ext_len); + const struct trie_leaf *ileaf = trie_find_prefix(&colors->iext_trie, copy); const struct ext_color *iext = ileaf ? ileaf->value : NULL; if (iext && (!ext || ext->priority < iext->priority)) { ext = iext; } + if (copy != buf) { + free(copy); + } + return ext ? ext->esc : NULL; } @@ -601,6 +620,7 @@ struct colors *parse_colors(void) { VARENA_INIT(&colors->ext_arena, struct ext_color, ext); trie_init(&colors->names); colors->ext_count = 0; + colors->ext_len = 0; trie_init(&colors->ext_trie); trie_init(&colors->iext_trie); diff --git a/tests/bfs/color_deep.out b/tests/bfs/color_deep.out new file mode 100644 index 0000000..fb990d5 --- /dev/null +++ b/tests/bfs/color_deep.out @@ -0,0 +1,16 @@ +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE diff --git a/tests/bfs/color_deep.sh b/tests/bfs/color_deep.sh new file mode 100644 index 0000000..a83ee0e --- /dev/null +++ b/tests/bfs/color_deep.sh @@ -0,0 +1,7 @@ +name="0123456789ABCDEF" +name="${name}${name}${name}${name}" +name="${name}${name}${name}${name}" +name="${name:0:255}" +export LS_COLORS="*${name}=01:" + +bfs_diff deep -color -type f -printf '%f\n' -- cgit v1.2.3 From c5a2bfd924445dfccd943ea34374a5d1266f5cb9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 15:22:42 -0400 Subject: color: TTY-escape filenames --- src/color.c | 10 +++++++++- src/color.h | 1 + src/printf.c | 4 ++-- tests/bfs/color.out | 2 ++ tests/bfs/color_L.out | 2 ++ tests/bfs/color_L_ln_target.out | 2 ++ tests/bfs/color_L_no_stat.out | 2 ++ tests/bfs/color_cd0_no.out | 2 ++ tests/bfs/color_escapes.out | 2 ++ tests/bfs/color_ext.out | 2 ++ tests/bfs/color_ext0.out | 2 ++ tests/bfs/color_ext_case.out | 2 ++ tests/bfs/color_ext_override.out | 2 ++ tests/bfs/color_ext_underride.out | 2 ++ tests/bfs/color_fi0_no.out | 2 ++ tests/bfs/color_fi_no.out | 2 ++ tests/bfs/color_ln_target.out | 2 ++ tests/bfs/color_mh.out | 2 ++ tests/bfs/color_mh0.out | 2 ++ tests/bfs/color_mi.out | 2 ++ tests/bfs/color_missing_colon.out | 2 ++ tests/bfs/color_no.out | 2 ++ tests/bfs/color_no_stat.out | 2 ++ tests/bfs/color_nul.out | 2 ++ tests/bfs/color_or.out | 2 ++ tests/bfs/color_or0_mi.out | 2 ++ tests/bfs/color_or0_mi0.out | 2 ++ tests/bfs/color_or_mi.out | 2 ++ tests/bfs/color_or_mi0.out | 2 ++ tests/bfs/color_rs_lc_rc_ec.out | 2 ++ tests/bfs/color_st0_tw0_ow.out | 2 ++ tests/bfs/color_st0_tw0_ow0.out | 2 ++ tests/bfs/color_st0_tw_ow.out | 2 ++ tests/bfs/color_st0_tw_ow0.out | 2 ++ tests/bfs/color_st_tw0_ow.out | 2 ++ tests/bfs/color_st_tw0_ow0.out | 2 ++ tests/bfs/color_st_tw_ow0.out | 2 ++ tests/bfs/color_su0_sg.out | 2 ++ tests/bfs/color_su0_sg0.out | 2 ++ tests/bfs/color_su_sg0.out | 2 ++ tests/bfs/nocolor.out | 2 ++ tests/gnu/empty_special.out | 1 + tests/tests.sh | 1 + 43 files changed, 90 insertions(+), 3 deletions(-) diff --git a/src/color.c b/src/color.c index 00d7920..6593f5b 100644 --- a/src/color.c +++ b/src/color.c @@ -922,9 +922,12 @@ static int print_colored(CFILE *cfile, const struct esc_seq *esc, const char *st if (print_esc(cfile, esc) != 0) { return -1; } - if (dstrxcat(&cfile->buffer, str, len) != 0) { + + // Don't let the string itself interfere with the colors + if (print_wordesc(cfile, str, len, WESC_TTY) != 0) { return -1; } + if (print_reset(cfile) != 0) { return -1; } @@ -1242,6 +1245,11 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { return -1; } break; + case 'Q': + if (print_wordesc(cfile, va_arg(args, const char *), SIZE_MAX, WESC_TTY) != 0) { + return -1; + } + break; case 'F': if (print_name(cfile, va_arg(args, const struct BFTW *)) != 0) { diff --git a/src/color.h b/src/color.h index 932d551..0d46c33 100644 --- a/src/color.h +++ b/src/color.h @@ -87,6 +87,7 @@ int cfclose(CFILE *cfile); * %zu: A size_t * %m: strerror(errno) * %pq: A shell-escaped string, like bash's printf %q + * %pQ: A TTY-escaped string. * %pF: A colored file name, from a const struct BFTW * argument * %pP: A colored file path, from a const struct BFTW * argument * %pL: A colored link target, from a const struct BFTW * argument diff --git a/src/printf.c b/src/printf.c index 6520d2d..f0910fa 100644 --- a/src/printf.c +++ b/src/printf.c @@ -298,7 +298,7 @@ static int bfs_printf_h(CFILE *cfile, const struct bfs_printf *directive, const int ret; if (should_color(cfile, directive)) { - ret = cfprintf(cfile, "${di}%s${rs}", buf); + ret = cfprintf(cfile, "${di}%pQ${rs}", buf); } else { ret = dyn_fprintf(cfile->file, directive, buf); } @@ -313,7 +313,7 @@ static int bfs_printf_H(CFILE *cfile, const struct bfs_printf *directive, const if (ftwbuf->depth == 0) { return cfprintf(cfile, "%pP", ftwbuf); } else { - return cfprintf(cfile, "${di}%s${rs}", ftwbuf->root); + return cfprintf(cfile, "${di}%pQ${rs}", ftwbuf->root); } } else { return dyn_fprintf(cfile->file, directive, ftwbuf->root); diff --git a/tests/bfs/color.out b/tests/bfs/color.out index 34c7153..5c6b43e 100644 --- a/tests/bfs/color.out +++ b/tests/bfs/color.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_L.out b/tests/bfs/color_L.out index 89f9410..6904e39 100644 --- a/tests/bfs/color_L.out +++ b/tests/bfs/color_L.out @@ -1,6 +1,8 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh rainbow/chardev_link +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/sticky_ow diff --git a/tests/bfs/color_L_ln_target.out b/tests/bfs/color_L_ln_target.out index 2562c98..50105c3 100644 --- a/tests/bfs/color_L_ln_target.out +++ b/tests/bfs/color_L_ln_target.out @@ -1,7 +1,9 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/broken rainbow/exec.sh rainbow/chardev_link +rainbow/$'\e[1m' rainbow/socket rainbow/sticky_ow rainbow/sgid diff --git a/tests/bfs/color_L_no_stat.out b/tests/bfs/color_L_no_stat.out index 4fe99c0..beb538c 100644 --- a/tests/bfs/color_L_no_stat.out +++ b/tests/bfs/color_L_no_stat.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/chardev_link +rainbow/$'\e[1m' rainbow/ow rainbow/sticky rainbow/sticky_ow diff --git a/tests/bfs/color_cd0_no.out b/tests/bfs/color_cd0_no.out index 30ad97f..4d710fd 100644 --- a/tests/bfs/color_cd0_no.out +++ b/tests/bfs/color_cd0_no.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/file.dat diff --git a/tests/bfs/color_escapes.out b/tests/bfs/color_escapes.out index 808585e..746a9ea 100644 --- a/tests/bfs/color_escapes.out +++ b/tests/bfs/color_escapes.out @@ -1,5 +1,7 @@ +:$'rainbow/\e[1m/'$'\e[0m' :rainbow :rainbow/:exec.sh +:rainbow/:$'\e[1m' :rainbow/:socket :rainbow/:broken :rainbow/:chardev_link diff --git a/tests/bfs/color_ext.out b/tests/bfs/color_ext.out index be22b82..a35ca0b 100644 --- a/tests/bfs/color_ext.out +++ b/tests/bfs/color_ext.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ext0.out b/tests/bfs/color_ext0.out index d151319..37301cc 100644 --- a/tests/bfs/color_ext0.out +++ b/tests/bfs/color_ext0.out @@ -1,6 +1,8 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/file.txt rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ext_case.out b/tests/bfs/color_ext_case.out index 4e7258d..9388343 100644 --- a/tests/bfs/color_ext_case.out +++ b/tests/bfs/color_ext_case.out @@ -1,3 +1,4 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/lower.gz rainbow/lower.tar.gz @@ -6,6 +7,7 @@ rainbow/upper.TAR.GZ rainbow/lower.tar rainbow/upper.TAR +rainbow/$'\e[1m' rainbow/ul.TAR.gz rainbow/lu.tar.GZ rainbow/socket diff --git a/tests/bfs/color_ext_override.out b/tests/bfs/color_ext_override.out index 3a09cd2..1e0146c 100644 --- a/tests/bfs/color_ext_override.out +++ b/tests/bfs/color_ext_override.out @@ -1,3 +1,4 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh rainbow/lower.tar @@ -8,6 +9,7 @@ rainbow/ul.TAR.gz rainbow/upper.GZ rainbow/upper.TAR.GZ +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ext_underride.out b/tests/bfs/color_ext_underride.out index 073d176..1ed66da 100644 --- a/tests/bfs/color_ext_underride.out +++ b/tests/bfs/color_ext_underride.out @@ -1,3 +1,4 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/lower.tar.gz rainbow/lu.tar.GZ @@ -8,6 +9,7 @@ rainbow/upper.TAR rainbow/lower.gz rainbow/upper.GZ +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_fi0_no.out b/tests/bfs/color_fi0_no.out index 34c7153..5c6b43e 100644 --- a/tests/bfs/color_fi0_no.out +++ b/tests/bfs/color_fi0_no.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_fi_no.out b/tests/bfs/color_fi_no.out index 8df9355..e64684d 100644 --- a/tests/bfs/color_fi_no.out +++ b/tests/bfs/color_fi_no.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ln_target.out b/tests/bfs/color_ln_target.out index 2562c98..50105c3 100644 --- a/tests/bfs/color_ln_target.out +++ b/tests/bfs/color_ln_target.out @@ -1,7 +1,9 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/broken rainbow/exec.sh rainbow/chardev_link +rainbow/$'\e[1m' rainbow/socket rainbow/sticky_ow rainbow/sgid diff --git a/tests/bfs/color_mh.out b/tests/bfs/color_mh.out index 7521b31..93bfde1 100644 --- a/tests/bfs/color_mh.out +++ b/tests/bfs/color_mh.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_mh0.out b/tests/bfs/color_mh0.out index 34c7153..5c6b43e 100644 --- a/tests/bfs/color_mh0.out +++ b/tests/bfs/color_mh0.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_mi.out b/tests/bfs/color_mi.out index 34c7153..5c6b43e 100644 --- a/tests/bfs/color_mi.out +++ b/tests/bfs/color_mi.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_missing_colon.out b/tests/bfs/color_missing_colon.out index be22b82..a35ca0b 100644 --- a/tests/bfs/color_missing_colon.out +++ b/tests/bfs/color_missing_colon.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_no.out b/tests/bfs/color_no.out index b70e47d..c61d246 100644 --- a/tests/bfs/color_no.out +++ b/tests/bfs/color_no.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_no_stat.out b/tests/bfs/color_no_stat.out index 7d7d767..e57e11a 100644 --- a/tests/bfs/color_no_stat.out +++ b/tests/bfs/color_no_stat.out @@ -1,4 +1,6 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow +rainbow/$'\e[1m' rainbow/ow rainbow/sticky rainbow/sticky_ow diff --git a/tests/bfs/color_nul.out b/tests/bfs/color_nul.out index 4ea4d85..7140e32 100644 --- a/tests/bfs/color_nul.out +++ b/tests/bfs/color_nul.out @@ -1,3 +1,4 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/lower.gz rainbow/lower.tar.gz @@ -6,6 +7,7 @@ rainbow/upper.GZ rainbow/upper.TAR.GZ rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_or.out b/tests/bfs/color_or.out index 98efb63..07916da 100644 --- a/tests/bfs/color_or.out +++ b/tests/bfs/color_or.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/chardev_link rainbow/link.txt diff --git a/tests/bfs/color_or0_mi.out b/tests/bfs/color_or0_mi.out index 34c7153..5c6b43e 100644 --- a/tests/bfs/color_or0_mi.out +++ b/tests/bfs/color_or0_mi.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_or0_mi0.out b/tests/bfs/color_or0_mi0.out index 34c7153..5c6b43e 100644 --- a/tests/bfs/color_or0_mi0.out +++ b/tests/bfs/color_or0_mi0.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_or_mi.out b/tests/bfs/color_or_mi.out index a9dc229..20bc61d 100644 --- a/tests/bfs/color_or_mi.out +++ b/tests/bfs/color_or_mi.out @@ -1,6 +1,8 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/broken rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/chardev_link rainbow/link.txt diff --git a/tests/bfs/color_or_mi0.out b/tests/bfs/color_or_mi0.out index a9dc229..20bc61d 100644 --- a/tests/bfs/color_or_mi0.out +++ b/tests/bfs/color_or_mi0.out @@ -1,6 +1,8 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/broken rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/chardev_link rainbow/link.txt diff --git a/tests/bfs/color_rs_lc_rc_ec.out b/tests/bfs/color_rs_lc_rc_ec.out index 82d94ec..f78b41f 100644 --- a/tests/bfs/color_rs_lc_rc_ec.out +++ b/tests/bfs/color_rs_lc_rc_ec.out @@ -1,4 +1,6 @@ +LC01;34RC$'rainbow/\e[1m/'EC$'\e[0m' LC01;34RCrainbow/ECLC01;32RCexec.shEC +LC01;34RCrainbow/ECLC01;34RC$'\e[1m'EC LC01;34RCrainbow/ECLC01;35RCsocketEC LC01;34RCrainbow/ECLC01;36RCbrokenEC LC01;34RCrainbow/ECLC01;36RCchardev_linkEC diff --git a/tests/bfs/color_st0_tw0_ow.out b/tests/bfs/color_st0_tw0_ow.out index bdc5942..d1fec74 100644 --- a/tests/bfs/color_st0_tw0_ow.out +++ b/tests/bfs/color_st0_tw0_ow.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/sticky rainbow/socket rainbow/broken diff --git a/tests/bfs/color_st0_tw0_ow0.out b/tests/bfs/color_st0_tw0_ow0.out index f13b7f3..cb80cb8 100644 --- a/tests/bfs/color_st0_tw0_ow0.out +++ b/tests/bfs/color_st0_tw0_ow0.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/ow rainbow/sticky rainbow/sticky_ow diff --git a/tests/bfs/color_st0_tw_ow.out b/tests/bfs/color_st0_tw_ow.out index 2d7f682..a183932 100644 --- a/tests/bfs/color_st0_tw_ow.out +++ b/tests/bfs/color_st0_tw_ow.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/sticky rainbow/socket rainbow/broken diff --git a/tests/bfs/color_st0_tw_ow0.out b/tests/bfs/color_st0_tw_ow0.out index 7e343b8..cdc5cdd 100644 --- a/tests/bfs/color_st0_tw_ow0.out +++ b/tests/bfs/color_st0_tw_ow0.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/ow rainbow/sticky rainbow/socket diff --git a/tests/bfs/color_st_tw0_ow.out b/tests/bfs/color_st_tw0_ow.out index c61a327..f45c75c 100644 --- a/tests/bfs/color_st_tw0_ow.out +++ b/tests/bfs/color_st_tw0_ow.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_st_tw0_ow0.out b/tests/bfs/color_st_tw0_ow0.out index 929a993..d95d12a 100644 --- a/tests/bfs/color_st_tw0_ow0.out +++ b/tests/bfs/color_st_tw0_ow0.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/ow rainbow/socket rainbow/broken diff --git a/tests/bfs/color_st_tw_ow0.out b/tests/bfs/color_st_tw_ow0.out index 7092f5a..f1059dd 100644 --- a/tests/bfs/color_st_tw_ow0.out +++ b/tests/bfs/color_st_tw_ow0.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/ow rainbow/socket rainbow/broken diff --git a/tests/bfs/color_su0_sg.out b/tests/bfs/color_su0_sg.out index 2bce534..31d5108 100644 --- a/tests/bfs/color_su0_sg.out +++ b/tests/bfs/color_su0_sg.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_su0_sg0.out b/tests/bfs/color_su0_sg0.out index 0c3d757..4804030 100644 --- a/tests/bfs/color_su0_sg0.out +++ b/tests/bfs/color_su0_sg0.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_su_sg0.out b/tests/bfs/color_su_sg0.out index 7a70598..3dbc7fe 100644 --- a/tests/bfs/color_su_sg0.out +++ b/tests/bfs/color_su_sg0.out @@ -1,5 +1,7 @@ +$'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh +rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/nocolor.out b/tests/bfs/nocolor.out index 29e7de8..d51d24d 100644 --- a/tests/bfs/nocolor.out +++ b/tests/bfs/nocolor.out @@ -1,4 +1,6 @@ rainbow +rainbow/ +rainbow// rainbow/broken rainbow/chardev_link rainbow/exec.sh diff --git a/tests/gnu/empty_special.out b/tests/gnu/empty_special.out index 3aa57d2..fa35478 100644 --- a/tests/gnu/empty_special.out +++ b/tests/gnu/empty_special.out @@ -1,3 +1,4 @@ +rainbow// rainbow/exec.sh rainbow/file.dat rainbow/file.txt diff --git a/tests/tests.sh b/tests/tests.sh index 46e3e33..66a79a8 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -432,6 +432,7 @@ function make_rainbow() { chmod +t "$1"/sticky* "$XTOUCH" -p "$1"/exec.sh chmod +x "$1"/exec.sh + "$XTOUCH" -p "$1/"$'\e[1m/\e[0m' } make_rainbow "$TMP/rainbow" -- cgit v1.2.3 From 0e4dffeb46f3d8bfe30d60a0cfe1e1fa5a082b13 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 15:45:08 -0400 Subject: color: Don't break up leading and trailing dirs --- src/color.c | 59 +++++++++++++++++++++------------------ tests/bfs/color.out | 2 +- tests/bfs/color_L.out | 2 +- tests/bfs/color_L_ln_target.out | 2 +- tests/bfs/color_L_no_stat.out | 8 +++--- tests/bfs/color_cd0_no.out | 2 +- tests/bfs/color_escapes.out | 2 +- tests/bfs/color_ext.out | 2 +- tests/bfs/color_ext0.out | 2 +- tests/bfs/color_ext_case.out | 2 +- tests/bfs/color_ext_override.out | 2 +- tests/bfs/color_ext_underride.out | 2 +- tests/bfs/color_fi0_no.out | 2 +- tests/bfs/color_fi_no.out | 2 +- tests/bfs/color_ln_target.out | 2 +- tests/bfs/color_ls.out | 16 +++++------ tests/bfs/color_mh.out | 2 +- tests/bfs/color_mh0.out | 2 +- tests/bfs/color_mi.out | 2 +- tests/bfs/color_missing_colon.out | 2 +- tests/bfs/color_no.out | 2 +- tests/bfs/color_no_stat.out | 8 +++--- tests/bfs/color_nul.out | 2 +- tests/bfs/color_or.out | 2 +- tests/bfs/color_or0_mi.out | 2 +- tests/bfs/color_or0_mi0.out | 2 +- tests/bfs/color_or_mi.out | 2 +- tests/bfs/color_or_mi0.out | 2 +- tests/bfs/color_rs_lc_rc_ec.out | 2 +- tests/bfs/color_st0_tw0_ow.out | 4 +-- tests/bfs/color_st0_tw0_ow0.out | 8 +++--- tests/bfs/color_st0_tw_ow.out | 4 +-- tests/bfs/color_st0_tw_ow0.out | 6 ++-- tests/bfs/color_st_tw0_ow.out | 2 +- tests/bfs/color_st_tw0_ow0.out | 4 +-- tests/bfs/color_st_tw_ow0.out | 4 +-- tests/bfs/color_su0_sg.out | 2 +- tests/bfs/color_su0_sg0.out | 2 +- tests/bfs/color_su_sg0.out | 2 +- tests/bfs/printf_color.out | 4 ++- 40 files changed, 95 insertions(+), 88 deletions(-) diff --git a/src/color.c b/src/color.c index 6593f5b..7d98978 100644 --- a/src/color.c +++ b/src/color.c @@ -995,27 +995,48 @@ out: return ret; } -/** Print the directories leading up to a file. */ -static int print_dirs_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, size_t nameoff) { - const struct colors *colors = cfile->colors; +/** Print a path with colors. */ +static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + size_t nameoff; + if (path == ftwbuf->path) { + nameoff = ftwbuf->nameoff; + } else { + nameoff = xbaseoff(path); + } + + const char *name = path + nameoff; + size_t pathlen = nameoff + strlen(name); ssize_t broken = first_broken_offset(path, ftwbuf, flags, nameoff); if (broken < 0) { return -1; } + size_t split = broken; - if (broken > 0) { - if (print_colored(cfile, colors->directory, path, broken) != 0) { - return -1; + const struct colors *colors = cfile->colors; + const struct esc_seq *dirs_color = colors->directory; + const struct esc_seq *name_color; + + if (split < nameoff) { + name_color = colors->missing; + if (!name_color) { + name_color = colors->orphan; + } + } else { + name_color = file_color(cfile->colors, path + nameoff, ftwbuf, flags); + if (name_color == dirs_color) { + split = pathlen; } } - if ((size_t)broken < nameoff) { - const struct esc_seq *color = colors->missing; - if (!color) { - color = colors->orphan; + if (split > 0) { + if (print_colored(cfile, dirs_color, path, split) != 0) { + return -1; } - if (print_colored(cfile, color, path + broken, nameoff - broken) != 0) { + } + + if (split < pathlen) { + if (print_colored(cfile, name_color, path + split, pathlen - split) != 0) { return -1; } } @@ -1029,22 +1050,6 @@ static int print_name_colored(CFILE *cfile, const char *name, const struct BFTW return print_colored(cfile, esc, name, strlen(name)); } -/** Print a path with colors. */ -static int print_path_colored(CFILE *cfile, const char *path, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { - size_t nameoff; - if (path == ftwbuf->path) { - nameoff = ftwbuf->nameoff; - } else { - nameoff = xbaseoff(path); - } - - if (print_dirs_colored(cfile, path, ftwbuf, flags, nameoff) != 0) { - return -1; - } - - return print_name_colored(cfile, path + nameoff, ftwbuf, flags); -} - /** Print the name of a file with the appropriate colors. */ static int print_name(CFILE *cfile, const struct BFTW *ftwbuf) { const char *name = ftwbuf->path + ftwbuf->nameoff; diff --git a/tests/bfs/color.out b/tests/bfs/color.out index 5c6b43e..a439814 100644 --- a/tests/bfs/color.out +++ b/tests/bfs/color.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_L.out b/tests/bfs/color_L.out index 6904e39..85923db 100644 --- a/tests/bfs/color_L.out +++ b/tests/bfs/color_L.out @@ -1,8 +1,8 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh rainbow/chardev_link -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/sticky_ow diff --git a/tests/bfs/color_L_ln_target.out b/tests/bfs/color_L_ln_target.out index 50105c3..23fe8d7 100644 --- a/tests/bfs/color_L_ln_target.out +++ b/tests/bfs/color_L_ln_target.out @@ -1,9 +1,9 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/broken rainbow/exec.sh rainbow/chardev_link -rainbow/$'\e[1m' rainbow/socket rainbow/sticky_ow rainbow/sgid diff --git a/tests/bfs/color_L_no_stat.out b/tests/bfs/color_L_no_stat.out index beb538c..72e0319 100644 --- a/tests/bfs/color_L_no_stat.out +++ b/tests/bfs/color_L_no_stat.out @@ -1,10 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/chardev_link -rainbow/$'\e[1m' -rainbow/ow -rainbow/sticky -rainbow/sticky_ow rainbow/socket rainbow/broken rainbow/file.txt @@ -25,3 +22,6 @@ rainbow/upper.GZ rainbow/upper.TAR rainbow/upper.TAR.GZ +rainbow/ow +rainbow/sticky +rainbow/sticky_ow diff --git a/tests/bfs/color_cd0_no.out b/tests/bfs/color_cd0_no.out index 4d710fd..37b3fbc 100644 --- a/tests/bfs/color_cd0_no.out +++ b/tests/bfs/color_cd0_no.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/file.dat diff --git a/tests/bfs/color_escapes.out b/tests/bfs/color_escapes.out index 746a9ea..0bf9fbb 100644 --- a/tests/bfs/color_escapes.out +++ b/tests/bfs/color_escapes.out @@ -1,7 +1,7 @@ +:$'rainbow/\e[1m' :$'rainbow/\e[1m/'$'\e[0m' :rainbow :rainbow/:exec.sh -:rainbow/:$'\e[1m' :rainbow/:socket :rainbow/:broken :rainbow/:chardev_link diff --git a/tests/bfs/color_ext.out b/tests/bfs/color_ext.out index a35ca0b..218100f 100644 --- a/tests/bfs/color_ext.out +++ b/tests/bfs/color_ext.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ext0.out b/tests/bfs/color_ext0.out index 37301cc..d2a7fd5 100644 --- a/tests/bfs/color_ext0.out +++ b/tests/bfs/color_ext0.out @@ -1,8 +1,8 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/file.txt rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ext_case.out b/tests/bfs/color_ext_case.out index 9388343..93dc8f6 100644 --- a/tests/bfs/color_ext_case.out +++ b/tests/bfs/color_ext_case.out @@ -1,3 +1,4 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/lower.gz @@ -7,7 +8,6 @@ rainbow/upper.TAR.GZ rainbow/lower.tar rainbow/upper.TAR -rainbow/$'\e[1m' rainbow/ul.TAR.gz rainbow/lu.tar.GZ rainbow/socket diff --git a/tests/bfs/color_ext_override.out b/tests/bfs/color_ext_override.out index 1e0146c..0acfcbc 100644 --- a/tests/bfs/color_ext_override.out +++ b/tests/bfs/color_ext_override.out @@ -1,3 +1,4 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh @@ -9,7 +10,6 @@ rainbow/ul.TAR.gz rainbow/upper.GZ rainbow/upper.TAR.GZ -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ext_underride.out b/tests/bfs/color_ext_underride.out index 1ed66da..5c98341 100644 --- a/tests/bfs/color_ext_underride.out +++ b/tests/bfs/color_ext_underride.out @@ -1,3 +1,4 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/lower.tar.gz @@ -9,7 +10,6 @@ rainbow/upper.TAR rainbow/lower.gz rainbow/upper.GZ -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_fi0_no.out b/tests/bfs/color_fi0_no.out index 5c6b43e..a439814 100644 --- a/tests/bfs/color_fi0_no.out +++ b/tests/bfs/color_fi0_no.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_fi_no.out b/tests/bfs/color_fi_no.out index e64684d..1c1ad8e 100644 --- a/tests/bfs/color_fi_no.out +++ b/tests/bfs/color_fi_no.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_ln_target.out b/tests/bfs/color_ln_target.out index 50105c3..23fe8d7 100644 --- a/tests/bfs/color_ln_target.out +++ b/tests/bfs/color_ln_target.out @@ -1,9 +1,9 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/broken rainbow/exec.sh rainbow/chardev_link -rainbow/$'\e[1m' rainbow/socket rainbow/sticky_ow rainbow/sgid diff --git a/tests/bfs/color_ls.out b/tests/bfs/color_ls.out index b08d894..b5216c4 100644 --- a/tests/bfs/color_ls.out +++ b/tests/bfs/color_ls.out @@ -1,12 +1,12 @@ -scratch/foo/bar -scratch/foo/bar -/__bfs__/nowhere -/__bfs__/nowhere -foo/bar/baz/qux -foo/bar/baz/qux +scratch/foo/bar +scratch/foo/bar +/__bfs__/nowhere +/__bfs__/nowhere +foo/bar/baz/qux +foo/bar/baz/qux foo/bar/nowhere foo/bar/nowhere -foo/bar/nowhere/nothing -foo/bar/nowhere/nothing +foo/bar/nowhere/nothing +foo/bar/nowhere/nothing foo/bar/baz foo/bar/baz diff --git a/tests/bfs/color_mh.out b/tests/bfs/color_mh.out index 93bfde1..c658082 100644 --- a/tests/bfs/color_mh.out +++ b/tests/bfs/color_mh.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_mh0.out b/tests/bfs/color_mh0.out index 5c6b43e..a439814 100644 --- a/tests/bfs/color_mh0.out +++ b/tests/bfs/color_mh0.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_mi.out b/tests/bfs/color_mi.out index 5c6b43e..a439814 100644 --- a/tests/bfs/color_mi.out +++ b/tests/bfs/color_mi.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_missing_colon.out b/tests/bfs/color_missing_colon.out index a35ca0b..218100f 100644 --- a/tests/bfs/color_missing_colon.out +++ b/tests/bfs/color_missing_colon.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_no.out b/tests/bfs/color_no.out index c61d246..67e1eee 100644 --- a/tests/bfs/color_no.out +++ b/tests/bfs/color_no.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_no_stat.out b/tests/bfs/color_no_stat.out index e57e11a..e3031b2 100644 --- a/tests/bfs/color_no_stat.out +++ b/tests/bfs/color_no_stat.out @@ -1,9 +1,6 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow -rainbow/$'\e[1m' -rainbow/ow -rainbow/sticky -rainbow/sticky_ow rainbow/socket rainbow/broken rainbow/chardev_link @@ -25,3 +22,6 @@ rainbow/upper.GZ rainbow/upper.TAR rainbow/upper.TAR.GZ +rainbow/ow +rainbow/sticky +rainbow/sticky_ow diff --git a/tests/bfs/color_nul.out b/tests/bfs/color_nul.out index 7140e32..8ccd9a7 100644 --- a/tests/bfs/color_nul.out +++ b/tests/bfs/color_nul.out @@ -1,3 +1,4 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/lower.gz @@ -7,7 +8,6 @@ rainbow/upper.GZ rainbow/upper.TAR.GZ rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_or.out b/tests/bfs/color_or.out index 07916da..0bd2570 100644 --- a/tests/bfs/color_or.out +++ b/tests/bfs/color_or.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/chardev_link rainbow/link.txt diff --git a/tests/bfs/color_or0_mi.out b/tests/bfs/color_or0_mi.out index 5c6b43e..a439814 100644 --- a/tests/bfs/color_or0_mi.out +++ b/tests/bfs/color_or0_mi.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_or0_mi0.out b/tests/bfs/color_or0_mi0.out index 5c6b43e..a439814 100644 --- a/tests/bfs/color_or0_mi0.out +++ b/tests/bfs/color_or0_mi0.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_or_mi.out b/tests/bfs/color_or_mi.out index 20bc61d..fb67e58 100644 --- a/tests/bfs/color_or_mi.out +++ b/tests/bfs/color_or_mi.out @@ -1,8 +1,8 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/broken rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/chardev_link rainbow/link.txt diff --git a/tests/bfs/color_or_mi0.out b/tests/bfs/color_or_mi0.out index 20bc61d..fb67e58 100644 --- a/tests/bfs/color_or_mi0.out +++ b/tests/bfs/color_or_mi0.out @@ -1,8 +1,8 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/broken rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/chardev_link rainbow/link.txt diff --git a/tests/bfs/color_rs_lc_rc_ec.out b/tests/bfs/color_rs_lc_rc_ec.out index f78b41f..077ef8d 100644 --- a/tests/bfs/color_rs_lc_rc_ec.out +++ b/tests/bfs/color_rs_lc_rc_ec.out @@ -1,6 +1,6 @@ +LC01;34RC$'rainbow/\e[1m'EC LC01;34RC$'rainbow/\e[1m/'EC$'\e[0m' LC01;34RCrainbow/ECLC01;32RCexec.shEC -LC01;34RCrainbow/ECLC01;34RC$'\e[1m'EC LC01;34RCrainbow/ECLC01;35RCsocketEC LC01;34RCrainbow/ECLC01;36RCbrokenEC LC01;34RCrainbow/ECLC01;36RCchardev_linkEC diff --git a/tests/bfs/color_st0_tw0_ow.out b/tests/bfs/color_st0_tw0_ow.out index d1fec74..a82762b 100644 --- a/tests/bfs/color_st0_tw0_ow.out +++ b/tests/bfs/color_st0_tw0_ow.out @@ -1,8 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' -rainbow/sticky rainbow/socket rainbow/broken rainbow/chardev_link @@ -25,3 +24,4 @@ rainbow/upper.GZ rainbow/upper.TAR rainbow/upper.TAR.GZ +rainbow/sticky diff --git a/tests/bfs/color_st0_tw0_ow0.out b/tests/bfs/color_st0_tw0_ow0.out index cb80cb8..041f1d4 100644 --- a/tests/bfs/color_st0_tw0_ow0.out +++ b/tests/bfs/color_st0_tw0_ow0.out @@ -1,10 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' -rainbow/ow -rainbow/sticky -rainbow/sticky_ow rainbow/socket rainbow/broken rainbow/chardev_link @@ -25,3 +22,6 @@ rainbow/upper.GZ rainbow/upper.TAR rainbow/upper.TAR.GZ +rainbow/ow +rainbow/sticky +rainbow/sticky_ow diff --git a/tests/bfs/color_st0_tw_ow.out b/tests/bfs/color_st0_tw_ow.out index a183932..4dcb2f2 100644 --- a/tests/bfs/color_st0_tw_ow.out +++ b/tests/bfs/color_st0_tw_ow.out @@ -1,8 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' -rainbow/sticky rainbow/socket rainbow/broken rainbow/chardev_link @@ -25,3 +24,4 @@ rainbow/upper.GZ rainbow/upper.TAR rainbow/upper.TAR.GZ +rainbow/sticky diff --git a/tests/bfs/color_st0_tw_ow0.out b/tests/bfs/color_st0_tw_ow0.out index cdc5cdd..954ce9c 100644 --- a/tests/bfs/color_st0_tw_ow0.out +++ b/tests/bfs/color_st0_tw_ow0.out @@ -1,9 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' -rainbow/ow -rainbow/sticky rainbow/socket rainbow/broken rainbow/chardev_link @@ -25,3 +23,5 @@ rainbow/upper.GZ rainbow/upper.TAR rainbow/upper.TAR.GZ +rainbow/ow +rainbow/sticky diff --git a/tests/bfs/color_st_tw0_ow.out b/tests/bfs/color_st_tw0_ow.out index f45c75c..a6e9a16 100644 --- a/tests/bfs/color_st_tw0_ow.out +++ b/tests/bfs/color_st_tw0_ow.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_st_tw0_ow0.out b/tests/bfs/color_st_tw0_ow0.out index d95d12a..756dafb 100644 --- a/tests/bfs/color_st_tw0_ow0.out +++ b/tests/bfs/color_st_tw0_ow0.out @@ -1,8 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' -rainbow/ow rainbow/socket rainbow/broken rainbow/chardev_link @@ -25,3 +24,4 @@ rainbow/upper.GZ rainbow/upper.TAR rainbow/upper.TAR.GZ +rainbow/ow diff --git a/tests/bfs/color_st_tw_ow0.out b/tests/bfs/color_st_tw_ow0.out index f1059dd..6e4a260 100644 --- a/tests/bfs/color_st_tw_ow0.out +++ b/tests/bfs/color_st_tw_ow0.out @@ -1,8 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' -rainbow/ow rainbow/socket rainbow/broken rainbow/chardev_link @@ -25,3 +24,4 @@ rainbow/upper.GZ rainbow/upper.TAR rainbow/upper.TAR.GZ +rainbow/ow diff --git a/tests/bfs/color_su0_sg.out b/tests/bfs/color_su0_sg.out index 31d5108..d13b6b6 100644 --- a/tests/bfs/color_su0_sg.out +++ b/tests/bfs/color_su0_sg.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_su0_sg0.out b/tests/bfs/color_su0_sg0.out index 4804030..77fba58 100644 --- a/tests/bfs/color_su0_sg0.out +++ b/tests/bfs/color_su0_sg0.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/color_su_sg0.out b/tests/bfs/color_su_sg0.out index 3dbc7fe..8fab046 100644 --- a/tests/bfs/color_su_sg0.out +++ b/tests/bfs/color_su_sg0.out @@ -1,7 +1,7 @@ +$'rainbow/\e[1m' $'rainbow/\e[1m/'$'\e[0m' rainbow rainbow/exec.sh -rainbow/$'\e[1m' rainbow/socket rainbow/broken rainbow/chardev_link diff --git a/tests/bfs/printf_color.out b/tests/bfs/printf_color.out index 0468f7d..6641e9a 100644 --- a/tests/bfs/printf_color.out +++ b/tests/bfs/printf_color.out @@ -1,5 +1,7 @@ -. . rainbow ./rainbow rainbow +. $'./rainbow/\e[1m' $'\e[0m' $'./rainbow/\e[1m/'$'\e[0m' $'rainbow/\e[1m/'$'\e[0m' +. . rainbow ./rainbow rainbow . ./rainbow exec.sh ./rainbow/exec.sh rainbow/exec.sh +. ./rainbow $'\e[1m' $'./rainbow/\e[1m' $'rainbow/\e[1m' . ./rainbow socket ./rainbow/socket rainbow/socket . ./rainbow broken ./rainbow/broken rainbow/broken nowhere . ./rainbow chardev_link ./rainbow/chardev_link rainbow/chardev_link /dev/null -- cgit v1.2.3 From b4c3201ccceb9c73dd7751d7f9937b4afe78966f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 16:00:02 -0400 Subject: color: Only highlight the trailing slash on ENOTDIR --- src/color.c | 6 ++++-- tests/bfs/color_ls.out | 4 ++-- tests/bfs/color_ls.sh | 6 +++--- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/color.c b/src/color.c index 7d98978..f7a5d86 100644 --- a/src/color.c +++ b/src/color.c @@ -982,8 +982,10 @@ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, while (ret && at_path[len - 1] == '/') { --len, --ret; } - while (ret && at_path[len - 1] != '/') { - --len, --ret; + if (errno != ENOTDIR) { + while (ret && at_path[len - 1] != '/') { + --len, --ret; + } } dstresize(&at_path, len); diff --git a/tests/bfs/color_ls.out b/tests/bfs/color_ls.out index b5216c4..f69eb9c 100644 --- a/tests/bfs/color_ls.out +++ b/tests/bfs/color_ls.out @@ -2,11 +2,11 @@ scratch/foo/bar /__bfs__/nowhere /__bfs__/nowhere -foo/bar/baz/qux -foo/bar/baz/qux foo/bar/nowhere foo/bar/nowhere foo/bar/nowhere/nothing foo/bar/nowhere/nothing foo/bar/baz foo/bar/baz +foo/bar/baz//qux +foo/bar/baz//qux diff --git a/tests/bfs/color_ls.sh b/tests/bfs/color_ls.sh index 37d088f..f2d3c72 100644 --- a/tests/bfs/color_ls.sh +++ b/tests/bfs/color_ls.sh @@ -3,13 +3,13 @@ clean_scratch ln -s foo/bar/baz scratch/link ln -s foo/bar/nowhere scratch/broken ln -s foo/bar/nowhere/nothing scratch/nested -ln -s foo/bar/baz/qux scratch/notdir +ln -s foo/bar/baz//qux scratch/notdir ln -s scratch/foo/bar scratch/relative mkdir scratch/__bfs__ ln -s /__bfs__/nowhere scratch/absolute -LS_COLORS="or=01;31:" invoke_bfs scratch/{,link,broken,nested,notdir,relative,absolute} -color -type l -ls \ +export LS_COLORS="or=01;31:" +invoke_bfs scratch/{,link,broken,nested,notdir,relative,absolute} -color -type l -ls \ | sed 's/.* -> //' \ | sort >"$OUT" - diff_output -- cgit v1.2.3 From 563a22c512e81a24c2dcc9562ca668b1162c94bd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 16:23:31 -0400 Subject: alloc: Use a different error code for size overflows This should help debuggability, and also squelches a GCC warning. --- src/alloc.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/alloc.c b/src/alloc.c index a6910ce..56d8763 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -28,6 +28,11 @@ void *alloc(size_t align, size_t size) { bfs_assert(has_single_bit(align)); bfs_assert((size & (align - 1)) == 0); + if (size >> (SIZE_WIDTH - 1)) { + errno = EOVERFLOW; + return NULL; + } + if (align <= alignof(max_align_t)) { return malloc(size); } else { @@ -39,6 +44,11 @@ void *zalloc(size_t align, size_t size) { bfs_assert(has_single_bit(align)); bfs_assert((size & (align - 1)) == 0); + if (size >> (SIZE_WIDTH - 1)) { + errno = EOVERFLOW; + return NULL; + } + if (align <= alignof(max_align_t)) { return calloc(1, size); } -- cgit v1.2.3 From 19c96abe0a1ee56cf206fd5e87defb1fd3e0daa5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 20:00:11 -0400 Subject: bfstd: Add an ASCII fast path to wordesc() --- src/bfstd.c | 159 ++++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 100 insertions(+), 59 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 97fa3b3..49c4a70 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -6,6 +6,7 @@ #include "config.h" #include "diag.h" #include "xregex.h" +#include #include #include #include @@ -540,6 +541,25 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * #endif } +/** mbrtowc() wrapper. */ +static int xmbrtowc(wchar_t *wc, size_t *i, const char *str, size_t len, mbstate_t *mb) { + size_t mblen = mbrtowc(wc, str + *i, len - *i, mb); + switch (mblen) { + case -1: + // Invalid byte sequence + *i += 1; + memset(mb, 0, sizeof(*mb)); + return -1; + case -2: + // Incomplete byte sequence + *i += len; + return -1; + default: + *i += mblen; + return 0; + } +} + size_t xstrwidth(const char *str) { size_t len = strlen(str); size_t ret = 0; @@ -547,65 +567,76 @@ size_t xstrwidth(const char *str) { mbstate_t mb; memset(&mb, 0, sizeof(mb)); - while (len > 0) { + for (size_t i = 0; i < len;) { wchar_t wc; - size_t mblen = mbrtowc(&wc, str, len, &mb); - int cwidth; - if (mblen == (size_t)-1) { - // Invalid byte sequence, assume a single-width '?' - mblen = 1; - cwidth = 1; - memset(&mb, 0, sizeof(mb)); - } else if (mblen == (size_t)-2) { - // Incomplete byte sequence, assume a single-width '?' - mblen = len; - cwidth = 1; + if (xmbrtowc(&wc, &i, str, len, &mb) == 0) { + ret += wcwidth(wc); } else { - cwidth = wcwidth(wc); - if (cwidth < 0) { - cwidth = 0; - } + // Assume a single-width '?' + ++ret; } - - str += mblen; - len -= mblen; - ret += cwidth; } return ret; } +/** Check if a character is printable. */ +static bool xisprint(unsigned char c, enum wesc_flags flags) { + if (isprint(c)) { + return true; + } + + // Technically a literal newline is safe inside single quotes, but $'\n' + // is much nicer than ' + // ' + if (!(flags & WESC_SHELL) && isspace(c)) { + return true; + } + + return false; +} + +/** Check if a wide character is printable. */ +static bool xiswprint(wchar_t c, enum wesc_flags flags) { + if (iswprint(c)) { + return true; + } + + if (!(flags & WESC_SHELL) && iswspace(c)) { + return true; + } + + return false; +} + /** Get the length of the longest printable prefix of a string. */ static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) { + // Fast path: avoid multibyte checks + size_t i; + for (i = 0; i < len; ++i) { + unsigned char c = str[i]; + if (!isascii(c)) { + break; + } + if (!xisprint(c, flags)) { + return i; + } + } + mbstate_t mb; memset(&mb, 0, sizeof(mb)); - const char *cur = str; - while (len > 0) { + while (i < len) { wchar_t wc; - size_t mblen = mbrtowc(&wc, cur, len, &mb); - if (mblen == (size_t)-1 || mblen == (size_t)-2) { + if (xmbrtowc(&wc, &i, str, len, &mb) != 0) { break; } - - bool safe = iswprint(wc); - - // Technically a literal newline is safe inside single quotes, - // but $'\n' is much nicer than ' - // ' - if (!(flags & WESC_SHELL) && iswspace(wc)) { - safe = true; - } - - if (!safe) { + if (!xiswprint(wc, flags)) { break; } - - cur += mblen; - len -= mblen; } - return cur - str; + return i; } /** Convert a special char into a well-known escape sequence like "\n". */ @@ -639,32 +670,42 @@ static const char *dollar_esc(char c) { /** $'Quote' a string for the shell. */ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, enum wesc_flags flags) { - static const char *hex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; - dest = xstpecpy(dest, end, "$'"); - while (len > 0) { - size_t plen = printable_len(str, len, flags); - size_t elen = strcspn(str, "'\\"); - size_t min = plen < elen ? plen : elen; - dest = xstpencpy(dest, end, str, min); - str += min; - len -= min; - if (len == 0) { - break; + mbstate_t mb; + memset(&mb, 0, sizeof(mb)); + + for (size_t i = 0; i < len;) { + size_t start = i; + bool safe = false; + + wchar_t wc; + if (xmbrtowc(&wc, &i, str, len, &mb) == 0) { + safe = xiswprint(wc, flags); } - unsigned char byte = *str; - ++str; - --len; + for (size_t j = start; j < i; ++j) { + if (str[j] == '\'' || str[j] == '\\') { + safe = false; + break; + } + } - const char *esc = dollar_esc(byte); - if (esc) { - dest = xstpecpy(dest, end, esc); + if (safe) { + dest = xstpencpy(dest, end, str + start, i - start); } else { - dest = xstpecpy(dest, end, "\\x"); - dest = xstpecpy(dest, end, hex[byte / 0x10]); - dest = xstpecpy(dest, end, hex[byte % 0x10]); + for (size_t j = start; j < i; ++j) { + unsigned char byte = str[j]; + const char *esc = dollar_esc(byte); + if (esc) { + dest = xstpecpy(dest, end, esc); + } else { + static const char *hex[] = {"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"}; + dest = xstpecpy(dest, end, "\\x"); + dest = xstpecpy(dest, end, hex[byte / 0x10]); + dest = xstpecpy(dest, end, hex[byte % 0x10]); + } + } } } -- cgit v1.2.3 From 05dd2b263df6fc0d26a98f6888e0064876c8749b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 21:00:43 -0400 Subject: build: Move some flags around --- Makefile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Makefile b/Makefile index beaf59d..db955b5 100644 --- a/Makefile +++ b/Makefile @@ -55,10 +55,9 @@ LOCAL_CPPFLAGS := \ -D_LARGEFILE64_SOURCE \ -D_FILE_OFFSET_BITS=64 \ -D_TIME_BITS=64 \ - -DBFS_VERSION=\"$(VERSION)\" \ - -pthread + -DBFS_VERSION=\"$(VERSION)\" -LOCAL_CFLAGS := -std=c17 +LOCAL_CFLAGS := -std=c17 -pthread LOCAL_LDFLAGS := LOCAL_LDLIBS := @@ -162,7 +161,8 @@ LOCAL_CFLAGS := $(patsubst -std=c%,-std=gnu%,$(LOCAL_CFLAGS)) endif ifneq ($(filter release,$(MAKECMDGOALS)),) -CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto -DNDEBUG +LOCAL_CPPFLAGS += -DNDEBUG +CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto endif ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) $(EXTRA_CPPFLAGS) -- cgit v1.2.3 From 57a2d70f9b4aa992df1ee510204ae3ce0b32ee26 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 13 Jul 2023 21:19:09 -0400 Subject: Release 3.0 --- docs/CHANGELOG.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 933ddc3..a1b0049 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -4,7 +4,7 @@ 3.0 --- -**Coming soon** +**July 13, 2023** ### New features @@ -26,6 +26,8 @@ - Added a warning/error if `$LS_COLORS` can't be parsed, depending on whether `-color` is requested explicitly +- Filenames with control characters are now escaped when printing with `-color` + - Build flags like `WITH_ONIGURUMA` have been renamed to `USE_ONIGURUMA` [C17]: https://en.cppreference.com/w/c/17 -- cgit v1.2.3 From cf1eee4af7c9cd7e888d4e2ffa6339bce7efcb92 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 17:15:47 -0400 Subject: bftw: Add bfs_dir allocation wrappers --- src/bftw.c | 28 +++++++++++++++++++--------- 1 file changed, 19 insertions(+), 9 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index aec804b..553363e 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -116,6 +116,16 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { bfs_dir_arena(&cache->dirs); } +/** Allocate a directory. */ +static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache) { + return arena_alloc(&cache->dirs); +} + +/** Free a directory. */ +static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) { + arena_free(&cache->dirs, dir); +} + /** Remove a bftw_file from the LRU list. */ static void bftw_lru_remove(struct bftw_cache *cache, struct bftw_file *file) { if (cache->target == file) { @@ -139,7 +149,7 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { if (file->dir) { bfs_assert(file->fd == bfs_dirfd(file->dir)); bfs_closedir(file->dir); - arena_free(&cache->dirs, file->dir); + bftw_freedir(cache, file->dir); file->dir = NULL; } else { xclose(file->fd); @@ -392,13 +402,13 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_f return NULL; } - struct bfs_dir *dir = arena_alloc(&cache->dirs); + struct bfs_dir *dir = bftw_allocdir(cache); if (!dir) { return NULL; } if (bfs_opendir(dir, fd, NULL) != 0) { - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); return NULL; } @@ -895,7 +905,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { case IOQ_CLOSEDIR: ++cache->capacity; dir = ent->closedir.dir; - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); break; case IOQ_OPENDIR: @@ -911,7 +921,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { if (ent->ret == 0) { bftw_file_set_dir(cache, file, dir); } else { - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); } if (!(state->flags & BFTW_SORT)) { @@ -984,7 +994,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } - struct bfs_dir *dir = arena_alloc(&cache->dirs); + struct bfs_dir *dir = bftw_allocdir(cache); if (!dir) { goto unpin; } @@ -1007,7 +1017,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { } free: - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); unpin: if (file->parent) { bftw_cache_unpin(cache, file->parent); @@ -1026,7 +1036,7 @@ static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { struct bftw_cache *cache = &state->cache; int ret = bfs_closedir(dir); - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); ++cache->capacity; return ret; } @@ -1081,7 +1091,7 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { #if BFS_USE_UNWRAPDIR if (reffed || pinned) { bfs_unwrapdir(dir); - arena_free(&cache->dirs, dir); + bftw_freedir(cache, dir); file->dir = NULL; return 0; } -- cgit v1.2.3 From 3cc581c0295465fd5dc2fc818aa92f48dd87788e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 19:03:30 -0400 Subject: bftw: Pass the whole bftw_state to bftw_openat() This required shuffling a lot of code around. Hopefully the new order makes more sense. --- src/bftw.c | 1263 +++++++++++++++++++++++++++++------------------------------- 1 file changed, 602 insertions(+), 661 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 553363e..2262591 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -34,6 +34,78 @@ #include #include +/** Caching bfs_stat(). */ +static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flags flags) { + if (!cache->buf) { + if (cache->error) { + errno = cache->error; + } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { + cache->buf = &cache->storage; + } else { + cache->error = errno; + } + } + + return cache->buf; +} + +const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + struct BFTW *mutbuf = (struct BFTW *)ftwbuf; + const struct bfs_stat *ret; + + if (flags & BFS_STAT_NOFOLLOW) { + ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); + if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) { + // Non-link, so share stat info + mutbuf->stat_cache.buf = ret; + } + } else { + ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); + if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) { + ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); + } + } + + return ret; +} + +const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + if (flags & BFS_STAT_NOFOLLOW) { + return ftwbuf->lstat_cache.buf; + } else if (ftwbuf->stat_cache.buf) { + return ftwbuf->stat_cache.buf; + } else if ((flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(ftwbuf->stat_cache.error)) { + return ftwbuf->lstat_cache.buf; + } else { + return NULL; + } +} + +enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + if (flags & BFS_STAT_NOFOLLOW) { + if (ftwbuf->type == BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { + return ftwbuf->type; + } + } else if (flags & BFS_STAT_TRYFOLLOW) { + if (ftwbuf->type != BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW)) { + return ftwbuf->type; + } + } else { + if (ftwbuf->type != BFS_LNK) { + return ftwbuf->type; + } else if (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW) { + return BFS_ERROR; + } + } + + const struct bfs_stat *statbuf = bftw_stat(ftwbuf, flags); + if (statbuf) { + return bfs_mode_to_type(statbuf->mode); + } else { + return BFS_ERROR; + } +} + /** * A file. */ @@ -277,103 +349,7 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil return file; } -/** - * Open a bftw_file relative to another one. - * - * @param cache - * The cache to hold the file. - * @param file - * The file to open. - * @param base - * The base directory for the relative path (may be NULL). - * @param at_fd - * The base file descriptor, AT_FDCWD if base == NULL. - * @param at_path - * The relative path to the file. - * @return - * The opened file descriptor, or negative on error. - */ -static int bftw_file_openat(struct bftw_cache *cache, struct bftw_file *file, struct bftw_file *base, const char *at_path) { - bfs_assert(file->fd < 0); - - int at_fd = AT_FDCWD; - if (base) { - bftw_cache_pin(cache, base); - at_fd = base->fd; - } - - int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; - int fd = openat(at_fd, at_path, flags); - - if (fd < 0 && errno == EMFILE) { - if (bftw_cache_pop(cache) == 0) { - fd = openat(at_fd, at_path, flags); - } - cache->capacity = 1; - } - - if (base) { - bftw_cache_unpin(cache, base); - } - - if (fd >= 0) { - file->fd = fd; - bftw_cache_add(cache, file); - } - - return file->fd; -} - -/** - * Open a bftw_file. - * - * @param cache - * The cache to hold the file. - * @param file - * The file to open. - * @param path - * The full path to the file. - * @return - * The opened file descriptor, or negative on error. - */ -static int bftw_file_open(struct bftw_cache *cache, struct bftw_file *file, const char *path) { - // Find the nearest open ancestor - struct bftw_file *base = file; - do { - base = base->parent; - } while (base && base->fd < 0); - - const char *at_path = path; - if (base) { - at_path += bftw_child_nameoff(base); - } - - int fd = bftw_file_openat(cache, file, base, at_path); - if (fd >= 0 || errno != ENAMETOOLONG) { - return fd; - } - - // Handle ENAMETOOLONG by manually traversing the path component-by-component - struct bftw_list parents; - SLIST_INIT(&parents); - - struct bftw_file *cur; - for (cur = file; cur != base; cur = cur->parent) { - SLIST_PREPEND(&parents, cur); - } - - while ((cur = SLIST_POP(&parents))) { - if (!cur->parent || cur->parent->fd >= 0) { - bftw_file_openat(cache, cur, cur->parent, cur->name); - } - } - - return file->fd; -} - -/** - * Associate an open directory with a bftw_file. - */ +/** Associate an open directory with a bftw_file. */ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, struct bfs_dir *dir) { bfs_assert(!file->dir); file->dir = dir; @@ -384,38 +360,6 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, } } -/** - * Open a bftw_file as a directory. - * - * @param cache - * The cache to hold the file. - * @param file - * The directory to open. - * @param path - * The full path to the directory. - * @return - * The opened directory, or NULL on error. - */ -static struct bfs_dir *bftw_file_opendir(struct bftw_cache *cache, struct bftw_file *file, const char *path) { - int fd = bftw_file_open(cache, file, path); - if (fd < 0) { - return NULL; - } - - struct bfs_dir *dir = bftw_allocdir(cache); - if (!dir) { - return NULL; - } - - if (bfs_opendir(dir, fd, NULL) != 0) { - bftw_freedir(cache, dir); - return NULL; - } - - bftw_file_set_dir(cache, file, dir); - return dir; -} - /** Free a bftw_file. */ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->refcount == 0); @@ -480,9 +424,7 @@ struct bftw_state { struct BFTW ftwbuf; }; -/** - * Initialize the bftw() state. - */ +/** Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) { state->callback = args->callback; state->ptr = args->ptr; @@ -542,676 +484,677 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg return 0; } -/** Cached bfs_stat(). */ -static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flags flags) { - if (!cache->buf) { - if (cache->error) { - errno = cache->error; - } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { - cache->buf = &cache->storage; - } else { - cache->error = errno; - } +/** Pop a response from the I/O queue. */ +static int bftw_ioq_pop(struct bftw_state *state, bool block) { + struct ioq *ioq = state->ioq; + if (!ioq) { + return -1; } - return cache->buf; -} + struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq); + if (!ent) { + return -1; + } -const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { - struct BFTW *mutbuf = (struct BFTW *)ftwbuf; - const struct bfs_stat *ret; + struct bftw_cache *cache = &state->cache; + struct bftw_file *file; + struct bfs_dir *dir; - if (flags & BFS_STAT_NOFOLLOW) { - ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); - if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) { - // Non-link, so share stat info - mutbuf->stat_cache.buf = ret; - } - } else { - ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); - if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) { - ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); - } - } + enum ioq_op op = ent->op; + switch (op) { + case IOQ_CLOSE: + ++cache->capacity; + break; - return ret; -} + case IOQ_CLOSEDIR: + ++cache->capacity; + dir = ent->closedir.dir; + bftw_freedir(cache, dir); + break; -const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { - if (flags & BFS_STAT_NOFOLLOW) { - return ftwbuf->lstat_cache.buf; - } else if (ftwbuf->stat_cache.buf) { - return ftwbuf->stat_cache.buf; - } else if ((flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(ftwbuf->stat_cache.error)) { - return ftwbuf->lstat_cache.buf; - } else { - return NULL; - } -} + case IOQ_OPENDIR: + file = ent->ptr; + file->ioqueued = false; -enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { - if (flags & BFS_STAT_NOFOLLOW) { - if (ftwbuf->type == BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - return ftwbuf->type; + ++cache->capacity; + if (file->parent) { + bftw_cache_unpin(cache, file->parent); } - } else if (flags & BFS_STAT_TRYFOLLOW) { - if (ftwbuf->type != BFS_LNK || (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW)) { - return ftwbuf->type; + + dir = ent->opendir.dir; + if (ent->ret == 0) { + bftw_file_set_dir(cache, file, dir); + } else { + bftw_freedir(cache, dir); } - } else { - if (ftwbuf->type != BFS_LNK) { - return ftwbuf->type; - } else if (ftwbuf->stat_flags & BFS_STAT_TRYFOLLOW) { - return BFS_ERROR; + + if (!(state->flags & BFTW_SORT)) { + SLIST_PREPEND(&state->dirs, file); } + break; } - const struct bfs_stat *statbuf = bftw_stat(ftwbuf, flags); - if (statbuf) { - return bfs_mode_to_type(statbuf->mode); - } else { - return BFS_ERROR; - } + ioq_free(ioq, ent); + return op; } -/** - * Build the path to the current file. - */ -static int bftw_build_path(struct bftw_state *state, const char *name) { - const struct bftw_file *file = state->file; - - size_t pathlen = file ? file->nameoff + file->namelen : 0; - if (dstresize(&state->path, pathlen) != 0) { - state->error = errno; +/** Try to reserve space in the I/O queue. */ +static int bftw_ioq_reserve(struct bftw_state *state) { + struct ioq *ioq = state->ioq; + if (!ioq) { return -1; } - // Try to find a common ancestor with the existing path - const struct bftw_file *ancestor = state->previous; - while (ancestor && ancestor->depth > file->depth) { - ancestor = ancestor->parent; + if (ioq_capacity(ioq) > 0) { + return 0; } - // Build the path backwards - while (file && file != ancestor) { - if (file->nameoff > 0) { - state->path[file->nameoff - 1] = '/'; - } - memcpy(state->path + file->nameoff, file->name, file->namelen); - - if (ancestor && ancestor->depth == file->depth) { - ancestor = ancestor->parent; - } - file = file->parent; + // With more than two threads, it is faster to wait for an I/O operation + // to complete than it is to do it ourselves + bool block = state->nthreads > 2; + if (bftw_ioq_pop(state, block) < 0) { + return -1; } - state->previous = state->file; + return 0; +} - if (name) { - if (pathlen > 0 && state->path[pathlen - 1] != '/') { - if (dstrapp(&state->path, '/') != 0) { - state->error = errno; - return -1; - } - } - if (dstrcat(&state->path, name) != 0) { - state->error = errno; - return -1; +/** Try to reserve space in the cache. */ +static int bftw_cache_reserve(struct bftw_state *state) { + struct bftw_cache *cache = &state->cache; + if (cache->capacity > 0) { + return 0; + } + + while (bftw_ioq_pop(state, true) >= 0) { + if (cache->capacity > 0) { + return 0; } } - return 0; + return bftw_cache_pop(cache); } -/** Check if a stat() call is needed for this visit. */ -static bool bftw_need_stat(const struct bftw_state *state) { - if (state->flags & BFTW_STAT) { - return true; +/** Open a bftw_file relative to another one. */ +static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, struct bftw_file *base, const char *at_path) { + bfs_assert(file->fd < 0); + + struct bftw_cache *cache = &state->cache; + + int at_fd = AT_FDCWD; + if (base) { + bftw_cache_pin(cache, base); + at_fd = base->fd; } - const struct BFTW *ftwbuf = &state->ftwbuf; - if (ftwbuf->type == BFS_UNKNOWN) { - return true; + int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; + int fd = openat(at_fd, at_path, flags); + + if (fd < 0 && errno == EMFILE) { + if (bftw_cache_pop(cache) == 0) { + fd = openat(at_fd, at_path, flags); + } + cache->capacity = 1; } - if (ftwbuf->type == BFS_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - return true; + if (base) { + bftw_cache_unpin(cache, base); } - if (ftwbuf->type == BFS_DIR) { - if (state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) { - return true; - } -#if __linux__ - } else if (state->mtab) { - // Linux fills in d_type from the underlying inode, even when - // the directory entry is a bind mount point. In that case, we - // need to stat() to get the correct type. We don't need to - // check for directories because they can only be mounted over - // by other directories. - if (bfs_might_be_mount(state->mtab, ftwbuf->path)) { - return true; - } -#endif + if (fd >= 0) { + file->fd = fd; + bftw_cache_add(cache, file); } - return false; + return file->fd; } -/** Initialize bftw_stat cache. */ -static void bftw_stat_init(struct bftw_stat *cache) { - cache->buf = NULL; - cache->error = 0; -} +/** Open a bftw_file. */ +static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, const char *path) { + // Find the nearest open ancestor + struct bftw_file *base = file; + do { + base = base->parent; + } while (base && base->fd < 0); -/** - * Open a file if necessary. - * - * @param file - * The file to open. - * @param path - * The path to that file or one of its descendants. - * @return - * The opened file descriptor, or -1 on error. - */ -static int bftw_ensure_open(struct bftw_cache *cache, struct bftw_file *file, const char *path) { - int ret = file->fd; + const char *at_path = path; + if (base) { + at_path += bftw_child_nameoff(base); + } - if (ret < 0) { - char *copy = strndup(path, file->nameoff + file->namelen); - if (!copy) { - return -1; - } + int fd = bftw_file_openat(state, file, base, at_path); + if (fd >= 0 || errno != ENAMETOOLONG) { + return fd; + } - ret = bftw_file_open(cache, file, copy); - free(copy); + // Handle ENAMETOOLONG by manually traversing the path component-by-component + struct bftw_list parents; + SLIST_INIT(&parents); + + struct bftw_file *cur; + for (cur = file; cur != base; cur = cur->parent) { + SLIST_PREPEND(&parents, cur); } - return ret; + while ((cur = SLIST_POP(&parents))) { + if (!cur->parent || cur->parent->fd >= 0) { + bftw_file_openat(state, cur, cur->parent, cur->name); + } + } + + return file->fd; } -/** - * Initialize the buffers with data about the current path. - */ -static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { - struct bftw_file *file = state->file; - const struct bfs_dirent *de = state->de; +/** Push a directory onto the queue. */ +static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->type == BFS_DIR); - struct BFTW *ftwbuf = &state->ftwbuf; - ftwbuf->path = state->path; - ftwbuf->root = file ? file->root->name : ftwbuf->path; - ftwbuf->depth = 0; - ftwbuf->visit = visit; - ftwbuf->type = BFS_UNKNOWN; - ftwbuf->error = state->direrror; - ftwbuf->at_fd = AT_FDCWD; - ftwbuf->at_path = ftwbuf->path; - ftwbuf->stat_flags = BFS_STAT_NOFOLLOW; - bftw_stat_init(&ftwbuf->lstat_cache); - bftw_stat_init(&ftwbuf->stat_cache); + struct bftw_cache *cache = &state->cache; - struct bftw_file *parent = NULL; - if (de) { - parent = file; - ftwbuf->depth = file->depth + 1; - ftwbuf->type = de->type; - ftwbuf->nameoff = bftw_child_nameoff(file); - } else if (file) { - parent = file->parent; - ftwbuf->depth = file->depth; - ftwbuf->type = file->type; - ftwbuf->nameoff = file->nameoff; + if (bftw_ioq_reserve(state) != 0) { + goto append; } - if (parent) { - // Try to ensure the immediate parent is open, to avoid ENAMETOOLONG - if (bftw_ensure_open(&state->cache, parent, state->path) >= 0) { - ftwbuf->at_fd = parent->fd; - ftwbuf->at_path += ftwbuf->nameoff; - } else { - ftwbuf->error = errno; + int dfd = AT_FDCWD; + if (file->parent) { + dfd = file->parent->fd; + if (dfd < 0) { + goto append; } + bftw_cache_pin(cache, file->parent); } - if (ftwbuf->depth == 0) { - // Compute the name offset for root paths like "foo/bar" - ftwbuf->nameoff = xbaseoff(ftwbuf->path); + if (bftw_cache_reserve(state) != 0) { + goto unpin; } - if (ftwbuf->error != 0) { - ftwbuf->type = BFS_ERROR; - return; + struct bfs_dir *dir = bftw_allocdir(cache); + if (!dir) { + goto unpin; } - int follow_flags = BFTW_FOLLOW_ALL; - if (ftwbuf->depth == 0) { - follow_flags |= BFTW_FOLLOW_ROOTS; - } - bool follow = state->flags & follow_flags; - if (follow) { - ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW; + if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { + goto free; } - const struct bfs_stat *statbuf = NULL; - if (bftw_need_stat(state)) { - statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); - if (statbuf) { - ftwbuf->type = bfs_mode_to_type(statbuf->mode); - } else { - ftwbuf->type = BFS_ERROR; - ftwbuf->error = errno; - return; - } + file->ioqueued = true; + --cache->capacity; + + if (state->flags & BFTW_SORT) { + // When sorting, dirs are always kept in order in state->dirs, + // and we wait for the ioq to open them before popping + goto append; + } else { + // When not sorting, dirs are not added to state->dirs until + // popped from the ioq + return; } - if (ftwbuf->type == BFS_DIR && (state->flags & BFTW_DETECT_CYCLES)) { - for (const struct bftw_file *ancestor = parent; ancestor; ancestor = ancestor->parent) { - if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) { - ftwbuf->type = BFS_ERROR; - ftwbuf->error = ELOOP; - return; - } - } +free: + bftw_freedir(cache, dir); +unpin: + if (file->parent) { + bftw_cache_unpin(cache, file->parent); } +append: + SLIST_APPEND(&state->dirs, file); } -/** Check if the current file is a mount point. */ -static bool bftw_is_mount(struct bftw_state *state, const char *name) { - const struct bftw_file *file = state->file; - if (!file) { - return false; +/** Close a directory, asynchronously if possible. */ +static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { + if (bftw_ioq_reserve(state) == 0) { + if (ioq_closedir(state->ioq, dir, NULL) == 0) { + return 0; + } } - const struct bftw_file *parent = name ? file : file->parent; - if (!parent) { - return false; + struct bftw_cache *cache = &state->cache; + int ret = bfs_closedir(dir); + bftw_freedir(cache, dir); + ++cache->capacity; + return ret; +} + +/** Close a file descriptor, asynchronously if possible. */ +static int bftw_ioq_close(struct bftw_state *state, int fd) { + if (bftw_ioq_reserve(state) == 0) { + if (ioq_close(state->ioq, fd, NULL) == 0) { + return 0; + } } - const struct BFTW *ftwbuf = &state->ftwbuf; - const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); - return statbuf && statbuf->dev != parent->dev; + struct bftw_cache *cache = &state->cache; + int ret = xclose(fd); + ++cache->capacity; + return ret; } -/** - * Invoke the callback. - */ -static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) { - if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { - return BFTW_PRUNE; - } +/** Close a file, asynchronously if possible. */ +static int bftw_close(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->fd >= 0); + bfs_assert(file->pincount == 0); - if (bftw_build_path(state, name) != 0) { - return BFTW_STOP; - } + struct bfs_dir *dir = file->dir; + int fd = file->fd; - const struct BFTW *ftwbuf = &state->ftwbuf; - bftw_init_ftwbuf(state, visit); + bftw_lru_remove(&state->cache, file); + file->dir = NULL; + file->fd = -1; - // Never give the callback BFS_ERROR unless BFTW_RECOVER is specified - if (ftwbuf->type == BFS_ERROR && !(state->flags & BFTW_RECOVER)) { - state->error = ftwbuf->error; - return BFTW_STOP; + if (dir) { + return bftw_ioq_closedir(state, dir); + } else { + return bftw_ioq_close(state, fd); } +} - if ((state->flags & BFTW_SKIP_MOUNTS) && bftw_is_mount(state, name)) { - return BFTW_PRUNE; +/** Free an open directory. */ +static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { + struct bfs_dir *dir = file->dir; + if (!dir) { + return 0; } - enum bftw_action ret = state->callback(ftwbuf, state->ptr); - switch (ret) { - case BFTW_CONTINUE: - if (visit != BFTW_PRE) { - return BFTW_PRUNE; - } - if (ftwbuf->type != BFS_DIR) { - return BFTW_PRUNE; - } - if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { - return BFTW_PRUNE; - } - fallthru; - case BFTW_PRUNE: - case BFTW_STOP: - return ret; + struct bftw_cache *cache = &state->cache; - default: - state->error = EINVAL; - return BFTW_STOP; + // Try to keep an open fd if any children exist + bool reffed = file->refcount > 1; + // Keep the fd the same if it's pinned + bool pinned = file->pincount > 0; + +#if BFS_USE_UNWRAPDIR + if (reffed || pinned) { + bfs_unwrapdir(dir); + bftw_freedir(cache, dir); + file->dir = NULL; + return 0; } -} +#else + if (pinned) { + return -1; + } +#endif -/** Pop a response from the I/O queue. */ -static int bftw_ioq_pop(struct bftw_state *state, bool block) { - struct ioq *ioq = state->ioq; - if (!ioq) { + if (!reffed) { + return bftw_close(state, file); + } + + if (bftw_cache_reserve(state) != 0) { return -1; } - struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq); - if (!ent) { + int fd = dup_cloexec(file->fd); + if (fd < 0) { return -1; } + --cache->capacity; - struct bftw_cache *cache = &state->cache; - struct bftw_file *file; - struct bfs_dir *dir; + file->dir = NULL; + file->fd = fd; + return bftw_ioq_closedir(state, dir); +} - enum ioq_op op = ent->op; - switch (op) { - case IOQ_CLOSE: - ++cache->capacity; - break; +/** Pop a directory to read from the queue. */ +static bool bftw_pop_dir(struct bftw_state *state) { + bfs_assert(!state->file); - case IOQ_CLOSEDIR: - ++cache->capacity; - dir = ent->closedir.dir; - bftw_freedir(cache, dir); - break; + struct bftw_file *dir = state->dirs.head; + bool have_files = state->files.head; - case IOQ_OPENDIR: - file = ent->ptr; - file->ioqueued = false; + if (state->flags & BFTW_SORT) { + // Keep strict breadth-first order when sorting + if (state->strategy != BFTW_DFS && have_files) { + return false; + } + } else { + while (!dir || !dir->dir) { + // Block if we have no other files/dirs to visit, or no room in the cache + bool have_room = state->cache.capacity > 0; + bool block = !(dir || have_files) || !have_room; - ++cache->capacity; - if (file->parent) { - bftw_cache_unpin(cache, file->parent); + if (bftw_ioq_pop(state, block) < 0) { + break; + } + dir = state->dirs.head; } + } - dir = ent->opendir.dir; - if (ent->ret == 0) { - bftw_file_set_dir(cache, file, dir); - } else { - bftw_freedir(cache, dir); - } + if (!dir) { + return false; + } - if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->dirs, file); - } - break; + SLIST_POP(&state->dirs); + state->file = dir; + + while (dir->ioqueued) { + bftw_ioq_pop(state, true); } - ioq_free(ioq, ent); - return op; + return true; } -/** Try to reserve space in the I/O queue. */ -static int bftw_ioq_reserve(struct bftw_state *state) { - struct ioq *ioq = state->ioq; - if (!ioq) { +/** Pop a file to visit from the queue. */ +static bool bftw_pop_file(struct bftw_state *state) { + bfs_assert(!state->file); + return (state->file = SLIST_POP(&state->files)); +} + +/** Build the path to the current file. */ +static int bftw_build_path(struct bftw_state *state, const char *name) { + const struct bftw_file *file = state->file; + + size_t pathlen = file ? file->nameoff + file->namelen : 0; + if (dstresize(&state->path, pathlen) != 0) { + state->error = errno; return -1; } - if (ioq_capacity(ioq) > 0) { - return 0; + // Try to find a common ancestor with the existing path + const struct bftw_file *ancestor = state->previous; + while (ancestor && ancestor->depth > file->depth) { + ancestor = ancestor->parent; } - // With more than two threads, it is faster to wait for an I/O operation - // to complete than it is to do it ourselves - bool block = state->nthreads > 2; - if (bftw_ioq_pop(state, block) < 0) { - return -1; + // Build the path backwards + while (file && file != ancestor) { + if (file->nameoff > 0) { + state->path[file->nameoff - 1] = '/'; + } + memcpy(state->path + file->nameoff, file->name, file->namelen); + + if (ancestor && ancestor->depth == file->depth) { + ancestor = ancestor->parent; + } + file = file->parent; + } + + state->previous = state->file; + + if (name) { + if (pathlen > 0 && state->path[pathlen - 1] != '/') { + if (dstrapp(&state->path, '/') != 0) { + state->error = errno; + return -1; + } + } + if (dstrcat(&state->path, name) != 0) { + state->error = errno; + return -1; + } } return 0; } -/** Try to reserve space in the cache. */ -static int bftw_cache_reserve(struct bftw_state *state) { +/** Open a bftw_file as a directory. */ +static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_file *file, const char *path) { + int fd = bftw_file_open(state, file, path); + if (fd < 0) { + return NULL; + } + struct bftw_cache *cache = &state->cache; - if (cache->capacity > 0) { - return 0; + struct bfs_dir *dir = bftw_allocdir(cache); + if (!dir) { + return NULL; } - while (bftw_ioq_pop(state, false) >= 0) { - if (cache->capacity > 0) { - return 0; - } + if (bfs_opendir(dir, fd, NULL) != 0) { + bftw_freedir(cache, dir); + return NULL; } - return bftw_cache_pop(cache); + bftw_file_set_dir(cache, file, dir); + return dir; } -/** Push a directory onto the queue. */ -static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { - bfs_assert(file->type == BFS_DIR); - - struct bftw_cache *cache = &state->cache; +/** Open the current directory. */ +static int bftw_opendir(struct bftw_state *state) { + bfs_assert(!state->dir); + bfs_assert(!state->de); - if (bftw_ioq_reserve(state) != 0) { - goto append; - } + state->direrror = 0; - int dfd = AT_FDCWD; - if (file->parent) { - dfd = file->parent->fd; - if (dfd < 0) { - goto append; + struct bftw_file *file = state->file; + if (file->dir) { + state->dir = file->dir; + } else { + if (bftw_build_path(state, NULL) != 0) { + return -1; } - bftw_cache_pin(cache, file->parent); + state->dir = bftw_file_opendir(state, file, state->path); } - if (bftw_cache_reserve(state) != 0) { - goto unpin; + if (state->dir) { + bftw_cache_pin(&state->cache, file); + } else { + state->direrror = errno; } - struct bfs_dir *dir = bftw_allocdir(cache); - if (!dir) { - goto unpin; - } + return 0; +} - if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { - goto free; +/** Read an entry from the current directory. */ +static int bftw_readdir(struct bftw_state *state) { + if (!state->dir) { + return -1; } - file->ioqueued = true; - --cache->capacity; - - if (state->flags & BFTW_SORT) { - // When sorting, dirs are always kept in order in state->dirs, - // and we wait for the ioq to open them before popping - goto append; + int ret = bfs_readdir(state->dir, &state->de_storage); + if (ret > 0) { + state->de = &state->de_storage; + } else if (ret == 0) { + state->de = NULL; } else { - // When not sorting, dirs are not added to state->dirs until - // popped from the ioq - return; + state->de = NULL; + state->direrror = errno; } -free: - bftw_freedir(cache, dir); -unpin: - if (file->parent) { - bftw_cache_unpin(cache, file->parent); - } -append: - SLIST_APPEND(&state->dirs, file); + return ret; } -/** Close a directory, asynchronously if possible. */ -static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { - if (bftw_ioq_reserve(state) == 0) { - if (ioq_closedir(state->ioq, dir, NULL) == 0) { - return 0; - } +/** Check if a stat() call is needed for this visit. */ +static bool bftw_need_stat(const struct bftw_state *state) { + if (state->flags & BFTW_STAT) { + return true; } - struct bftw_cache *cache = &state->cache; - int ret = bfs_closedir(dir); - bftw_freedir(cache, dir); - ++cache->capacity; - return ret; -} + const struct BFTW *ftwbuf = &state->ftwbuf; + if (ftwbuf->type == BFS_UNKNOWN) { + return true; + } -/** Close a file descriptor, asynchronously if possible. */ -static int bftw_ioq_close(struct bftw_state *state, int fd) { - if (bftw_ioq_reserve(state) == 0) { - if (ioq_close(state->ioq, fd, NULL) == 0) { - return 0; + if (ftwbuf->type == BFS_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { + return true; + } + + if (ftwbuf->type == BFS_DIR) { + if (state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) { + return true; + } +#if __linux__ + } else if (state->mtab) { + // Linux fills in d_type from the underlying inode, even when + // the directory entry is a bind mount point. In that case, we + // need to stat() to get the correct type. We don't need to + // check for directories because they can only be mounted over + // by other directories. + if (bfs_might_be_mount(state->mtab, ftwbuf->path)) { + return true; } +#endif } - struct bftw_cache *cache = &state->cache; - int ret = xclose(fd); - ++cache->capacity; - return ret; + return false; } -/** Close a file, asynchronously if possible. */ -static int bftw_close(struct bftw_state *state, struct bftw_file *file) { - bfs_assert(file->fd >= 0); - bfs_assert(file->pincount == 0); +/** Initialize bftw_stat cache. */ +static void bftw_stat_init(struct bftw_stat *cache) { + cache->buf = NULL; + cache->error = 0; +} - struct bfs_dir *dir = file->dir; - int fd = file->fd; +/** Open a file if necessary. */ +static int bftw_ensure_open(struct bftw_state *state, struct bftw_file *file, const char *path) { + int ret = file->fd; - bftw_lru_remove(&state->cache, file); - file->dir = NULL; - file->fd = -1; + if (ret < 0) { + char *copy = strndup(path, file->nameoff + file->namelen); + if (!copy) { + return -1; + } - if (dir) { - return bftw_ioq_closedir(state, dir); - } else { - return bftw_ioq_close(state, fd); + ret = bftw_file_open(state, file, copy); + free(copy); } + + return ret; } -/** Free an open directory. */ -static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { - struct bfs_dir *dir = file->dir; - if (!dir) { - return 0; +/** Initialize the buffers with data about the current path. */ +static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { + struct bftw_file *file = state->file; + const struct bfs_dirent *de = state->de; + + struct BFTW *ftwbuf = &state->ftwbuf; + ftwbuf->path = state->path; + ftwbuf->root = file ? file->root->name : ftwbuf->path; + ftwbuf->depth = 0; + ftwbuf->visit = visit; + ftwbuf->type = BFS_UNKNOWN; + ftwbuf->error = state->direrror; + ftwbuf->at_fd = AT_FDCWD; + ftwbuf->at_path = ftwbuf->path; + ftwbuf->stat_flags = BFS_STAT_NOFOLLOW; + bftw_stat_init(&ftwbuf->lstat_cache); + bftw_stat_init(&ftwbuf->stat_cache); + + struct bftw_file *parent = NULL; + if (de) { + parent = file; + ftwbuf->depth = file->depth + 1; + ftwbuf->type = de->type; + ftwbuf->nameoff = bftw_child_nameoff(file); + } else if (file) { + parent = file->parent; + ftwbuf->depth = file->depth; + ftwbuf->type = file->type; + ftwbuf->nameoff = file->nameoff; } - struct bftw_cache *cache = &state->cache; - - // Try to keep an open fd if any children exist - bool reffed = file->refcount > 1; - // Keep the fd the same if it's pinned - bool pinned = file->pincount > 0; - -#if BFS_USE_UNWRAPDIR - if (reffed || pinned) { - bfs_unwrapdir(dir); - bftw_freedir(cache, dir); - file->dir = NULL; - return 0; - } -#else - if (pinned) { - return -1; + if (parent) { + // Try to ensure the immediate parent is open, to avoid ENAMETOOLONG + if (bftw_ensure_open(state, parent, state->path) >= 0) { + ftwbuf->at_fd = parent->fd; + ftwbuf->at_path += ftwbuf->nameoff; + } else { + ftwbuf->error = errno; + } } -#endif - if (!reffed) { - return bftw_close(state, file); + if (ftwbuf->depth == 0) { + // Compute the name offset for root paths like "foo/bar" + ftwbuf->nameoff = xbaseoff(ftwbuf->path); } - if (bftw_cache_reserve(state) != 0) { - return -1; + if (ftwbuf->error != 0) { + ftwbuf->type = BFS_ERROR; + return; } - int fd = dup_cloexec(file->fd); - if (fd < 0) { - return -1; + int follow_flags = BFTW_FOLLOW_ALL; + if (ftwbuf->depth == 0) { + follow_flags |= BFTW_FOLLOW_ROOTS; + } + bool follow = state->flags & follow_flags; + if (follow) { + ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW; } - --cache->capacity; - - file->dir = NULL; - file->fd = fd; - return bftw_ioq_closedir(state, dir); -} - -/** Pop a directory to read from the queue. */ -static bool bftw_pop_dir(struct bftw_state *state) { - bfs_assert(!state->file); - - struct bftw_file *dir = state->dirs.head; - bool have_files = state->files.head; - if (state->flags & BFTW_SORT) { - // Keep strict breadth-first order when sorting - if (state->strategy != BFTW_DFS && have_files) { - return false; + const struct bfs_stat *statbuf = NULL; + if (bftw_need_stat(state)) { + statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); + if (statbuf) { + ftwbuf->type = bfs_mode_to_type(statbuf->mode); + } else { + ftwbuf->type = BFS_ERROR; + ftwbuf->error = errno; + return; } - } else { - while (!dir || !dir->dir) { - // Block if we have no other files/dirs to visit, or no room in the cache - bool have_room = state->cache.capacity > 0; - bool block = !(dir || have_files) || !have_room; + } - if (bftw_ioq_pop(state, block) < 0) { - break; + if (ftwbuf->type == BFS_DIR && (state->flags & BFTW_DETECT_CYCLES)) { + for (const struct bftw_file *ancestor = parent; ancestor; ancestor = ancestor->parent) { + if (ancestor->dev == statbuf->dev && ancestor->ino == statbuf->ino) { + ftwbuf->type = BFS_ERROR; + ftwbuf->error = ELOOP; + return; } - dir = state->dirs.head; } } +} - if (!dir) { +/** Check if the current file is a mount point. */ +static bool bftw_is_mount(struct bftw_state *state, const char *name) { + const struct bftw_file *file = state->file; + if (!file) { return false; } - SLIST_POP(&state->dirs); - state->file = dir; - - while (dir->ioqueued) { - bftw_ioq_pop(state, true); + const struct bftw_file *parent = name ? file : file->parent; + if (!parent) { + return false; } - return true; -} - -/** Pop a file to visit from the queue. */ -static bool bftw_pop_file(struct bftw_state *state) { - bfs_assert(!state->file); - return (state->file = SLIST_POP(&state->files)); + const struct BFTW *ftwbuf = &state->ftwbuf; + const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); + return statbuf && statbuf->dev != parent->dev; } -/** - * Open the current directory. - */ -static int bftw_opendir(struct bftw_state *state) { - bfs_assert(!state->dir); - bfs_assert(!state->de); - - state->direrror = 0; - - struct bftw_file *file = state->file; - if (file->dir) { - state->dir = file->dir; - } else { - if (bftw_build_path(state, NULL) != 0) { - return -1; - } - state->dir = bftw_file_opendir(&state->cache, file, state->path); +/** Invoke the callback. */ +static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) { + if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { + return BFTW_PRUNE; } - if (state->dir) { - bftw_cache_pin(&state->cache, file); - } else { - state->direrror = errno; + if (bftw_build_path(state, name) != 0) { + return BFTW_STOP; } - return 0; -} + const struct BFTW *ftwbuf = &state->ftwbuf; + bftw_init_ftwbuf(state, visit); -/** - * Read an entry from the current directory. - */ -static int bftw_readdir(struct bftw_state *state) { - if (!state->dir) { - return -1; + // Never give the callback BFS_ERROR unless BFTW_RECOVER is specified + if (ftwbuf->type == BFS_ERROR && !(state->flags & BFTW_RECOVER)) { + state->error = ftwbuf->error; + return BFTW_STOP; } - int ret = bfs_readdir(state->dir, &state->de_storage); - if (ret > 0) { - state->de = &state->de_storage; - } else if (ret == 0) { - state->de = NULL; - } else { - state->de = NULL; - state->direrror = errno; + if ((state->flags & BFTW_SKIP_MOUNTS) && bftw_is_mount(state, name)) { + return BFTW_PRUNE; } - return ret; + enum bftw_action ret = state->callback(ftwbuf, state->ptr); + switch (ret) { + case BFTW_CONTINUE: + if (visit != BFTW_PRE) { + return BFTW_PRUNE; + } + if (ftwbuf->type != BFS_DIR) { + return BFTW_PRUNE; + } + if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { + return BFTW_PRUNE; + } + fallthru; + case BFTW_PRUNE: + case BFTW_STOP: + return ret; + + default: + state->error = EINVAL; + return BFTW_STOP; + } } /** @@ -1230,9 +1173,7 @@ enum bftw_gc_flags { BFTW_VISIT_ALL = BFTW_VISIT_ERROR | BFTW_VISIT_FILE | BFTW_VISIT_PARENTS, }; -/** - * Garbage collect the current file and its parents. - */ +/** Garbage collect the current file and its parents. */ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; @@ -1286,35 +1227,6 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { return ret; } -/** - * Dispose of the bftw() state. - * - * @return - * The bftw() return value. - */ -static int bftw_state_destroy(struct bftw_state *state) { - dstrfree(state->path); - - struct ioq *ioq = state->ioq; - if (ioq) { - ioq_cancel(ioq); - while (bftw_ioq_pop(state, true) >= 0); - state->ioq = NULL; - } - - SLIST_EXTEND(&state->files, &state->batch); - do { - bftw_gc(state, BFTW_VISIT_NONE); - } while (bftw_pop_dir(state) || bftw_pop_file(state)); - - ioq_destroy(ioq); - - bftw_cache_destroy(&state->cache); - - errno = state->error; - return state->error ? -1 : 0; -} - /** Sort a bftw_list by filename. */ static void bftw_list_sort(struct bftw_list *list) { if (!list->head || !list->head->next) { @@ -1436,6 +1348,35 @@ static int bftw_visit(struct bftw_state *state, const char *name) { } } +/** + * Dispose of the bftw() state. + * + * @return + * The bftw() return value. + */ +static int bftw_state_destroy(struct bftw_state *state) { + dstrfree(state->path); + + struct ioq *ioq = state->ioq; + if (ioq) { + ioq_cancel(ioq); + while (bftw_ioq_pop(state, true) >= 0); + state->ioq = NULL; + } + + SLIST_EXTEND(&state->files, &state->batch); + do { + bftw_gc(state, BFTW_VISIT_NONE); + } while (bftw_pop_dir(state) || bftw_pop_file(state)); + + ioq_destroy(ioq); + + bftw_cache_destroy(&state->cache); + + errno = state->error; + return state->error ? -1 : 0; +} + /** * bftw() implementation for simple breadth-/depth-first search. */ -- cgit v1.2.3 From 196c36bb13de94c977a3e360dc5fa529efe2c5ca Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 19:05:23 -0400 Subject: bftw: Reserve space in the cache before opening files This fixes a storm of EMFILE retries observed with -j1 on a very large directory tree. Fixes: 7888fbababd22190e9f919fc272957426a27969e --- src/bftw.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 2262591..9d85966 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -572,7 +572,13 @@ static int bftw_cache_reserve(struct bftw_state *state) { } } - return bftw_cache_pop(cache); + if (bftw_cache_pop(cache) != 0) { + errno = EMFILE; + return -1; + } + + bfs_assert(cache->capacity > 0); + return 0; } /** Open a bftw_file relative to another one. */ @@ -587,8 +593,13 @@ static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, st at_fd = base->fd; } + int fd = -1; + if (bftw_cache_reserve(state) != 0) { + goto unpin; + } + int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; - int fd = openat(at_fd, at_path, flags); + fd = openat(at_fd, at_path, flags); if (fd < 0 && errno == EMFILE) { if (bftw_cache_pop(cache) == 0) { @@ -597,6 +608,7 @@ static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, st cache->capacity = 1; } +unpin: if (base) { bftw_cache_unpin(cache, base); } @@ -606,7 +618,7 @@ static int bftw_file_openat(struct bftw_state *state, struct bftw_file *file, st bftw_cache_add(cache, file); } - return file->fd; + return fd; } /** Open a bftw_file. */ -- cgit v1.2.3 From d24941bb85f8ccc41a9894871e7cca7a02de066c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 19:58:39 -0400 Subject: bftw: Check that file->fd == bfs_dirfd(file->dir) earlier This has the potential to fail on at least one known platform: macports with the legacysupport implementation of fdopendir(). Link: https://github.com/macports/macports-ports/pull/19047#issuecomment-1636059809 --- src/bftw.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 9d85966..f8d0ac5 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -219,7 +219,6 @@ static void bftw_file_close(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->pincount == 0); if (file->dir) { - bfs_assert(file->fd == bfs_dirfd(file->dir)); bfs_closedir(file->dir); bftw_freedir(cache, file->dir); file->dir = NULL; @@ -354,7 +353,9 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, bfs_assert(!file->dir); file->dir = dir; - if (file->fd < 0) { + if (file->fd >= 0) { + bfs_assert(file->fd == bfs_dirfd(dir)); + } else { file->fd = bfs_dirfd(dir); bftw_cache_add(cache, file); } -- cgit v1.2.3 From d3f4fd1f5fc7e9502d5fb0087d9ef6c1566655c2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 18:40:32 -0400 Subject: bftw: Use separate queues for open and closed directories --- src/bftw.c | 204 ++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 115 insertions(+), 89 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index f8d0ac5..a412c26 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -114,10 +114,15 @@ struct bftw_file { struct bftw_file *parent; /** The root under which this file was found. */ struct bftw_file *root; - /** The next file in the queue, if any. */ - struct bftw_file *next; - /** LRU list links. */ + /** The next directory to open. */ + struct { struct bftw_file *next; } to_open; + /** The next directory to read. */ + struct { struct bftw_file *next; } to_read; + /** The next file to visit. */ + struct { struct bftw_file *next; } to_visit; + + /** LRU list node. */ struct { struct bftw_file *prev; struct bftw_file *next; @@ -329,7 +334,9 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->nameoff = 0; } - file->next = NULL; + file->to_open.next = NULL; + file->to_read.next = NULL; + file->to_visit.next = NULL; file->lru.prev = file->lru.next = NULL; file->refcount = 1; @@ -398,10 +405,13 @@ struct bftw_state { /** The number of I/O threads. */ size_t nthreads; + /** The queue of directories to open. */ + struct bftw_list to_open; /** The queue of directories to read. */ - struct bftw_list dirs; + struct bftw_list to_read; + /** The queue of files to visit. */ - struct bftw_list files; + struct bftw_list to_visit; /** A batch of files to enqueue. */ struct bftw_list batch; @@ -471,8 +481,10 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } state->nthreads = nthreads; - SLIST_INIT(&state->dirs); - SLIST_INIT(&state->files); + SLIST_INIT(&state->to_open); + SLIST_INIT(&state->to_read); + + SLIST_INIT(&state->to_visit); SLIST_INIT(&state->batch); state->file = NULL; @@ -530,7 +542,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { } if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->dirs, file); + SLIST_PREPEND(&state->to_read, file, to_read); } break; } @@ -646,10 +658,10 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons struct bftw_file *cur; for (cur = file; cur != base; cur = cur->parent) { - SLIST_PREPEND(&parents, cur); + SLIST_PREPEND(&parents, cur, to_open); } - while ((cur = SLIST_POP(&parents))) { + while ((cur = SLIST_POP(&parents, to_open))) { if (!cur->parent || cur->parent->fd >= 0) { bftw_file_openat(state, cur, cur->parent, cur->name); } @@ -658,61 +670,6 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons return file->fd; } -/** Push a directory onto the queue. */ -static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { - bfs_assert(file->type == BFS_DIR); - - struct bftw_cache *cache = &state->cache; - - if (bftw_ioq_reserve(state) != 0) { - goto append; - } - - int dfd = AT_FDCWD; - if (file->parent) { - dfd = file->parent->fd; - if (dfd < 0) { - goto append; - } - bftw_cache_pin(cache, file->parent); - } - - if (bftw_cache_reserve(state) != 0) { - goto unpin; - } - - struct bfs_dir *dir = bftw_allocdir(cache); - if (!dir) { - goto unpin; - } - - if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { - goto free; - } - - file->ioqueued = true; - --cache->capacity; - - if (state->flags & BFTW_SORT) { - // When sorting, dirs are always kept in order in state->dirs, - // and we wait for the ioq to open them before popping - goto append; - } else { - // When not sorting, dirs are not added to state->dirs until - // popped from the ioq - return; - } - -free: - bftw_freedir(cache, dir); -unpin: - if (file->parent) { - bftw_cache_unpin(cache, file->parent); - } -append: - SLIST_APPEND(&state->dirs, file); -} - /** Close a directory, asynchronously if possible. */ static int bftw_ioq_closedir(struct bftw_state *state, struct bfs_dir *dir) { if (bftw_ioq_reserve(state) == 0) { @@ -807,12 +764,76 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { return bftw_ioq_closedir(state, dir); } +/** Open a directory asynchronously. */ +static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { + if (bftw_ioq_reserve(state) != 0) { + goto fail; + } + + int dfd = AT_FDCWD; + struct bftw_cache *cache = &state->cache; + struct bftw_file *parent = file->parent; + if (parent) { + dfd = parent->fd; + if (dfd < 0) { + goto fail; + } + bftw_cache_pin(cache, parent); + } + + if (bftw_cache_reserve(state) != 0) { + goto unpin; + } + + struct bfs_dir *dir = bftw_allocdir(cache); + if (!dir) { + goto unpin; + } + + if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { + goto free; + } + + file->ioqueued = true; + --cache->capacity; + return 0; + +free: + bftw_freedir(cache, dir); +unpin: + if (parent) { + bftw_cache_unpin(cache, parent); + } +fail: + return -1; +} + +/** Push a directory onto the queue. */ +static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->type == BFS_DIR); + + SLIST_APPEND(&state->to_open, file, to_open); + + if (state->flags & BFTW_SORT) { + // When sorting, directories are kept in order on the to_read + // list; otherwise, they are only added once they are open + SLIST_APPEND(&state->to_read, file, to_read); + } + + while (state->to_open.head) { + if (bftw_ioq_opendir(state, state->to_open.head) == 0) { + SLIST_POP(&state->to_open, to_open); + } else { + break; + } + } +} + /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - struct bftw_file *dir = state->dirs.head; - bool have_files = state->files.head; + bool have_files = state->to_visit.head; if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting @@ -820,36 +841,39 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } } else { - while (!dir || !dir->dir) { + while (!state->to_read.head) { // Block if we have no other files/dirs to visit, or no room in the cache + bool have_dirs = state->to_open.head; bool have_room = state->cache.capacity > 0; - bool block = !(dir || have_files) || !have_room; + bool block = !(have_dirs || have_files) || !have_room; if (bftw_ioq_pop(state, block) < 0) { break; } - dir = state->dirs.head; } } - if (!dir) { + struct bftw_file *file = SLIST_POP(&state->to_read, to_read); + if (!file || file == state->to_open.head) { + file = SLIST_POP(&state->to_open, to_open); + } + if (!file) { return false; } - SLIST_POP(&state->dirs); - state->file = dir; - - while (dir->ioqueued) { + while (file->ioqueued) { bftw_ioq_pop(state, true); } + state->file = file; return true; } /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - return (state->file = SLIST_POP(&state->files)); + state->file = SLIST_POP(&state->to_visit, to_visit); + return state->file; } /** Build the path to the current file. */ @@ -1242,7 +1266,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { /** Sort a bftw_list by filename. */ static void bftw_list_sort(struct bftw_list *list) { - if (!list->head || !list->head->next) { + if (!list->head || !list->head->to_visit.next) { return; } @@ -1251,9 +1275,11 @@ static void bftw_list_sort(struct bftw_list *list) { SLIST_INIT(&right); // Split - for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) { - struct bftw_file *tortoise = SLIST_POP(list); - SLIST_APPEND(&left, tortoise); + for (struct bftw_file *hare = list->head; + hare && (hare = hare->to_visit.next); + hare = hare->to_visit.next) { + struct bftw_file *tortoise = SLIST_POP(list, to_visit); + SLIST_APPEND(&left, tortoise, to_visit); } SLIST_EXTEND(&right, list); @@ -1267,11 +1293,11 @@ static void bftw_list_sort(struct bftw_list *list) { struct bftw_file *rf = right.head; if (strcoll(lf->name, rf->name) <= 0) { - SLIST_POP(&left); - SLIST_APPEND(list, lf); + SLIST_POP(&left, to_visit); + SLIST_APPEND(list, lf, to_visit); } else { - SLIST_POP(&right); - SLIST_APPEND(list, rf); + SLIST_POP(&right, to_visit); + SLIST_APPEND(list, rf, to_visit); } } SLIST_EXTEND(list, &left); @@ -1285,9 +1311,9 @@ static void bftw_batch_finish(struct bftw_state *state) { } if (state->strategy != BFTW_BFS) { - SLIST_EXTEND(&state->batch, &state->files); + SLIST_EXTEND(&state->batch, &state->to_visit); } - SLIST_EXTEND(&state->files, &state->batch); + SLIST_EXTEND(&state->to_visit, &state->batch); } /** Close the current directory. */ @@ -1329,7 +1355,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - SLIST_APPEND(&state->batch, file); + SLIST_APPEND(&state->batch, file, to_visit); return 0; } @@ -1377,7 +1403,7 @@ static int bftw_state_destroy(struct bftw_state *state) { state->ioq = NULL; } - SLIST_EXTEND(&state->files, &state->batch); + SLIST_EXTEND(&state->to_visit, &state->batch); do { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); -- cgit v1.2.3 From eb466474e7b5ab79bbeccb2d99bb7b1cfc57af5f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 17 Jul 2023 20:30:57 -0400 Subject: bftw: Add dirs to the end of the queue in bftw_ioq_pop() I tried this before in #105 but it led to performance regressions. The key to avoiding those regressions is to put some backpressure on how many bfs_dir's can be allocated simultaneously. --- src/bftw.c | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index a412c26..0b90f55 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -366,6 +366,8 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, file->fd = bfs_dirfd(dir); bftw_cache_add(cache, file); } + + bftw_cache_pin(cache, file); } /** Free a bftw_file. */ @@ -409,6 +411,8 @@ struct bftw_state { struct bftw_list to_open; /** The queue of directories to read. */ struct bftw_list to_read; + /** Available capacity in to_read. */ + size_t dirlimit; /** The queue of files to visit. */ struct bftw_list to_visit; @@ -483,6 +487,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); + state->dirlimit = qdepth; SLIST_INIT(&state->to_visit); SLIST_INIT(&state->batch); @@ -539,10 +544,11 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { bftw_file_set_dir(cache, file, dir); } else { bftw_freedir(cache, dir); + ++state->dirlimit; } if (!(state->flags & BFTW_SORT)) { - SLIST_PREPEND(&state->to_read, file, to_read); + SLIST_APPEND(&state->to_read, file, to_read); } break; } @@ -766,6 +772,10 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { /** Open a directory asynchronously. */ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { + if (state->dirlimit == 0) { + goto fail; + } + if (bftw_ioq_reserve(state) != 0) { goto fail; } @@ -796,6 +806,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { file->ioqueued = true; --cache->capacity; + --state->dirlimit; return 0; free: @@ -861,6 +872,10 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } + if (file->dir) { + ++state->dirlimit; + } + while (file->ioqueued) { bftw_ioq_pop(state, true); } @@ -953,18 +968,17 @@ static int bftw_opendir(struct bftw_state *state) { state->direrror = 0; struct bftw_file *file = state->file; - if (file->dir) { - state->dir = file->dir; - } else { - if (bftw_build_path(state, NULL) != 0) { - return -1; - } - state->dir = bftw_file_opendir(state, file, state->path); + state->dir = file->dir; + if (state->dir) { + return 0; } - if (state->dir) { - bftw_cache_pin(&state->cache, file); - } else { + if (bftw_build_path(state, NULL) != 0) { + return -1; + } + + state->dir = bftw_file_opendir(state, file, state->path); + if (!state->dir) { state->direrror = errno; } -- cgit v1.2.3 From 815798e1eea7fc8dacd5acab40202ec4d251d517 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 18 Jul 2023 11:43:21 -0400 Subject: bftw: Add a queue of directories to unwrap For !BFS_USE_UNWRAPDIR, if a file is still pinned in bftw_closedir(), it has to stay open until its pincount drops to zero. Since this happens in bftw_ioq_pop(), we can't immediately call bftw_unwrapdir() as that adds to the ioq. Instead, add it to a list that gets drained by the next bftw_gc(). --- src/bftw.c | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 0b90f55..dd34d64 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -119,6 +119,8 @@ struct bftw_file { struct { struct bftw_file *next; } to_open; /** The next directory to read. */ struct { struct bftw_file *next; } to_read; + /** The next directory to close. */ + struct { struct bftw_file *next; } to_close; /** The next file to visit. */ struct { struct bftw_file *next; } to_visit; @@ -336,6 +338,7 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->to_open.next = NULL; file->to_read.next = NULL; + file->to_close.next = NULL; file->to_visit.next = NULL; file->lru.prev = file->lru.next = NULL; @@ -411,6 +414,8 @@ struct bftw_state { struct bftw_list to_open; /** The queue of directories to read. */ struct bftw_list to_read; + /** The queue of unpinned directories to unwrap. */ + struct bftw_list to_close; /** Available capacity in to_read. */ size_t dirlimit; @@ -487,6 +492,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); + SLIST_INIT(&state->to_close); state->dirlimit = qdepth; SLIST_INIT(&state->to_visit); @@ -516,6 +522,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct bftw_cache *cache = &state->cache; struct bftw_file *file; + struct bftw_file *parent; struct bfs_dir *dir; enum ioq_op op = ent->op; @@ -535,8 +542,12 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { file->ioqueued = false; ++cache->capacity; - if (file->parent) { - bftw_cache_unpin(cache, file->parent); + parent = file->parent; + if (parent) { + bftw_cache_unpin(cache, parent); + if (parent->pincount == 0 && parent->dir) { + SLIST_APPEND(&state->to_close, parent, to_close); + } } dir = ent->opendir.dir; @@ -1228,9 +1239,10 @@ enum bftw_gc_flags { static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; - if (state->dir) { - bftw_cache_unpin(&state->cache, state->file); - bftw_unwrapdir(state, state->file); + struct bftw_file *file = state->file; + if (file && file->dir) { + bftw_cache_unpin(&state->cache, file); + SLIST_APPEND(&state->to_close, file, to_close); } state->dir = NULL; state->de = NULL; @@ -1247,9 +1259,12 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { } state->direrror = 0; + while ((file = SLIST_POP(&state->to_close, to_close))) { + bftw_unwrapdir(state, file); + } + enum bftw_gc_flags visit = BFTW_VISIT_FILE; - while (state->file) { - struct bftw_file *file = state->file; + while ((file = state->file)) { if (--file->refcount > 0) { state->file = NULL; break; -- cgit v1.2.3 From 0d5bcc9e5c53f64024afbad19b1a01ae9b2937af Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 18 Jul 2023 11:56:41 -0400 Subject: bftw: Use a larger ioq depth Now that the dirlimit provides backpressure on the number of open directories, we can use a uniformly larger queue depth for increased performance. The current parameters were tuned with a small grid search on my workstation. --- src/bftw.c | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index dd34d64..c66e607 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -462,42 +462,32 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg errno = EMFILE; return -1; } - - state->path = dstralloc(0); - if (!state->path) { - return -1; - } - bftw_cache_init(&state->cache, args->nopenfd); - size_t qdepth = args->nopenfd - 1; - if (qdepth > 1024) { - qdepth = 1024; - } - - size_t nthreads = args->nthreads; - if (nthreads > qdepth) { - nthreads = qdepth; - } - - state->ioq = NULL; - if (nthreads > 0) { - state->ioq = ioq_create(qdepth, nthreads); + state->nthreads = args->nthreads; + if (state->nthreads > 0) { + state->ioq = ioq_create(4096, state->nthreads); if (!state->ioq) { - dstrfree(state->path); return -1; } + } else { + state->ioq = NULL; } - state->nthreads = nthreads; SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); SLIST_INIT(&state->to_close); - state->dirlimit = qdepth; + + size_t dirlimit = args->nopenfd - 1; + if (dirlimit > 1024) { + dirlimit = 1024; + } + state->dirlimit = dirlimit; SLIST_INIT(&state->to_visit); SLIST_INIT(&state->batch); + state->path = NULL; state->file = NULL; state->previous = NULL; -- cgit v1.2.3 From 0e72e5c985db9a131aea3f3e9238a8916c470d8e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 18 Jul 2023 12:14:07 -0400 Subject: bftw: Use bftw_file->next for multiple lists A file can be on the to_open and to_read lists at the same time, but otherwise only one list, so we can save memory by sharing the pointers. --- src/bftw.c | 50 +++++++++++++++++++++----------------------------- 1 file changed, 21 insertions(+), 29 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index c66e607..5d28612 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -115,14 +115,10 @@ struct bftw_file { /** The root under which this file was found. */ struct bftw_file *root; - /** The next directory to open. */ - struct { struct bftw_file *next; } to_open; + /** The next file to open/close/visit. */ + struct bftw_file *next; /** The next directory to read. */ struct { struct bftw_file *next; } to_read; - /** The next directory to close. */ - struct { struct bftw_file *next; } to_close; - /** The next file to visit. */ - struct { struct bftw_file *next; } to_visit; /** LRU list node. */ struct { @@ -336,10 +332,8 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->nameoff = 0; } - file->to_open.next = NULL; + file->next = NULL; file->to_read.next = NULL; - file->to_close.next = NULL; - file->to_visit.next = NULL; file->lru.prev = file->lru.next = NULL; file->refcount = 1; @@ -536,7 +530,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { if (parent) { bftw_cache_unpin(cache, parent); if (parent->pincount == 0 && parent->dir) { - SLIST_APPEND(&state->to_close, parent, to_close); + SLIST_APPEND(&state->to_close, parent); } } @@ -665,10 +659,10 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons struct bftw_file *cur; for (cur = file; cur != base; cur = cur->parent) { - SLIST_PREPEND(&parents, cur, to_open); + SLIST_PREPEND(&parents, cur); } - while ((cur = SLIST_POP(&parents, to_open))) { + while ((cur = SLIST_POP(&parents))) { if (!cur->parent || cur->parent->fd >= 0) { bftw_file_openat(state, cur, cur->parent, cur->name); } @@ -824,7 +818,7 @@ fail: static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bfs_assert(file->type == BFS_DIR); - SLIST_APPEND(&state->to_open, file, to_open); + SLIST_APPEND(&state->to_open, file); if (state->flags & BFTW_SORT) { // When sorting, directories are kept in order on the to_read @@ -834,7 +828,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { while (state->to_open.head) { if (bftw_ioq_opendir(state, state->to_open.head) == 0) { - SLIST_POP(&state->to_open, to_open); + SLIST_POP(&state->to_open); } else { break; } @@ -867,7 +861,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { struct bftw_file *file = SLIST_POP(&state->to_read, to_read); if (!file || file == state->to_open.head) { - file = SLIST_POP(&state->to_open, to_open); + file = SLIST_POP(&state->to_open); } if (!file) { return false; @@ -888,7 +882,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - state->file = SLIST_POP(&state->to_visit, to_visit); + state->file = SLIST_POP(&state->to_visit); return state->file; } @@ -1232,7 +1226,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { struct bftw_file *file = state->file; if (file && file->dir) { bftw_cache_unpin(&state->cache, file); - SLIST_APPEND(&state->to_close, file, to_close); + SLIST_APPEND(&state->to_close, file); } state->dir = NULL; state->de = NULL; @@ -1249,7 +1243,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { } state->direrror = 0; - while ((file = SLIST_POP(&state->to_close, to_close))) { + while ((file = SLIST_POP(&state->to_close))) { bftw_unwrapdir(state, file); } @@ -1285,7 +1279,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { /** Sort a bftw_list by filename. */ static void bftw_list_sort(struct bftw_list *list) { - if (!list->head || !list->head->to_visit.next) { + if (!list->head || !list->head->next) { return; } @@ -1294,11 +1288,9 @@ static void bftw_list_sort(struct bftw_list *list) { SLIST_INIT(&right); // Split - for (struct bftw_file *hare = list->head; - hare && (hare = hare->to_visit.next); - hare = hare->to_visit.next) { - struct bftw_file *tortoise = SLIST_POP(list, to_visit); - SLIST_APPEND(&left, tortoise, to_visit); + for (struct bftw_file *hare = list->head; hare && (hare = hare->next); hare = hare->next) { + struct bftw_file *tortoise = SLIST_POP(list); + SLIST_APPEND(&left, tortoise); } SLIST_EXTEND(&right, list); @@ -1312,11 +1304,11 @@ static void bftw_list_sort(struct bftw_list *list) { struct bftw_file *rf = right.head; if (strcoll(lf->name, rf->name) <= 0) { - SLIST_POP(&left, to_visit); - SLIST_APPEND(list, lf, to_visit); + SLIST_POP(&left); + SLIST_APPEND(list, lf); } else { - SLIST_POP(&right, to_visit); - SLIST_APPEND(list, rf, to_visit); + SLIST_POP(&right); + SLIST_APPEND(list, rf); } } SLIST_EXTEND(list, &left); @@ -1374,7 +1366,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - SLIST_APPEND(&state->batch, file, to_visit); + SLIST_APPEND(&state->batch, file); return 0; } -- cgit v1.2.3 From fb023fdeea088fa66b0df6cbf4e7becf8edeebbb Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 18 Jul 2023 12:23:21 -0400 Subject: Release 3.0.1 --- Makefile | 2 +- docs/CHANGELOG.md | 18 ++++++++++++++++++ src/config.h | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index db955b5..406506b 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 3.0 +VERSION := 3.0.1 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index a1b0049..1cc95e9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,24 @@ 3.* === +3.0.1 +----- + +**July 18, 2023** + +### Bug fixes + +- Traversal fixes that mostly affect large directory trees ([#107]) + + - `bfs` could encounter `EMFILE`, close a file, and retry many times, particularly with `-j1` + + - Breadth-first search could become highly unbalanced, negating many of the benefits of `bfs` + + - On non-{Linux,FreeBSD} plaforms, directories could stay open longer than necessary, consuming extra memory + +[#107]: https://github.com/tavianator/bfs/pull/107 + + 3.0 --- diff --git a/src/config.h b/src/config.h index 59d2671..fd4961f 100644 --- a/src/config.h +++ b/src/config.h @@ -22,7 +22,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "3.0" +# define BFS_VERSION "3.0.1" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From 7ce554e17bb2185498d28cfc6ea795a9d39eae0b Mon Sep 17 00:00:00 2001 From: Jason Stewart Date: Wed, 19 Jul 2023 12:50:14 -0400 Subject: CFLAGS adjustment in Makefile `-flto` to `-flto=auto` to eliminate `using serial compilation of 3 LTRANS jobs` gcc warning see https://stackoverflow.com/questions/72218980/gcc-v12-1-warning-about-serial-compilation --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 406506b..7987687 100644 --- a/Makefile +++ b/Makefile @@ -162,7 +162,7 @@ endif ifneq ($(filter release,$(MAKECMDGOALS)),) LOCAL_CPPFLAGS += -DNDEBUG -CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto +CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto=auto endif ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) $(EXTRA_CPPFLAGS) -- cgit v1.2.3 From 5f0b2636c86a12dac9ab670eebe1b6bc2fe75457 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 20 Jul 2023 08:45:11 -0400 Subject: README: Add official Arch Linux package --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index aa3d666..bd1d46c 100644 --- a/README.md +++ b/README.md @@ -218,8 +218,8 @@ Installation Alpine Linux # apk add bfs -Arch Linux -(Available in the AUR) +Arch Linux +# pacman -S bfs Debian/Ubuntu # apt install bfs -- cgit v1.2.3 From 44cf900b78f7a9e6a838c82be38ddad3c0c60a25 Mon Sep 17 00:00:00 2001 From: Raf Czlonka Date: Thu, 20 Jul 2023 16:58:51 +0100 Subject: Rename Makefile to GNUmakefile --- GNUmakefile | 331 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ Makefile | 331 ------------------------------------------------------- docs/BUILDING.md | 4 +- docs/USAGE.md | 2 +- 4 files changed, 334 insertions(+), 334 deletions(-) create mode 100644 GNUmakefile delete mode 100644 Makefile diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..069c548 --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,331 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +ifneq ($(wildcard .git),) +VERSION := $(shell git describe --always 2>/dev/null) +endif + +ifndef VERSION +VERSION := 3.0.1 +endif + +ifndef OS +OS := $(shell uname) +endif + +ifndef ARCH +ARCH := $(shell uname -m) +endif + +CC ?= gcc +INSTALL ?= install +MKDIR ?= mkdir -p +RM ?= rm -f + +export BUILDDIR ?= . +DESTDIR ?= +PREFIX ?= /usr +MANDIR ?= $(PREFIX)/share/man + +BIN := $(BUILDDIR)/bin +OBJ := $(BUILDDIR)/obj + +DEFAULT_CFLAGS := \ + -g \ + -Wall \ + -Wformat=2 \ + -Werror=implicit \ + -Wimplicit-fallthrough \ + -Wmissing-declarations \ + -Wshadow \ + -Wsign-compare \ + -Wstrict-prototypes + +CFLAGS ?= $(DEFAULT_CFLAGS) +LDFLAGS ?= +DEPFLAGS ?= -MD -MP -MF $(@:.o=.d) + +LOCAL_CPPFLAGS := \ + -D__EXTENSIONS__ \ + -D_ATFILE_SOURCE \ + -D_BSD_SOURCE \ + -D_DARWIN_C_SOURCE \ + -D_DEFAULT_SOURCE \ + -D_GNU_SOURCE \ + -D_LARGEFILE64_SOURCE \ + -D_FILE_OFFSET_BITS=64 \ + -D_TIME_BITS=64 \ + -DBFS_VERSION=\"$(VERSION)\" + +LOCAL_CFLAGS := -std=c17 -pthread +LOCAL_LDFLAGS := +LOCAL_LDLIBS := + +ASAN := $(filter asan,$(MAKECMDGOALS)) +LSAN := $(filter lsan,$(MAKECMDGOALS)) +MSAN := $(filter msan,$(MAKECMDGOALS)) +TSAN := $(filter tsan,$(MAKECMDGOALS)) +UBSAN := $(filter ubsan,$(MAKECMDGOALS)) + +ifdef ASAN +LOCAL_CFLAGS += -fsanitize=address +SANITIZE := y +endif + +ifdef LSAN +LOCAL_CFLAGS += -fsanitize=leak +SANITIZE := y +endif + +ifdef MSAN +# msan needs all code instrumented +NOLIBS := y +LOCAL_CFLAGS += -fsanitize=memory -fsanitize-memory-track-origins +SANITIZE := y +endif + +ifdef TSAN +# tsan needs all code instrumented +NOLIBS := y +# https://github.com/google/sanitizers/issues/342 +LOCAL_CPPFLAGS += -DBFS_USE_TARGET_CLONES=0 +LOCAL_CFLAGS += -fsanitize=thread +SANITIZE := y +endif + +ifdef UBSAN +LOCAL_CFLAGS += -fsanitize=undefined +SANITIZE := y +endif + +ifdef SANITIZE +LOCAL_CFLAGS += -fno-sanitize-recover=all +endif + +ifndef NOLIBS +USE_ONIGURUMA := y +endif + +ifdef USE_ONIGURUMA +LOCAL_CPPFLAGS += -DBFS_USE_ONIGURUMA=1 + +ONIG_CONFIG := $(shell command -v onig-config 2>/dev/null) +ifdef ONIG_CONFIG +ONIG_CFLAGS := $(shell $(ONIG_CONFIG) --cflags) +ONIG_LDLIBS := $(shell $(ONIG_CONFIG) --libs) +else +ONIG_LDLIBS := -lonig +endif + +LOCAL_CFLAGS += $(ONIG_CFLAGS) +LOCAL_LDLIBS += $(ONIG_LDLIBS) +endif # USE_ONIGURUMA + +ifeq ($(OS),Linux) +ifndef NOLIBS +USE_ACL := y +USE_ATTR := y +USE_LIBCAP := y +endif + +ifdef USE_ACL +LOCAL_LDLIBS += -lacl +else +LOCAL_CPPFLAGS += -DBFS_USE_SYS_ACL_H=0 +endif + +ifdef USE_ATTR +LOCAL_LDLIBS += -lattr +else +LOCAL_CPPFLAGS += -DBFS_USE_SYS_XATTR_H=0 +endif + +ifdef USE_LIBCAP +LOCAL_LDLIBS += -lcap +else +LOCAL_CPPFLAGS += -DBFS_USE_SYS_CAPABILITY_H=0 +endif + +LOCAL_LDFLAGS += -Wl,--as-needed +LOCAL_LDLIBS += -lrt +endif # Linux + +ifeq ($(OS),NetBSD) +LOCAL_LDLIBS += -lutil +endif + +ifneq ($(filter gcov,$(MAKECMDGOALS)),) +LOCAL_CFLAGS += --coverage +# gcov only intercepts fork()/exec() with -std=gnu* +LOCAL_CFLAGS := $(patsubst -std=c%,-std=gnu%,$(LOCAL_CFLAGS)) +endif + +ifneq ($(filter release,$(MAKECMDGOALS)),) +LOCAL_CPPFLAGS += -DNDEBUG +CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto=auto +endif + +ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) $(EXTRA_CPPFLAGS) +ALL_CFLAGS = $(ALL_CPPFLAGS) $(LOCAL_CFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(DEPFLAGS) +ALL_LDFLAGS = $(ALL_CFLAGS) $(LOCAL_LDFLAGS) $(LDFLAGS) $(EXTRA_LDFLAGS) +ALL_LDLIBS = $(LOCAL_LDLIBS) $(LDLIBS) $(EXTRA_LDLIBS) + +# Default make target +bfs: $(BIN)/bfs +.PHONY: bfs + +# Goals that are treated like flags by this makefile +FLAG_GOALS := asan lsan msan tsan ubsan gcov release + +# These are the remaining non-flag goals +GOALS := $(filter-out $(FLAG_GOALS),$(MAKECMDGOALS)) + +# Build the default goal if only flag goals are specified +FLAG_PREREQS := +ifndef GOALS +FLAG_PREREQS += bfs +endif + +# Make sure that "make release" builds everything, but "make release obj/src/main.o" doesn't +$(FLAG_GOALS): $(FLAG_PREREQS) + @: +.PHONY: $(FLAG_GOALS) + +all: bfs tests +.PHONY: all + +$(BIN)/%: + @$(MKDIR) $(@D) + +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ +ifeq ($(OS) $(SANITIZE),FreeBSD y) + elfctl -e +noaslr $@ +endif + +$(OBJ)/%.o: %.c $(OBJ)/FLAGS + @$(MKDIR) $(@D) + $(CC) $(ALL_CFLAGS) -c $< -o $@ + +# Save the full set of flags to rebuild everything when they change +$(OBJ)/FLAGS.new: + @$(MKDIR) $(@D) + @echo $(CC) : $(ALL_CFLAGS) : $(ALL_LDFLAGS) : $(ALL_LDLIBS) >$@ +.PHONY: $(OBJ)/FLAGS.new + +# Only update obj/FLAGS if obj/FLAGS.new is different +$(OBJ)/FLAGS: $(OBJ)/FLAGS.new + @test -e $@ && cmp -s $@ $< && rm $< || mv $< $@ + +# All object files except the entry point +LIBBFS := \ + $(OBJ)/src/alloc.o \ + $(OBJ)/src/bar.o \ + $(OBJ)/src/bfstd.o \ + $(OBJ)/src/bftw.o \ + $(OBJ)/src/color.o \ + $(OBJ)/src/ctx.o \ + $(OBJ)/src/darray.o \ + $(OBJ)/src/diag.o \ + $(OBJ)/src/dir.o \ + $(OBJ)/src/dstring.o \ + $(OBJ)/src/eval.o \ + $(OBJ)/src/exec.o \ + $(OBJ)/src/fsade.o \ + $(OBJ)/src/ioq.o \ + $(OBJ)/src/mtab.o \ + $(OBJ)/src/opt.o \ + $(OBJ)/src/parse.o \ + $(OBJ)/src/printf.o \ + $(OBJ)/src/pwcache.o \ + $(OBJ)/src/stat.o \ + $(OBJ)/src/trie.o \ + $(OBJ)/src/typo.o \ + $(OBJ)/src/xregex.o \ + $(OBJ)/src/xspawn.o \ + $(OBJ)/src/xtime.o + +# The main executable +$(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) + +# Standalone unit tests +UNITS := alloc bfstd bit trie xtimegm +UNIT_TESTS := $(UNITS:%=$(BIN)/tests/%) +UNIT_CHECKS := $(UNITS:%=check-%) + +# Testing utilities +TEST_UTILS := $(BIN)/tests/mksock $(BIN)/tests/xtouch + +TESTS := $(UNIT_TESTS) $(TEST_UTILS) + +tests: $(TESTS) +.PHONY: tests + +$(TESTS): $(BIN)/tests/%: $(OBJ)/tests/%.o $(LIBBFS) + +# The different search strategies that we test +STRATEGIES := bfs dfs ids eds +STRATEGY_CHECKS := $(STRATEGIES:%=check-%) + +# All the different checks we run +CHECKS := $(UNIT_CHECKS) $(STRATEGY_CHECKS) + +check: $(CHECKS) +.PHONY: check $(CHECKS) + +$(UNIT_CHECKS): check-%: $(BIN)/tests/% + $< + +$(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(TEST_UTILS) + ./tests/tests.sh --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) + +# Custom test flags for distcheck +DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" + +distcheck: + +$(MAKE) -B asan ubsan check $(DISTCHECK_FLAGS) +ifneq ($(OS),Darwin) + +$(MAKE) -B msan ubsan check CC=clang $(DISTCHECK_FLAGS) +endif + +$(MAKE) -B tsan ubsan check CC=clang $(DISTCHECK_FLAGS) +ifeq ($(OS) $(ARCH),Linux x86_64) + +$(MAKE) -B check EXTRA_CFLAGS="-m32" ONIG_CONFIG= $(DISTCHECK_FLAGS) +endif + +$(MAKE) -B release check $(DISTCHECK_FLAGS) + +$(MAKE) -B check $(DISTCHECK_FLAGS) + +$(MAKE) check-install $(DISTCHECK_FLAGS) +.PHONY: distcheck + +clean: + $(RM) -r $(BIN) $(OBJ) +.PHONY: clean + +install: + $(MKDIR) $(DESTDIR)$(PREFIX)/bin + $(INSTALL) -m755 $(BIN)/bfs $(DESTDIR)$(PREFIX)/bin/bfs + $(MKDIR) $(DESTDIR)$(MANDIR)/man1 + $(INSTALL) -m644 docs/bfs.1 $(DESTDIR)$(MANDIR)/man1/bfs.1 + $(MKDIR) $(DESTDIR)$(PREFIX)/share/bash-completion/completions + $(INSTALL) -m644 completions/bfs.bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs + $(MKDIR) $(DESTDIR)$(PREFIX)/share/zsh/site-functions + $(INSTALL) -m644 completions/bfs.zsh $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_bfs + $(MKDIR) $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d + $(INSTALL) -m644 completions/bfs.fish $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/bfs.fish +.PHONY: install + +uninstall: + $(RM) $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs + $(RM) $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_bfs + $(RM) $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/bfs.fish + $(RM) $(DESTDIR)$(MANDIR)/man1/bfs.1 + $(RM) $(DESTDIR)$(PREFIX)/bin/bfs +.PHONY: uninstall + +check-install: + +$(MAKE) install DESTDIR=$(BUILDDIR)/pkg + +$(MAKE) uninstall DESTDIR=$(BUILDDIR)/pkg + $(BIN)/bfs $(BUILDDIR)/pkg -not -type d -print -exit 1 + $(RM) -r $(BUILDDIR)/pkg +.PHONY: check-install + +.SUFFIXES: + +-include $(wildcard $(OBJ)/*/*.d) diff --git a/Makefile b/Makefile deleted file mode 100644 index 7987687..0000000 --- a/Makefile +++ /dev/null @@ -1,331 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -ifneq ($(wildcard .git),) -VERSION := $(shell git describe --always 2>/dev/null) -endif - -ifndef VERSION -VERSION := 3.0.1 -endif - -ifndef OS -OS := $(shell uname) -endif - -ifndef ARCH -ARCH := $(shell uname -m) -endif - -CC ?= gcc -INSTALL ?= install -MKDIR ?= mkdir -p -RM ?= rm -f - -export BUILDDIR ?= . -DESTDIR ?= -PREFIX ?= /usr -MANDIR ?= $(PREFIX)/share/man - -BIN := $(BUILDDIR)/bin -OBJ := $(BUILDDIR)/obj - -DEFAULT_CFLAGS := \ - -g \ - -Wall \ - -Wformat=2 \ - -Werror=implicit \ - -Wimplicit-fallthrough \ - -Wmissing-declarations \ - -Wshadow \ - -Wsign-compare \ - -Wstrict-prototypes - -CFLAGS ?= $(DEFAULT_CFLAGS) -LDFLAGS ?= -DEPFLAGS ?= -MD -MP -MF $(@:.o=.d) - -LOCAL_CPPFLAGS := \ - -D__EXTENSIONS__ \ - -D_ATFILE_SOURCE \ - -D_BSD_SOURCE \ - -D_DARWIN_C_SOURCE \ - -D_DEFAULT_SOURCE \ - -D_GNU_SOURCE \ - -D_LARGEFILE64_SOURCE \ - -D_FILE_OFFSET_BITS=64 \ - -D_TIME_BITS=64 \ - -DBFS_VERSION=\"$(VERSION)\" - -LOCAL_CFLAGS := -std=c17 -pthread -LOCAL_LDFLAGS := -LOCAL_LDLIBS := - -ASAN := $(filter asan,$(MAKECMDGOALS)) -LSAN := $(filter lsan,$(MAKECMDGOALS)) -MSAN := $(filter msan,$(MAKECMDGOALS)) -TSAN := $(filter tsan,$(MAKECMDGOALS)) -UBSAN := $(filter ubsan,$(MAKECMDGOALS)) - -ifdef ASAN -LOCAL_CFLAGS += -fsanitize=address -SANITIZE := y -endif - -ifdef LSAN -LOCAL_CFLAGS += -fsanitize=leak -SANITIZE := y -endif - -ifdef MSAN -# msan needs all code instrumented -NOLIBS := y -LOCAL_CFLAGS += -fsanitize=memory -fsanitize-memory-track-origins -SANITIZE := y -endif - -ifdef TSAN -# tsan needs all code instrumented -NOLIBS := y -# https://github.com/google/sanitizers/issues/342 -LOCAL_CPPFLAGS += -DBFS_USE_TARGET_CLONES=0 -LOCAL_CFLAGS += -fsanitize=thread -SANITIZE := y -endif - -ifdef UBSAN -LOCAL_CFLAGS += -fsanitize=undefined -SANITIZE := y -endif - -ifdef SANITIZE -LOCAL_CFLAGS += -fno-sanitize-recover=all -endif - -ifndef NOLIBS -USE_ONIGURUMA := y -endif - -ifdef USE_ONIGURUMA -LOCAL_CPPFLAGS += -DBFS_USE_ONIGURUMA=1 - -ONIG_CONFIG := $(shell command -v onig-config 2>/dev/null) -ifdef ONIG_CONFIG -ONIG_CFLAGS := $(shell $(ONIG_CONFIG) --cflags) -ONIG_LDLIBS := $(shell $(ONIG_CONFIG) --libs) -else -ONIG_LDLIBS := -lonig -endif - -LOCAL_CFLAGS += $(ONIG_CFLAGS) -LOCAL_LDLIBS += $(ONIG_LDLIBS) -endif # USE_ONIGURUMA - -ifeq ($(OS),Linux) -ifndef NOLIBS -USE_ACL := y -USE_ATTR := y -USE_LIBCAP := y -endif - -ifdef USE_ACL -LOCAL_LDLIBS += -lacl -else -LOCAL_CPPFLAGS += -DBFS_USE_SYS_ACL_H=0 -endif - -ifdef USE_ATTR -LOCAL_LDLIBS += -lattr -else -LOCAL_CPPFLAGS += -DBFS_USE_SYS_XATTR_H=0 -endif - -ifdef USE_LIBCAP -LOCAL_LDLIBS += -lcap -else -LOCAL_CPPFLAGS += -DBFS_USE_SYS_CAPABILITY_H=0 -endif - -LOCAL_LDFLAGS += -Wl,--as-needed -LOCAL_LDLIBS += -lrt -endif # Linux - -ifeq ($(OS),NetBSD) -LOCAL_LDLIBS += -lutil -endif - -ifneq ($(filter gcov,$(MAKECMDGOALS)),) -LOCAL_CFLAGS += --coverage -# gcov only intercepts fork()/exec() with -std=gnu* -LOCAL_CFLAGS := $(patsubst -std=c%,-std=gnu%,$(LOCAL_CFLAGS)) -endif - -ifneq ($(filter release,$(MAKECMDGOALS)),) -LOCAL_CPPFLAGS += -DNDEBUG -CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto=auto -endif - -ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) $(EXTRA_CPPFLAGS) -ALL_CFLAGS = $(ALL_CPPFLAGS) $(LOCAL_CFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(DEPFLAGS) -ALL_LDFLAGS = $(ALL_CFLAGS) $(LOCAL_LDFLAGS) $(LDFLAGS) $(EXTRA_LDFLAGS) -ALL_LDLIBS = $(LOCAL_LDLIBS) $(LDLIBS) $(EXTRA_LDLIBS) - -# Default make target -bfs: $(BIN)/bfs -.PHONY: bfs - -# Goals that are treated like flags by this Makefile -FLAG_GOALS := asan lsan msan tsan ubsan gcov release - -# These are the remaining non-flag goals -GOALS := $(filter-out $(FLAG_GOALS),$(MAKECMDGOALS)) - -# Build the default goal if only flag goals are specified -FLAG_PREREQS := -ifndef GOALS -FLAG_PREREQS += bfs -endif - -# Make sure that "make release" builds everything, but "make release obj/src/main.o" doesn't -$(FLAG_GOALS): $(FLAG_PREREQS) - @: -.PHONY: $(FLAG_GOALS) - -all: bfs tests -.PHONY: all - -$(BIN)/%: - @$(MKDIR) $(@D) - +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ -ifeq ($(OS) $(SANITIZE),FreeBSD y) - elfctl -e +noaslr $@ -endif - -$(OBJ)/%.o: %.c $(OBJ)/FLAGS - @$(MKDIR) $(@D) - $(CC) $(ALL_CFLAGS) -c $< -o $@ - -# Save the full set of flags to rebuild everything when they change -$(OBJ)/FLAGS.new: - @$(MKDIR) $(@D) - @echo $(CC) : $(ALL_CFLAGS) : $(ALL_LDFLAGS) : $(ALL_LDLIBS) >$@ -.PHONY: $(OBJ)/FLAGS.new - -# Only update obj/FLAGS if obj/FLAGS.new is different -$(OBJ)/FLAGS: $(OBJ)/FLAGS.new - @test -e $@ && cmp -s $@ $< && rm $< || mv $< $@ - -# All object files except the entry point -LIBBFS := \ - $(OBJ)/src/alloc.o \ - $(OBJ)/src/bar.o \ - $(OBJ)/src/bfstd.o \ - $(OBJ)/src/bftw.o \ - $(OBJ)/src/color.o \ - $(OBJ)/src/ctx.o \ - $(OBJ)/src/darray.o \ - $(OBJ)/src/diag.o \ - $(OBJ)/src/dir.o \ - $(OBJ)/src/dstring.o \ - $(OBJ)/src/eval.o \ - $(OBJ)/src/exec.o \ - $(OBJ)/src/fsade.o \ - $(OBJ)/src/ioq.o \ - $(OBJ)/src/mtab.o \ - $(OBJ)/src/opt.o \ - $(OBJ)/src/parse.o \ - $(OBJ)/src/printf.o \ - $(OBJ)/src/pwcache.o \ - $(OBJ)/src/stat.o \ - $(OBJ)/src/trie.o \ - $(OBJ)/src/typo.o \ - $(OBJ)/src/xregex.o \ - $(OBJ)/src/xspawn.o \ - $(OBJ)/src/xtime.o - -# The main executable -$(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) - -# Standalone unit tests -UNITS := alloc bfstd bit trie xtimegm -UNIT_TESTS := $(UNITS:%=$(BIN)/tests/%) -UNIT_CHECKS := $(UNITS:%=check-%) - -# Testing utilities -TEST_UTILS := $(BIN)/tests/mksock $(BIN)/tests/xtouch - -TESTS := $(UNIT_TESTS) $(TEST_UTILS) - -tests: $(TESTS) -.PHONY: tests - -$(TESTS): $(BIN)/tests/%: $(OBJ)/tests/%.o $(LIBBFS) - -# The different search strategies that we test -STRATEGIES := bfs dfs ids eds -STRATEGY_CHECKS := $(STRATEGIES:%=check-%) - -# All the different checks we run -CHECKS := $(UNIT_CHECKS) $(STRATEGY_CHECKS) - -check: $(CHECKS) -.PHONY: check $(CHECKS) - -$(UNIT_CHECKS): check-%: $(BIN)/tests/% - $< - -$(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(TEST_UTILS) - ./tests/tests.sh --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) - -# Custom test flags for distcheck -DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" - -distcheck: - +$(MAKE) -B asan ubsan check $(DISTCHECK_FLAGS) -ifneq ($(OS),Darwin) - +$(MAKE) -B msan ubsan check CC=clang $(DISTCHECK_FLAGS) -endif - +$(MAKE) -B tsan ubsan check CC=clang $(DISTCHECK_FLAGS) -ifeq ($(OS) $(ARCH),Linux x86_64) - +$(MAKE) -B check EXTRA_CFLAGS="-m32" ONIG_CONFIG= $(DISTCHECK_FLAGS) -endif - +$(MAKE) -B release check $(DISTCHECK_FLAGS) - +$(MAKE) -B check $(DISTCHECK_FLAGS) - +$(MAKE) check-install $(DISTCHECK_FLAGS) -.PHONY: distcheck - -clean: - $(RM) -r $(BIN) $(OBJ) -.PHONY: clean - -install: - $(MKDIR) $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m755 $(BIN)/bfs $(DESTDIR)$(PREFIX)/bin/bfs - $(MKDIR) $(DESTDIR)$(MANDIR)/man1 - $(INSTALL) -m644 docs/bfs.1 $(DESTDIR)$(MANDIR)/man1/bfs.1 - $(MKDIR) $(DESTDIR)$(PREFIX)/share/bash-completion/completions - $(INSTALL) -m644 completions/bfs.bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs - $(MKDIR) $(DESTDIR)$(PREFIX)/share/zsh/site-functions - $(INSTALL) -m644 completions/bfs.zsh $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_bfs - $(MKDIR) $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d - $(INSTALL) -m644 completions/bfs.fish $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/bfs.fish -.PHONY: install - -uninstall: - $(RM) $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs - $(RM) $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_bfs - $(RM) $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/bfs.fish - $(RM) $(DESTDIR)$(MANDIR)/man1/bfs.1 - $(RM) $(DESTDIR)$(PREFIX)/bin/bfs -.PHONY: uninstall - -check-install: - +$(MAKE) install DESTDIR=$(BUILDDIR)/pkg - +$(MAKE) uninstall DESTDIR=$(BUILDDIR)/pkg - $(BIN)/bfs $(BUILDDIR)/pkg -not -type d -print -exit 1 - $(RM) -r $(BUILDDIR)/pkg -.PHONY: check-install - -.SUFFIXES: - --include $(wildcard $(OBJ)/*/*.d) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index d0cb1fc..b19ef00 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -49,7 +49,7 @@ You can combine multiple flags and other targets (e.g. `make asan ubsan check`), ### Flags Other flags are controlled with `make` variables and/or environment variables. -Here are some of the common ones; check the [`Makefile`](/Makefile) for more. +Here are some of the common ones; check the [`GNUmakefile`](/GNUmakefile) for more. | Flag | Description | |----------------------------------|---------------------------------------------| @@ -84,7 +84,7 @@ These dependencies are optional, and can be turned off at build time if necessar ### Dependency tracking -The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`Makefile`](/Makefile)). +The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`GNUmakefile`](/GNUmakefile)). So if you edit a header file, `make` will rebuild the necessary object files ensuring they don't go out of sync. We go one step further than most build systems by tracking the flags that were used for the previous compilation. diff --git a/docs/USAGE.md b/docs/USAGE.md index 0e45b34..86eef87 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -7,8 +7,8 @@ When invoked with no arguments, `bfs` will list everything under the current dir ```console $ bfs . +./GNUmakefile ./LICENSE -./Makefile ./README.md ./completions ./docs -- cgit v1.2.3 From d91b3f5188e840e56e8ff81d4ab3807ab73e1d35 Mon Sep 17 00:00:00 2001 From: Wayne Scott Date: Sat, 22 Jul 2023 05:55:58 -0400 Subject: README.md: fix reversed examples --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index bd1d46c..9a914a1 100644 --- a/README.md +++ b/README.md @@ -79,29 +79,29 @@ On the other hand, `bfs` lists files from shallowest to deepest, so you never ha ```console -$ find haystack +$ bfs haystack haystack haystack/deep -haystack/deep/1 -haystack/deep/1/2 -haystack/deep/1/2/3 -haystack/deep/1/2/3/4 -... haystack/shallow +haystack/deep/1 haystack/shallow/needle +... ``` ```console -$ bfs haystack +$ find haystack haystack haystack/deep -haystack/shallow haystack/deep/1 -haystack/shallow/needle +haystack/deep/1/2 +haystack/deep/1/2/3 +haystack/deep/1/2/3/4 ... +haystack/shallow +haystack/shallow/needle ``` -- cgit v1.2.3 From 1d5eea47bdf198d6b3266ab0247a2aad3b74adf2 Mon Sep 17 00:00:00 2001 From: Martin Polden Date: Sat, 22 Jul 2023 12:51:35 +0200 Subject: README: Official Homebrew formula An official `bfs` formula has been added to Homebrew now. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 9a914a1..812acaa 100644 --- a/README.md +++ b/README.md @@ -239,8 +239,8 @@ Installation MacPorts # port install bfs -Homebrew -$ brew install tavianator/tap/bfs +Homebrew +$ brew install bfs
-- cgit v1.2.3 From 81dda3027c8e75a3e7988561614d153c888c046e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 24 Jul 2023 12:13:38 -0400 Subject: bit: Add a cast to squelch -Wsign-compare I see this only with musl-gcc for some reason. --- src/bit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bit.h b/src/bit.h index efd7d27..8cde9b3 100644 --- a/src/bit.h +++ b/src/bit.h @@ -241,7 +241,7 @@ static inline uint8_t bswap8(uint8_t n) { #define TRAILING_ZEROS(type, suffix, width) \ static inline int trailing_zeros##suffix(type n) { \ - return n ? UINT_BUILTIN(ctz, suffix)(n) : width; \ + return n ? UINT_BUILTIN(ctz, suffix)(n) : (int)width; \ } #define FIRST_TRAILING_ONE(type, suffix, width) \ -- cgit v1.2.3 From 39baf8a76ddefe3a9f02fc1170b213939ab2d5c2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 7 Aug 2023 18:29:19 -0400 Subject: bfstd: Speed up wordesc() by caching isprint()/isspace() --- src/bfstd.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 49c4a70..9cfd09c 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -5,6 +5,7 @@ #include "bit.h" #include "config.h" #include "diag.h" +#include "thread.h" #include "xregex.h" #include #include @@ -580,16 +581,39 @@ size_t xstrwidth(const char *str) { return ret; } +/** + * Character type flags. + */ +enum ctype { + IS_PRINT = 1 << 0, + IS_SPACE = 1 << 1, +}; + +/** Cached ctypes. */ +static unsigned char ctype_cache[UCHAR_MAX + 1]; + +/** Initialize the ctype cache. */ +static void char_cache_init(void) { + for (size_t c = 0; c <= UCHAR_MAX; ++c) { + if (isprint(c)) { + ctype_cache[c] |= IS_PRINT; + } + if (isspace(c)) { + ctype_cache[c] |= IS_SPACE; + } + } +} + /** Check if a character is printable. */ static bool xisprint(unsigned char c, enum wesc_flags flags) { - if (isprint(c)) { + if (ctype_cache[c] & IS_PRINT) { return true; } // Technically a literal newline is safe inside single quotes, but $'\n' // is much nicer than ' // ' - if (!(flags & WESC_SHELL) && isspace(c)) { + if (!(flags & WESC_SHELL) && (ctype_cache[c] & IS_SPACE)) { return true; } @@ -611,6 +635,9 @@ static bool xiswprint(wchar_t c, enum wesc_flags flags) { /** Get the length of the longest printable prefix of a string. */ static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) { + static pthread_once_t once = PTHREAD_ONCE_INIT; + call_once(&once, char_cache_init); + // Fast path: avoid multibyte checks size_t i; for (i = 0; i < len; ++i) { -- cgit v1.2.3 From 2bc5a379b5f4f6a35465a2df19107209ed191d06 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 7 Aug 2023 19:41:12 -0400 Subject: bfstd: Check multiple chars at once for isascii() --- src/bfstd.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 9cfd09c..a71e4b4 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -639,17 +639,34 @@ static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) call_once(&once, char_cache_init); // Fast path: avoid multibyte checks - size_t i; - for (i = 0; i < len; ++i) { + size_t i, word; + for (i = 0; i + sizeof(word) <= len;) { + // Word-at-a-time isascii() + memcpy(&word, str + i, sizeof(word)); + // 0xFFFF... / 0xFF == 0x10101... + size_t mask = (SIZE_MAX / 0xFF) << 7; + if (word & mask) { + goto multibyte; + } + + for (size_t j = 0; j < sizeof(word); ++i, ++j) { + if (!xisprint(str[i], flags)) { + return i; + } + } + } + + for (; i < len; ++i) { unsigned char c = str[i]; if (!isascii(c)) { - break; + goto multibyte; } if (!xisprint(c, flags)) { return i; } } +multibyte: mbstate_t mb; memset(&mb, 0, sizeof(mb)); -- cgit v1.2.3 From 3438244e02304700fa21f26798df363cfedfd78f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 7 Aug 2023 19:11:18 -0400 Subject: stat: Don't check for statx() mask bits we already checked for --- src/stat.c | 46 +++++++++++++++------------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/src/stat.c b/src/stat.c index 7a413cd..e8f48ee 100644 --- a/src/stat.c +++ b/src/stat.c @@ -162,7 +162,7 @@ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct b } // Callers shouldn't have to check anything except the times - const unsigned int guaranteed = STATX_BASIC_STATS ^ (STATX_ATIME | STATX_CTIME | STATX_MTIME); + const unsigned int guaranteed = STATX_BASIC_STATS & ~(STATX_ATIME | STATX_CTIME | STATX_MTIME); if ((xbuf.stx_mask & guaranteed) != guaranteed) { errno = ENOTSUP; return -1; @@ -173,43 +173,27 @@ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct b buf->dev = xmakedev(xbuf.stx_dev_major, xbuf.stx_dev_minor); buf->mask |= BFS_STAT_DEV; - if (xbuf.stx_mask & STATX_INO) { - buf->ino = xbuf.stx_ino; - buf->mask |= BFS_STAT_INO; - } + buf->ino = xbuf.stx_ino; + buf->mask |= BFS_STAT_INO; buf->mode = xbuf.stx_mode; - if (xbuf.stx_mask & STATX_TYPE) { - buf->mask |= BFS_STAT_TYPE; - } - if (xbuf.stx_mask & STATX_MODE) { - buf->mask |= BFS_STAT_MODE; - } + buf->mask |= BFS_STAT_TYPE; + buf->mask |= BFS_STAT_MODE; - if (xbuf.stx_mask & STATX_NLINK) { - buf->nlink = xbuf.stx_nlink; - buf->mask |= BFS_STAT_NLINK; - } + buf->nlink = xbuf.stx_nlink; + buf->mask |= BFS_STAT_NLINK; - if (xbuf.stx_mask & STATX_GID) { - buf->gid = xbuf.stx_gid; - buf->mask |= BFS_STAT_GID; - } + buf->gid = xbuf.stx_gid; + buf->mask |= BFS_STAT_GID; - if (xbuf.stx_mask & STATX_UID) { - buf->uid = xbuf.stx_uid; - buf->mask |= BFS_STAT_UID; - } + buf->uid = xbuf.stx_uid; + buf->mask |= BFS_STAT_UID; - if (xbuf.stx_mask & STATX_SIZE) { - buf->size = xbuf.stx_size; - buf->mask |= BFS_STAT_SIZE; - } + buf->size = xbuf.stx_size; + buf->mask |= BFS_STAT_SIZE; - if (xbuf.stx_mask & STATX_BLOCKS) { - buf->blocks = xbuf.stx_blocks; - buf->mask |= BFS_STAT_BLOCKS; - } + buf->blocks = xbuf.stx_blocks; + buf->mask |= BFS_STAT_BLOCKS; buf->rdev = xmakedev(xbuf.stx_rdev_major, xbuf.stx_rdev_minor); buf->mask |= BFS_STAT_RDEV; -- cgit v1.2.3 From a63078374d83a00e8f2bafb9dbc27c3c3d362971 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 8 Aug 2023 15:11:45 -0400 Subject: bfstd: Don't label a declaration --- src/bfstd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bfstd.c b/src/bfstd.c index a71e4b4..e546a47 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -666,8 +666,8 @@ static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) } } -multibyte: mbstate_t mb; +multibyte: memset(&mb, 0, sizeof(mb)); while (i < len) { -- cgit v1.2.3 From 92f0c513ba4e9119c5b6977366ce997151a44977 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 14 Aug 2023 15:59:04 -0400 Subject: parse: Allow -files0-from an empty set of paths This follows a behaviour change in GNU findutils 4.9.0. --- src/parse.c | 13 +++---------- tests/gnu/files0_from_none.sh | 2 +- 2 files changed, 4 insertions(+), 11 deletions(-) diff --git a/src/parse.c b/src/parse.c index 6b1eaa0..37940de 100644 --- a/src/parse.c +++ b/src/parse.c @@ -194,8 +194,6 @@ struct parser_state { char **mount_arg; /** An "-xdev" argument, if any. */ char **xdev_arg; - /** A "-files0-from" argument, if any. */ - char **files0_arg; /** A "-files0-from -" argument, if any. */ char **files0_stdin_arg; /** An "-ok"-type expression, if any. */ @@ -1318,7 +1316,6 @@ static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, return NULL; } - state->files0_arg = expr->argv; const char *from = expr->argv[1]; FILE *file; @@ -1350,7 +1347,7 @@ static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, } if (file == stdin) { - state->files0_stdin_arg = state->files0_arg; + state->files0_stdin_arg = expr->argv; } else { fclose(file); } @@ -3720,7 +3717,6 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .prune_arg = NULL, .mount_arg = NULL, .xdev_arg = NULL, - .files0_arg = NULL, .files0_stdin_arg = NULL, .ok_expr = NULL, .now = ctx->now, @@ -3748,11 +3744,8 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { goto fail; } - if (darray_length(ctx->paths) == 0) { - if (!state.implicit_root) { - parse_argv_error(&state, state.files0_arg, 2, "No root paths specified.\n"); - goto fail; - } else if (parse_root(&state, ".") != 0) { + if (darray_length(ctx->paths) == 0 && state.implicit_root) { + if (parse_root(&state, ".") != 0) { goto fail; } } diff --git a/tests/gnu/files0_from_none.sh b/tests/gnu/files0_from_none.sh index 090fce0..1633163 100644 --- a/tests/gnu/files0_from_none.sh +++ b/tests/gnu/files0_from_none.sh @@ -1 +1 @@ -! printf "" | invoke_bfs -files0-from - +printf "" | bfs_diff -files0-from - -- cgit v1.2.3 From b327e3813162e44e53805c307672dc8dc69eeb06 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 14 Aug 2023 16:05:35 -0400 Subject: tests: Add missing .out file --- tests/gnu/files0_from_none.out | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/gnu/files0_from_none.out diff --git a/tests/gnu/files0_from_none.out b/tests/gnu/files0_from_none.out new file mode 100644 index 0000000..e69de29 -- cgit v1.2.3 From 7327b361acfbfc97657416c4bffc5aef79b86542 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:19:59 -0400 Subject: docs/bfs.1: Make bold/italic formatting more consistent --- docs/bfs.1 | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index bc82457..249fcae 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -90,7 +90,7 @@ Follow all symbolic links. Never follow symbolic links (the default). .TP .B \-E -Use extended regular expressions (same as \fB\-regextype posix-extended\fR). +Use extended regular expressions (same as \fB\-regextype \fIposix-extended\fR). .TP .B \-X Filter out files with @@ -432,9 +432,9 @@ Find files the current user can execute/read/write. Always false/true. .RE .TP -.B \-fstype TYPE +\fB\-fstype \fITYPE\fR Find files on file systems with the given -.BR TYPE . +.IR TYPE . .PP \fB\-gid\fR [\fI\-+\fR]\fIN\fR .br @@ -632,7 +632,9 @@ but run the command in the same directory as the found file(s). .RE .TP \fB\-exit\fR [\fISTATUS\fR] -Exit immediately with the given status (0 if unspecified). +Exit immediately with the given status +.RI ( 0 +if unspecified). .PP \fB\-fls \fIFILE\fR .br -- cgit v1.2.3 From 0651f7d74391e4acce498a9c8f0e071355d5940f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:21:47 -0400 Subject: docs/bfs.1: Give an example with a space separating date from time --- docs/bfs.1 | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index 249fcae..00005c0 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -512,13 +512,12 @@ to parse as an ISO 8601-style timestamp. For example: .PP .RS -1991-12-14 -.br -1991-12-14T03:00 -.br -1991-12-14T03:00-07:00 -.br -1991-12-14T10:00Z +.nf +\(bu \fI1991-12-14\fR +\(bu \fI1991-12-14T03:00\fR +\(bu \fI1991-12-14T03:00-07:00\fR +\(bu '\fI1991-12-14 10:00Z\fR' +.fi .RE .PP .B \-nogroup -- cgit v1.2.3 From 9b767578944283ab858b62d36073853cf942a382 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:25:42 -0400 Subject: docs/bfs.1: Use a bulleted list for -size units --- docs/bfs.1 | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index 00005c0..6e10f4d 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -554,14 +554,21 @@ See for examples of the timestamp format. .TP \fB\-size\fR [\fI\-+\fR]\fIN\fR[\fIcwbkMGTP\fR] -Find files with the given size, in 1-byte -.IR c haracters, -2-byte -.IR w ords, -512-byte -.IR b locks -(default), or -.IR k iB/ M iB/ G iB/ T iB/ P iB. +Find files with the given size. +The unit can be one of +.PP +.RS +.nf +\(bu \fIc\fRhars (1 byte) +\(bu \fIw\fRords (2 bytes) +\(bu \fIb\fRlocks (512 bytes, the default) +\(bu \fIk\fRiB (1024 bytes) +\(bu \fIM\fRiB (1024 kiB) +\(bu \fIG\fRiB (1024 MiB) +\(bu \fIT\fRiB (1024 GiB) +\(bu \fIP\fRiB (1024 TiB) +.fi +.RE .TP .B \-sparse Find files that occupy fewer disk blocks than expected. -- cgit v1.2.3 From 19f16a31e1a1bd4ccdbbaa59c719f9ba11daf813 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:26:23 -0400 Subject: docs/bfs.1: Document the -type d,f syntax --- docs/bfs.1 | 51 ++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 38 insertions(+), 13 deletions(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index 6e10f4d..f4edd5b 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -575,21 +575,46 @@ Find files that occupy fewer disk blocks than expected. .TP \fB\-type\fR [\fIbcdlpfswD\fR] Find files of the given type. -Possible types are +The possible types are +.PP +.RS +\(bu .IR b lock -device, +device +.br +\(bu .IR c haracter -device, -.IR d irectory, -symbolic -.IR l ink, -.IR p ipe, -regular -.IR f ile, -.IR s ocket, -.IR w hiteout, -and -.IR D oor. +device +.br +\(bu +.IR d irectory +.br +\(bu +.IR l ink +(symbolic) +.br +\(bu +.IR p ipe +.br +\(bu +.IR f ile +(regular) +.br +\(bu +.IR s ocket +.br +\(bu +.IR w hiteout +.br +\(bu +.IR D oor +.PP +Multiple types can be given at once, separated by commas. +For example, +.B \-type +.I d,f +matches both directories and regular files. +.RE .TP \fB\-used\fR [\fI\-+\fR]\fIN\fR Find files last accessed -- cgit v1.2.3 From d8aa899bd9770296ef63b09c3161bedaf25e5207 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:27:01 -0400 Subject: docs/bfs.1: Document that -prune does not work together with -depth --- docs/bfs.1 | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/bfs.1 b/docs/bfs.1 index f4edd5b..7e8a3f3 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -728,6 +728,13 @@ instead. .TP .B \-prune Don't descend into this directory. +This has no effect if +.B \-depth +is enabled (either explicitly, or implicitly by +.BR \-delete ). +Use +.B \-exclude +instead in that case. .TP .B \-quit Quit immediately. -- cgit v1.2.3 From 83a1fab7371f59df7af4175d731590305fb149a1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:28:10 -0400 Subject: docs/bfs.1: Document use of $PATH --- docs/bfs.1 | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/bfs.1 b/docs/bfs.1 index 7e8a3f3..1ffebf4 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -794,8 +794,18 @@ Specifies the pager used for .B \-help output. Defaults to +.BR less (1), +if found on the current +.BR PATH , +otherwise .BR more (1). .TP +.B PATH +Used to resolve executables for +.BR \-exec [ dir ] +and +.BR \-ok [ dir ]. +.TP .B POSIXLY_CORRECT Makes .B bfs -- cgit v1.2.3 From d49e44a5e55d834a609753b113d6055a056c4091 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:30:17 -0400 Subject: docs/bfs.1: Clarify the effects of $POSIXLY_CORRECT --- docs/bfs.1 | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index 1ffebf4..f817c0a 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -811,10 +811,31 @@ Makes .B bfs conform more strictly to the POSIX.1-2017 specification for .BR find (1). -Currently this just disables warnings by default. +Currently this has two effects: +.RS +.IP \(bu +Disables warnings by default, because POSIX prohibits writing to standard error (except for the +.B \-ok +prompt), unless the command also fails with a non-zero exit status. +.IP \(bu +Makes +.B \-ls +and +.B \-fls +use 512-byte blocks instead of 1024-byte blocks. +(POSIX does not specify these actions, but BSD +.BR find (1) +implementations use 512-byte blocks, while GNU +.BR find (1) +uses 1024-byte blocks by default.) +.PP It does not disable .BR bfs 's various extensions to the base POSIX functionality. +.B POSIXLY_CORRECT +has the same effects on GNU +.BR find (1). +.RE .SH EXAMPLES .TP .B bfs -- cgit v1.2.3 From 5ee67902d7c635ac7df4b4e6de3c4af3b93a4b89 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:30:41 -0400 Subject: docs/bfs.1: Don't unnecessarily quote {} --- docs/bfs.1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index f817c0a..49778bd 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -872,7 +872,7 @@ skipping every .B .git directory. .TP -.B bfs \-type f \-executable \-exec strip '{}' + +.B bfs \-type f \-executable \-exec strip {} + Runs .BR strip (1) on all executable files it finds, passing it multiple files at a time. -- cgit v1.2.3 From 97edb42d2b4e5b15e783d0bb43f2024df0f93888 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 14:35:55 -0400 Subject: docs/bfs.1: Document that -help/-version exit immediately --- docs/bfs.1 | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index 49778bd..c7a4373 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -251,6 +251,20 @@ or .B \-mindepth for example. Exclusions are always applied before other expressions, so it may be least confusing to put them first on the command line. +.PP +.B \-help +.br +.B \-\-help +.RS +Print usage information, and exit immediately (without parsing the rest of the command line or processing any files). +.RE +.PP +.B \-version +.br +.B \-\-version +.RS +Print version information, and exit immediately. +.RE .SH OPTIONS .PP .B \-color @@ -738,12 +752,6 @@ instead in that case. .TP .B \-quit Quit immediately. -.TP -.B \-version -Print version information. -.TP -.B \-help -Print usage information. .SH ENVIRONMENT Certain environment variables affect the behavior of .BR bfs . -- cgit v1.2.3 From 15ce658253f13f356d51b746938b19c9ffe1f33d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 15 Aug 2023 15:57:02 -0400 Subject: docs/bfs.1: Document each -regextype --- docs/bfs.1 | 39 ++++++++++++++++++++++++++++++++++----- 1 file changed, 34 insertions(+), 5 deletions(-) diff --git a/docs/bfs.1 b/docs/bfs.1 index c7a4373..2ecb891 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -329,11 +329,40 @@ Ignored; for compatibility with GNU find. \fB\-regextype \fITYPE\fR Use .IR TYPE -flavored -regexes (default: -.IR posix-basic ; -see -.B \-regextype -.IR help ). +regular expressions. +The possible types are +.RS +.TP +.I posix-basic +POSIX basic regular expressions (the default). +.TP +.I posix-extended +POSIX extended resular expressions. +.TP +.I ed +Like +.BR ed (1) +(same as +.IR posix-basic ). +.TP +.I emacs +Like +.BR emacs (1). +.TP +.I grep +Like +.BR grep (1). +.TP +.I sed +Like +.BR sed (1) +(same as +.IR posix-basic ). +.PP +See +.BR regex (7) +for a description of regular expression syntax. +.RE .TP .B \-status Display a status bar while searching. -- cgit v1.2.3 From 43a59c487209293ee48b8d1f0215ceb643af8ad3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 8 Aug 2023 16:03:11 -0400 Subject: ioq: New ioq_slot_monitor() helper --- src/ioq.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index a394e07..f7ca8c6 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -146,10 +146,15 @@ static struct ioqq *ioqq_create(size_t size) { return ioqq; } +/** Get the monitor associated with a slot. */ +static struct ioq_monitor *ioq_slot_monitor(struct ioqq *ioqq, ioq_slot *slot) { + size_t i = slot - ioqq->slots; + return &ioqq->monitors[i & ioqq->monitor_mask]; +} + /** Atomically wait for a slot to change. */ static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) { - size_t i = slot - ioqq->slots; - struct ioq_monitor *monitor = &ioqq->monitors[i & ioqq->monitor_mask]; + struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); mutex_lock(&monitor->mutex); uintptr_t ret = load(slot, relaxed); @@ -178,8 +183,7 @@ done: /** Wake up any threads waiting on a slot. */ static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { - size_t i = slot - ioqq->slots; - struct ioq_monitor *monitor = &ioqq->monitors[i & ioqq->monitor_mask]; + struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); // The following implementation would clearly avoid the missed wakeup // issue mentioned above in ioq_slot_wait(): -- cgit v1.2.3 From f34baf1a2f42259a4873f6d9733078003184d4e4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 23 Aug 2023 23:19:55 -0400 Subject: Work around https://github.com/llvm/llvm-project/issues/64946 --- src/parse.c | 3 +++ tests/xtouch.c | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/parse.c b/src/parse.c index 37940de..3861610 100644 --- a/src/parse.c +++ b/src/parse.c @@ -582,6 +582,9 @@ static const char *parse_int(const struct parser_state *state, char **arg, const } } + // https://github.com/llvm/llvm-project/issues/64946 + sanitize_init(&endptr); + if (endptr == str) { goto bad; } diff --git a/tests/xtouch.c b/tests/xtouch.c index 50416ba..4a02bf3 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -3,6 +3,7 @@ #include "../src/bfstd.h" #include "../src/config.h" +#include "../src/sanity.h" #include "../src/xtime.h" #include #include @@ -172,6 +173,8 @@ int main(int argc, char *argv[]) { if (marg) { char *end; long mode = strtol(marg, &end, 8); + // https://github.com/llvm/llvm-project/issues/64946 + sanitize_init(&end); if (*marg && !*end && mode >= 0 && mode < 01000) { args.fmode = args.dmode = mode; } else { -- cgit v1.2.3 From 5f736eada55f84c7499103faf7c181b80eb7092f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Aug 2023 10:16:15 -0400 Subject: alloc: New [v]arena_clear() functions --- src/alloc.c | 16 +++++++++++++++- src/alloc.h | 10 ++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/alloc.c b/src/alloc.c index 56d8763..8a4dc3a 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -163,11 +163,19 @@ void arena_free(struct arena *arena, void *ptr) { sanitize_free(chunk, arena->size); } -void arena_destroy(struct arena *arena) { +void arena_clear(struct arena *arena) { for (size_t i = 0; i < arena->nslabs; ++i) { free(arena->slabs[i]); } free(arena->slabs); + + arena->chunks = NULL; + arena->nslabs = 0; + arena->slabs = NULL; +} + +void arena_destroy(struct arena *arena) { + arena_clear(arena); sanitize_uninit(arena); } @@ -280,6 +288,12 @@ void varena_free(struct varena *varena, void *ptr, size_t count) { arena_free(arena, ptr); } +void varena_clear(struct varena *varena) { + for (size_t i = 0; i < varena->narenas; ++i) { + arena_clear(&varena->arenas[i]); + } +} + void varena_destroy(struct varena *varena) { for (size_t i = 0; i < varena->narenas; ++i) { arena_destroy(&varena->arenas[i]); diff --git a/src/alloc.h b/src/alloc.h index c2ea09b..b5dfa68 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -185,6 +185,11 @@ void *arena_alloc(struct arena *arena); */ void arena_free(struct arena *arena, void *ptr); +/** + * Free all allocations from an arena. + */ +void arena_clear(struct arena *arena); + /** * Destroy an arena, freeing all allocations. */ @@ -277,6 +282,11 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t */ void varena_free(struct varena *varena, void *ptr, size_t count); +/** + * Free all allocations from a varena. + */ +void varena_clear(struct varena *varena); + /** * Destroy a varena, freeing all allocations. */ -- cgit v1.2.3 From 345047be5b8766a9763d4a717ff034e4e4083ade Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Aug 2023 10:16:35 -0400 Subject: trie: New trie_clear() function --- src/color.c | 3 +-- src/trie.c | 8 ++++++++ src/trie.h | 5 +++++ 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/color.c b/src/color.c index f7a5d86..b9a788b 100644 --- a/src/color.c +++ b/src/color.c @@ -326,8 +326,7 @@ fail: /** Rebuild the case-insensitive trie after all extensions have been parsed. */ static int build_iext_trie(struct colors *colors) { - trie_destroy(&colors->iext_trie); - trie_init(&colors->iext_trie); + trie_clear(&colors->iext_trie); TRIE_FOR_EACH(&colors->ext_trie, leaf) { size_t len = leaf->length - 1; diff --git a/src/trie.c b/src/trie.c index 0206509..77aa2d0 100644 --- a/src/trie.c +++ b/src/trie.c @@ -715,6 +715,14 @@ void trie_remove(struct trie *trie, struct trie_leaf *leaf) { trie_remove_impl(trie, leaf); } +void trie_clear(struct trie *trie) { + trie->root = 0; + LIST_INIT(trie); + + varena_clear(&trie->leaves); + varena_clear(&trie->nodes); +} + void trie_destroy(struct trie *trie) { varena_destroy(&trie->leaves); varena_destroy(&trie->nodes); diff --git a/src/trie.h b/src/trie.h index 6921f62..dfaae15 100644 --- a/src/trie.h +++ b/src/trie.h @@ -128,6 +128,11 @@ struct trie_leaf *trie_insert_mem(struct trie *trie, const void *key, size_t len */ void trie_remove(struct trie *trie, struct trie_leaf *leaf); +/** + * Remove all leaves from a trie. + */ +void trie_clear(struct trie *trie); + /** * Destroy a trie and its contents. */ -- cgit v1.2.3 From 1bb2b6ccfc11d4aec61287441379cb6584b8fb5f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Aug 2023 10:42:42 -0400 Subject: alloc: New varena_grow() function --- src/alloc.c | 15 +++++++++++++++ src/alloc.h | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/src/alloc.c b/src/alloc.c index 8a4dc3a..0b978ba 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -283,6 +283,21 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t return ret; } +void *varena_grow(struct varena *varena, void *ptr, size_t *count) { + size_t old_count = *count; + + // Round up to the limit of the current size class. If we're already at + // the limit, go to the next size class. + size_t new_shift = varena_size_class(varena, old_count + 1) + varena->shift; + size_t new_count = (size_t)1 << new_shift; + + ptr = varena_realloc(varena, ptr, old_count, new_count); + if (ptr) { + *count = new_count; + } + return ptr; +} + void varena_free(struct varena *varena, void *ptr, size_t count) { struct arena *arena = varena_get(varena, count); arena_free(arena, ptr); diff --git a/src/alloc.h b/src/alloc.h index b5dfa68..5f0c423 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -270,6 +270,20 @@ void *varena_alloc(struct varena *varena, size_t count); */ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count); +/** + * Grow a flexible struct by an arbitrary amount. + * + * @param varena + * The varena to allocate from. + * @param ptr + * The object to resize. + * @param count + * Pointer to the flexible array length. + * @return + * The resized struct, or NULL on failure. + */ +void *varena_grow(struct varena *varena, void *ptr, size_t *count); + /** * Free an arena-allocated flexible struct. * -- cgit v1.2.3 From bb1cc1422b7acde887cd11c131146dafddc382dc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Aug 2023 10:55:39 -0400 Subject: pwcache: Arena-allocate struct passwd/group --- src/pwcache.c | 134 +++++++++++++++++++++++++++++++++++----------------------- 1 file changed, 80 insertions(+), 54 deletions(-) diff --git a/src/pwcache.c b/src/pwcache.c index 9f32eb0..a9acfc0 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -17,54 +17,56 @@ static void *MISSING = &MISSING; /** Callback type for bfs_getent(). */ -typedef void *bfs_getent_fn(const void *key, void *ent, void *buf, size_t bufsize); +typedef void *bfs_getent_fn(const void *key, void *ptr, size_t bufsize); /** Shared scaffolding for get{pw,gr}{nam,?id}_r(). */ -static void *bfs_getent(struct trie_leaf *leaf, bfs_getent_fn *fn, const void *key, size_t entsize, size_t bufsize) { +static void *bfs_getent(bfs_getent_fn *fn, const void *key, struct trie_leaf *leaf, struct varena *varena, size_t bufsize) { if (leaf->value) { errno = 0; return leaf->value == MISSING ? NULL : leaf->value; } - void *buf = NULL; - while (true) { - void *result = buf; - buf = realloc(buf, entsize + bufsize); - if (!buf) { - free(result); - return NULL; - } + void *ptr = varena_alloc(varena, bufsize); + if (!ptr) { + return NULL; + } - result = fn(key, buf, (char *)buf + entsize, bufsize); - if (result) { - leaf->value = result; - return result; + while (true) { + void *ret = fn(key, ptr, bufsize); + if (ret) { + leaf->value = ret; + return ret; } else if (errno == 0) { - free(buf); leaf->value = MISSING; - return NULL; + break; } else if (errno == ERANGE) { - bufsize *= 2; + void *next = varena_grow(varena, ptr, &bufsize); + if (!next) { + break; + } + ptr = next; } else { - free(buf); - return NULL; + break; } } -} -/** Flush a single cache. */ -static void bfs_pwcache_flush(struct trie *trie) { - TRIE_FOR_EACH(trie, leaf) { - if (leaf->value != MISSING) { - free(leaf->value); - } - trie_remove(trie, leaf); - } + varena_free(varena, ptr, bufsize); + return NULL; } +/** + * An arena-allocated struct passwd. + */ +struct bfs_passwd { + struct passwd pwd; + char buf[]; +}; + struct bfs_users { /** Initial buffer size for getpw*_r(). */ size_t bufsize; + /** bfs_passwd arena. */ + struct varena varena; /** A map from usernames to entries. */ struct trie by_name; /** A map from UIDs to entries. */ @@ -84,16 +86,19 @@ struct bfs_users *bfs_users_new(void) { users->bufsize = 1024; } + VARENA_INIT(&users->varena, struct bfs_passwd, buf); trie_init(&users->by_name); trie_init(&users->by_uid); return users; } /** bfs_getent() callback for getpwnam_r(). */ -static void *bfs_getpwnam_impl(const void *key, void *ent, void *buf, size_t bufsize) { - struct passwd *result; - errno = getpwnam_r(key, ent, buf, bufsize, &result); - return result; +static void *bfs_getpwnam_impl(const void *key, void *ptr, size_t bufsize) { + struct bfs_passwd *storage = ptr; + + struct passwd *ret; + errno = getpwnam_r(key, &storage->pwd, storage->buf, bufsize, &ret); + return ret; } const struct passwd *bfs_getpwnam(struct bfs_users *users, const char *name) { @@ -102,14 +107,17 @@ const struct passwd *bfs_getpwnam(struct bfs_users *users, const char *name) { return NULL; } - return bfs_getent(leaf, bfs_getpwnam_impl, name, sizeof(struct passwd), users->bufsize); + return bfs_getent(bfs_getpwnam_impl, name, leaf, &users->varena, users->bufsize); } /** bfs_getent() callback for getpwuid_r(). */ -static void *bfs_getpwuid_impl(const void *key, void *ent, void *buf, size_t bufsize) { - struct passwd *result; - errno = getpwuid_r(*(const uid_t *)key, ent, buf, bufsize, &result); - return result; +static void *bfs_getpwuid_impl(const void *key, void *ptr, size_t bufsize) { + const uid_t *uid = key; + struct bfs_passwd *storage = ptr; + + struct passwd *ret; + errno = getpwuid_r(*uid, &storage->pwd, storage->buf, bufsize, &ret); + return ret; } const struct passwd *bfs_getpwuid(struct bfs_users *users, uid_t uid) { @@ -118,26 +126,37 @@ const struct passwd *bfs_getpwuid(struct bfs_users *users, uid_t uid) { return NULL; } - return bfs_getent(leaf, bfs_getpwuid_impl, &uid, sizeof(struct passwd), users->bufsize); + return bfs_getent(bfs_getpwuid_impl, &uid, leaf, &users->varena, users->bufsize); } void bfs_users_flush(struct bfs_users *users) { - bfs_pwcache_flush(&users->by_name); - bfs_pwcache_flush(&users->by_uid); + trie_clear(&users->by_uid); + trie_clear(&users->by_name); + varena_clear(&users->varena); } void bfs_users_free(struct bfs_users *users) { if (users) { - bfs_users_flush(users); trie_destroy(&users->by_uid); trie_destroy(&users->by_name); + varena_destroy(&users->varena); free(users); } } +/** + * An arena-allocated struct group. + */ +struct bfs_group { + struct group grp; + char buf[]; +}; + struct bfs_groups { /** Initial buffer size for getgr*_r(). */ size_t bufsize; + /** bfs_group arena. */ + struct varena varena; /** A map from group names to entries. */ struct trie by_name; /** A map from GIDs to entries. */ @@ -157,16 +176,19 @@ struct bfs_groups *bfs_groups_new(void) { groups->bufsize = 1024; } + VARENA_INIT(&groups->varena, struct bfs_group, buf); trie_init(&groups->by_name); trie_init(&groups->by_gid); return groups; } /** bfs_getent() callback for getgrnam_r(). */ -static void *bfs_getgrnam_impl(const void *key, void *ent, void *buf, size_t bufsize) { - struct group *result; - errno = getgrnam_r(key, ent, buf, bufsize, &result); - return result; +static void *bfs_getgrnam_impl(const void *key, void *ptr, size_t bufsize) { + struct bfs_group *storage = ptr; + + struct group *ret; + errno = getgrnam_r(key, &storage->grp, storage->buf, bufsize, &ret); + return ret; } const struct group *bfs_getgrnam(struct bfs_groups *groups, const char *name) { @@ -175,14 +197,17 @@ const struct group *bfs_getgrnam(struct bfs_groups *groups, const char *name) { return NULL; } - return bfs_getent(leaf, bfs_getgrnam_impl, name, sizeof(struct group), groups->bufsize); + return bfs_getent(bfs_getgrnam_impl, name, leaf, &groups->varena, groups->bufsize); } /** bfs_getent() callback for getgrgid_r(). */ -static void *bfs_getgrgid_impl(const void *key, void *ent, void *buf, size_t bufsize) { - struct group *result; - errno = getgrgid_r(*(const gid_t *)key, ent, buf, bufsize, &result); - return result; +static void *bfs_getgrgid_impl(const void *key, void *ptr, size_t bufsize) { + const gid_t *gid = key; + struct bfs_group *storage = ptr; + + struct group *ret; + errno = getgrgid_r(*gid, &storage->grp, storage->buf, bufsize, &ret); + return ret; } const struct group *bfs_getgrgid(struct bfs_groups *groups, gid_t gid) { @@ -191,19 +216,20 @@ const struct group *bfs_getgrgid(struct bfs_groups *groups, gid_t gid) { return NULL; } - return bfs_getent(leaf, bfs_getgrgid_impl, &gid, sizeof(struct group), groups->bufsize); + return bfs_getent(bfs_getgrgid_impl, &gid, leaf, &groups->varena, groups->bufsize); } void bfs_groups_flush(struct bfs_groups *groups) { - bfs_pwcache_flush(&groups->by_name); - bfs_pwcache_flush(&groups->by_gid); + trie_clear(&groups->by_gid); + trie_clear(&groups->by_name); + varena_clear(&groups->varena); } void bfs_groups_free(struct bfs_groups *groups) { if (groups) { - bfs_groups_flush(groups); trie_destroy(&groups->by_gid); trie_destroy(&groups->by_name); + varena_destroy(&groups->varena); free(groups); } } -- cgit v1.2.3 From db77001f27575a1c2e8723b1257a91423867171d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 31 Aug 2023 11:07:38 -0400 Subject: pwcache: Don't use _SC_GET{PW,GR}_R_SIZE_MAX They tend be 1024, which is a lot of memory per user/group. 128 is usually enough, so start there instead. --- src/pwcache.c | 32 +++++++++----------------------- 1 file changed, 9 insertions(+), 23 deletions(-) diff --git a/src/pwcache.c b/src/pwcache.c index a9acfc0..0e2f5c1 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -20,12 +20,16 @@ static void *MISSING = &MISSING; typedef void *bfs_getent_fn(const void *key, void *ptr, size_t bufsize); /** Shared scaffolding for get{pw,gr}{nam,?id}_r(). */ -static void *bfs_getent(bfs_getent_fn *fn, const void *key, struct trie_leaf *leaf, struct varena *varena, size_t bufsize) { +static void *bfs_getent(bfs_getent_fn *fn, const void *key, struct trie_leaf *leaf, struct varena *varena) { if (leaf->value) { errno = 0; return leaf->value == MISSING ? NULL : leaf->value; } + // _SC_GET{PW,GR}_R_SIZE_MAX tend to be fairly large (~1K). That's okay + // for temporary allocations, but for these long-lived ones, let's start + // with a smaller buffer. + size_t bufsize = 128; void *ptr = varena_alloc(varena, bufsize); if (!ptr) { return NULL; @@ -63,8 +67,6 @@ struct bfs_passwd { }; struct bfs_users { - /** Initial buffer size for getpw*_r(). */ - size_t bufsize; /** bfs_passwd arena. */ struct varena varena; /** A map from usernames to entries. */ @@ -79,13 +81,6 @@ struct bfs_users *bfs_users_new(void) { return NULL; } - long bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); - if (bufsize > 0) { - users->bufsize = bufsize; - } else { - users->bufsize = 1024; - } - VARENA_INIT(&users->varena, struct bfs_passwd, buf); trie_init(&users->by_name); trie_init(&users->by_uid); @@ -107,7 +102,7 @@ const struct passwd *bfs_getpwnam(struct bfs_users *users, const char *name) { return NULL; } - return bfs_getent(bfs_getpwnam_impl, name, leaf, &users->varena, users->bufsize); + return bfs_getent(bfs_getpwnam_impl, name, leaf, &users->varena); } /** bfs_getent() callback for getpwuid_r(). */ @@ -126,7 +121,7 @@ const struct passwd *bfs_getpwuid(struct bfs_users *users, uid_t uid) { return NULL; } - return bfs_getent(bfs_getpwuid_impl, &uid, leaf, &users->varena, users->bufsize); + return bfs_getent(bfs_getpwuid_impl, &uid, leaf, &users->varena); } void bfs_users_flush(struct bfs_users *users) { @@ -153,8 +148,6 @@ struct bfs_group { }; struct bfs_groups { - /** Initial buffer size for getgr*_r(). */ - size_t bufsize; /** bfs_group arena. */ struct varena varena; /** A map from group names to entries. */ @@ -169,13 +162,6 @@ struct bfs_groups *bfs_groups_new(void) { return NULL; } - long bufsize = sysconf(_SC_GETGR_R_SIZE_MAX); - if (bufsize > 0) { - groups->bufsize = bufsize; - } else { - groups->bufsize = 1024; - } - VARENA_INIT(&groups->varena, struct bfs_group, buf); trie_init(&groups->by_name); trie_init(&groups->by_gid); @@ -197,7 +183,7 @@ const struct group *bfs_getgrnam(struct bfs_groups *groups, const char *name) { return NULL; } - return bfs_getent(bfs_getgrnam_impl, name, leaf, &groups->varena, groups->bufsize); + return bfs_getent(bfs_getgrnam_impl, name, leaf, &groups->varena); } /** bfs_getent() callback for getgrgid_r(). */ @@ -216,7 +202,7 @@ const struct group *bfs_getgrgid(struct bfs_groups *groups, gid_t gid) { return NULL; } - return bfs_getent(bfs_getgrgid_impl, &gid, leaf, &groups->varena, groups->bufsize); + return bfs_getent(bfs_getgrgid_impl, &gid, leaf, &groups->varena); } void bfs_groups_flush(struct bfs_groups *groups) { -- cgit v1.2.3 From b8a3b9aaf37fc62079b4e9d921a6007845d3ef03 Mon Sep 17 00:00:00 2001 From: Alejandro Lazaro Date: Fri, 1 Sep 2023 17:50:26 +0200 Subject: bfs uses the C17 standard version `bfs` uses C17 since 3.0 --- docs/HACKING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/HACKING.md b/docs/HACKING.md index d129a28..0763fda 100644 --- a/docs/HACKING.md +++ b/docs/HACKING.md @@ -17,7 +17,7 @@ This enables machine processing of license information based on the SPDX License Implementation -------------- -`bfs` is written in [C](https://en.wikipedia.org/wiki/C_(programming_language)), specifically [C11](https://en.wikipedia.org/wiki/C11_(C_standard_revision)). +`bfs` is written in [C](https://en.wikipedia.org/wiki/C_(programming_language)), specifically [C17](https://en.wikipedia.org/wiki/C17_(C_standard_revision)). You can get a feel for the coding style by skimming the source code. [`main.c`](/src/main.c) contains an overview of the rest of source files. A quick summary: -- cgit v1.2.3 From 37dd040e04bd23293b6e46f8f5af22ea07717894 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Sep 2023 09:30:47 -0400 Subject: bftw: Enforce the dirlimit strictly The previous accounting didn't fully control the number of allocated bfs_dirs, as the dirlimit was incremented once we popped the directory, not when we freed it. --- src/bftw.c | 36 +++++++++++++++++------------------- 1 file changed, 17 insertions(+), 19 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 5d28612..810cd8a 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -180,6 +180,8 @@ struct bftw_cache { struct varena files; /** bfs_dir arena. */ struct arena dirs; + /** Remaining bfs_dir capacity. */ + size_t dirlimit; }; /** Initialize a cache. */ @@ -187,17 +189,30 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { LIST_INIT(cache); cache->target = NULL; cache->capacity = capacity; + VARENA_INIT(&cache->files, struct bftw_file, name); bfs_dir_arena(&cache->dirs); + + cache->dirlimit = capacity - 1; + if (cache->dirlimit > 1024) { + cache->dirlimit = 1024; + } } /** Allocate a directory. */ static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache) { + if (cache->dirlimit == 0) { + errno = ENOMEM; + return NULL; + } + --cache->dirlimit; + return arena_alloc(&cache->dirs); } /** Free a directory. */ static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) { + ++cache->dirlimit; arena_free(&cache->dirs, dir); } @@ -410,8 +425,6 @@ struct bftw_state { struct bftw_list to_read; /** The queue of unpinned directories to unwrap. */ struct bftw_list to_close; - /** Available capacity in to_read. */ - size_t dirlimit; /** The queue of files to visit. */ struct bftw_list to_visit; @@ -472,12 +485,6 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg SLIST_INIT(&state->to_read); SLIST_INIT(&state->to_close); - size_t dirlimit = args->nopenfd - 1; - if (dirlimit > 1024) { - dirlimit = 1024; - } - state->dirlimit = dirlimit; - SLIST_INIT(&state->to_visit); SLIST_INIT(&state->batch); @@ -539,7 +546,6 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { bftw_file_set_dir(cache, file, dir); } else { bftw_freedir(cache, dir); - ++state->dirlimit; } if (!(state->flags & BFTW_SORT)) { @@ -767,10 +773,6 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { /** Open a directory asynchronously. */ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { - if (state->dirlimit == 0) { - goto fail; - } - if (bftw_ioq_reserve(state) != 0) { goto fail; } @@ -801,7 +803,6 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { file->ioqueued = true; --cache->capacity; - --state->dirlimit; return 0; free: @@ -839,6 +840,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); + struct bftw_cache *cache = &state->cache; bool have_files = state->to_visit.head; if (state->flags & BFTW_SORT) { @@ -850,7 +852,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { while (!state->to_read.head) { // Block if we have no other files/dirs to visit, or no room in the cache bool have_dirs = state->to_open.head; - bool have_room = state->cache.capacity > 0; + bool have_room = cache->capacity > 0 && cache->dirlimit > 0; bool block = !(have_dirs || have_files) || !have_room; if (bftw_ioq_pop(state, block) < 0) { @@ -867,10 +869,6 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } - if (file->dir) { - ++state->dirlimit; - } - while (file->ioqueued) { bftw_ioq_pop(state, true); } -- cgit v1.2.3 From 377709664480a30fa5acdd11c7ca8c16669678ce Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Sep 2023 14:59:59 -0400 Subject: bfstd: Fix an OOB string index in xmbrtowc() This bug could be reproduced with something like $ bfs -samefile $'\xFA\xFA' bfs: error: bfs: dstrnescat@src/dstring.c:252: wordesc() result truncated or worse, with -DNDEBUG, $ bfs -samefile $'.....................\xFA\xFA' bfs: error: bfs -samefile $'.....................\xFA\xFA\x00\x55\x53\x45\x52\x3D\x74\x61\x76\x69\x61\x6E\x61\x74\x6F\x72 bfs: error: ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bfs: error: No such file or directory. which prints the memory after the end of the string (in this case, the environment variable USER=tavianator). The bug was caused by the line `*i += len`, which was intended to be `*i = len`. But actually, the right behaviour seems to be `*i += 1`. Fixes: 19c96abe0a1ee56cf206fd5e87defb1fd3e0daa5 --- src/bfstd.c | 8 ++------ tests/bfstd.c | 18 ++++++++++++++++++ tests/common/samefile_wordesc.sh | 4 ++++ 3 files changed, 24 insertions(+), 6 deletions(-) create mode 100644 tests/common/samefile_wordesc.sh diff --git a/src/bfstd.c b/src/bfstd.c index e546a47..2d9f60a 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -546,15 +546,11 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * static int xmbrtowc(wchar_t *wc, size_t *i, const char *str, size_t len, mbstate_t *mb) { size_t mblen = mbrtowc(wc, str + *i, len - *i, mb); switch (mblen) { - case -1: - // Invalid byte sequence + case -1: // Invalid byte sequence + case -2: // Incomplete byte sequence *i += 1; memset(mb, 0, sizeof(*mb)); return -1; - case -2: - // Incomplete byte sequence - *i += len; - return -1; default: *i += mblen; return 0; diff --git a/tests/bfstd.c b/tests/bfstd.c index fa854a8..2db084a 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -23,6 +23,15 @@ static void check_base_dir(const char *path, const char *dir, const char *base) free(xbase); } +/** Check the result of wordesc(). */ +static void check_wordesc(const char *str, const char *exp, enum wesc_flags flags) { + char buf[256]; + char *end = buf + sizeof(buf); + char *ret = wordesc(buf, end, str, flags); + bfs_verify(ret != end); + bfs_verify(strcmp(buf, exp) == 0, "wordesc(%s) == %s (!= %s)", str, buf, exp); +} + int main(void) { // From man 3p basename check_base_dir("usr", ".", "usr"); @@ -36,5 +45,14 @@ int main(void) { check_base_dir("//usr//lib//", "//usr", "lib"); check_base_dir("/home//dwc//test", "/home//dwc", "test"); + check_wordesc("", "\"\"", WESC_SHELL); + check_wordesc("word", "word", WESC_SHELL); + check_wordesc("two words", "\"two words\"", WESC_SHELL); + check_wordesc("word's", "\"word's\"", WESC_SHELL); + check_wordesc("\"word\"", "'\"word\"'", WESC_SHELL); + check_wordesc("\"word's\"", "'\"word'\\''s\"'", WESC_SHELL); + check_wordesc("\033[1mbold's\033[0m", "$'\\e[1mbold\\'s\\e[0m'", WESC_SHELL | WESC_TTY); + check_wordesc("\x7F", "$'\\x7F'", WESC_SHELL | WESC_TTY); + return EXIT_SUCCESS; } diff --git a/tests/common/samefile_wordesc.sh b/tests/common/samefile_wordesc.sh new file mode 100644 index 0000000..b5d158f --- /dev/null +++ b/tests/common/samefile_wordesc.sh @@ -0,0 +1,4 @@ +# Regression test: don't abort on incomplete UTF-8 sequences +export LC_ALL=$(locale -a | grep -Ei 'utf-?8$' | head -n1) +test -n "$LC_ALL" || skip +! invoke_bfs -samefile $'\xFA\xFA' -- cgit v1.2.3 From 90f9205b40b2f2049df46d819d14d67bfcb055be Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Sep 2023 16:23:10 -0400 Subject: bfstd: Fix printable_len() off-by-one If xmbrtowc() fails, or if xiswprint() is false, then we shouldn't include that wide char in the printable length. Fixes: 19c96abe0a1ee56cf206fd5e87defb1fd3e0daa5 --- src/bfstd.c | 4 ++-- tests/bfstd.c | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 2d9f60a..61d2fee 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -666,9 +666,9 @@ static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) multibyte: memset(&mb, 0, sizeof(mb)); - while (i < len) { + for (size_t j = i; i < len; i = j) { wchar_t wc; - if (xmbrtowc(&wc, &i, str, len, &mb) != 0) { + if (xmbrtowc(&wc, &j, str, len, &mb) != 0) { break; } if (!xiswprint(wc, flags)) { diff --git a/tests/bfstd.c b/tests/bfstd.c index 2db084a..83964e5 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -5,6 +5,8 @@ #include "../src/config.h" #include "../src/diag.h" #include +#include +#include #include #include #include @@ -33,6 +35,11 @@ static void check_wordesc(const char *str, const char *exp, enum wesc_flags flag } int main(void) { + // Try to set a UTF-8 locale + if (!setlocale(LC_ALL, "C.UTF-8")) { + setlocale(LC_ALL, ""); + } + // From man 3p basename check_base_dir("usr", ".", "usr"); check_base_dir("usr/", ".", "usr"); @@ -54,5 +61,13 @@ int main(void) { check_wordesc("\033[1mbold's\033[0m", "$'\\e[1mbold\\'s\\e[0m'", WESC_SHELL | WESC_TTY); check_wordesc("\x7F", "$'\\x7F'", WESC_SHELL | WESC_TTY); + const char *charmap = nl_langinfo(CODESET); + if (strcmp(charmap, "UTF-8") == 0) { + check_wordesc("\xF0", "$'\\xF0'", WESC_SHELL | WESC_TTY); + check_wordesc("\xF0\x9F", "$'\\xF0\\x9F'", WESC_SHELL | WESC_TTY); + check_wordesc("\xF0\x9F\x98", "$'\\xF0\\x9F\\x98'", WESC_SHELL | WESC_TTY); + check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY); + } + return EXIT_SUCCESS; } -- cgit v1.2.3 From 4f9762218079c4d83eb30065804367506da8330d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Sep 2023 16:28:12 -0400 Subject: bfstd: Skip a whole loop in dollar_quote() if possible --- src/bfstd.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 61d2fee..c858910 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -724,10 +724,9 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, en safe = xiswprint(wc, flags); } - for (size_t j = start; j < i; ++j) { + for (size_t j = start; safe && j < i; ++j) { if (str[j] == '\'' || str[j] == '\\') { safe = false; - break; } } -- cgit v1.2.3 From dc0443270904bf623d5b4a8e7b9ed8467eb9c93c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Sep 2023 17:13:04 -0400 Subject: bfstd: Work around a FreeBSD-specific msan issue Link: https://github.com/llvm/llvm-project/issues/65532 --- src/bfstd.c | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index c858910..1a5a67d 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -5,6 +5,7 @@ #include "bit.h" #include "config.h" #include "diag.h" +#include "sanity.h" #include "thread.h" #include "xregex.h" #include @@ -590,11 +591,20 @@ static unsigned char ctype_cache[UCHAR_MAX + 1]; /** Initialize the ctype cache. */ static void char_cache_init(void) { +#if __FreeBSD__ && SANITIZE_MEMORY +// Work around https://github.com/llvm/llvm-project/issues/65532 +# define bfs_isprint (isprint) +# define bfs_isspace (isspace) +#else +# define bfs_isprint isprint +# define bfs_isspace isspace +#endif + for (size_t c = 0; c <= UCHAR_MAX; ++c) { - if (isprint(c)) { + if (bfs_isprint(c)) { ctype_cache[c] |= IS_PRINT; } - if (isspace(c)) { + if (bfs_isspace(c)) { ctype_cache[c] |= IS_SPACE; } } @@ -618,11 +628,20 @@ static bool xisprint(unsigned char c, enum wesc_flags flags) { /** Check if a wide character is printable. */ static bool xiswprint(wchar_t c, enum wesc_flags flags) { - if (iswprint(c)) { +#if __FreeBSD__ && SANITIZE_MEMORY +// Work around https://github.com/llvm/llvm-project/issues/65532 +# define bfs_iswprint (iswprint) +# define bfs_iswspace (iswspace) +#else +# define bfs_iswprint iswprint +# define bfs_iswspace iswspace +#endif + + if (bfs_iswprint(c)) { return true; } - if (!(flags & WESC_SHELL) && iswspace(c)) { + if (!(flags & WESC_SHELL) && bfs_iswspace(c)) { return true; } -- cgit v1.2.3 From 0015b79936a58a325e80cf036c10cb1010122703 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Sep 2023 21:15:50 -0400 Subject: Release 3.0.2 --- GNUmakefile | 2 +- docs/CHANGELOG.md | 18 ++++++++++++++++++ src/config.h | 2 +- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 069c548..6902979 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 3.0.1 +VERSION := 3.0.2 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 1cc95e9..3dca97c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,24 @@ 3.* === +3.0.2 +----- + +**September 6, 2023** + +### Changes + +- `-files0-from` now allows an empty set of paths to be given, matching GNU findutils 4.9.0 + +- Reduced memory consumption in multi-threaded searches + +- Many man page updates + +### Bug fixes + +- Fixed an out-of-bounds memory read that could occur when escaping a string containing an incomplete multi-byte character + + 3.0.1 ----- diff --git a/src/config.h b/src/config.h index fd4961f..14c9305 100644 --- a/src/config.h +++ b/src/config.h @@ -22,7 +22,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "3.0.1" +# define BFS_VERSION "3.0.2" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From beea0d2c3d3fa6ef317989f42d7f965e7797c098 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 12 Sep 2023 15:04:30 -0400 Subject: parse: Give more ephemeral_fds to -no{user,group} Fewer than 3 can lead to Assertion failed: (retval->write_queue != -1), function __open_cached_connection, file /usr/src/lib/libc/net/nscachedcli.c, line 224. on a FreeBSD system with LDAP accounts. --- src/parse.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/parse.c b/src/parse.c index 3861610..3416d9e 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1236,6 +1236,7 @@ static struct bfs_expr *parse_depth_limit(struct parser_state *state, int is_min static struct bfs_expr *parse_empty(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(state, eval_empty); if (expr) { + // For opendir() expr->ephemeral_fds = 1; } return expr; @@ -1258,10 +1259,13 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar expr->exec = execbuf; + // For pipe() in bfs_spawn() expr->ephemeral_fds = 2; + if (execbuf->flags & BFS_EXEC_CHDIR) { + // To dup() the parent directory if (execbuf->flags & BFS_EXEC_MULTI) { - expr->persistent_fds = 1; + ++expr->persistent_fds; } else { ++expr->ephemeral_fds; } @@ -1874,9 +1878,8 @@ fail: static struct bfs_expr *parse_nogroup(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(state, eval_nogroup); if (expr) { - // Who knows how many FDs getgrgid_r() needs? Probably at least - // one for /etc/group - expr->ephemeral_fds = 1; + // Who knows how many FDs getgrgid_r() needs? + expr->ephemeral_fds = 3; } return expr; } @@ -1912,9 +1915,8 @@ static struct bfs_expr *parse_noleaf(struct parser_state *state, int arg1, int a static struct bfs_expr *parse_nouser(struct parser_state *state, int arg1, int arg2) { struct bfs_expr *expr = parse_nullary_test(state, eval_nouser); if (expr) { - // Who knows how many FDs getpwuid_r() needs? Probably at least - // one for /etc/passwd - expr->ephemeral_fds = 1; + // Who knows how many FDs getpwuid_r() needs? + expr->ephemeral_fds = 3; } return expr; } -- cgit v1.2.3 From 5f1616912ba3a7a23ce6bce02df3791b73da38ab Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 13 Sep 2023 11:39:50 -0400 Subject: bftw: Share the bftw_state between iterations of ids/eds --- src/bftw.c | 143 ++++++++++++++++++++++++++++++------------------------------- 1 file changed, 71 insertions(+), 72 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 810cd8a..e6b8cd5 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -397,6 +397,10 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { * Holds the current state of the bftw() traversal. */ struct bftw_state { + /** The path(s) to start from. */ + const char **paths; + /** The number of starting paths. */ + size_t npaths; /** bftw() callback. */ bftw_callback *callback; /** bftw() callback data. */ @@ -453,6 +457,8 @@ struct bftw_state { /** Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) { + state->paths = args->paths; + state->npaths = args->npaths; state->callback = args->callback; state->ptr = args->ptr; state->flags = args->flags; @@ -1426,45 +1432,52 @@ static int bftw_state_destroy(struct bftw_state *state) { } /** - * bftw() implementation for simple breadth-/depth-first search. + * Shared implementation for all search strategies. */ -static int bftw_impl(const struct bftw_args *args) { - struct bftw_state state; - if (bftw_state_init(&state, args) != 0) { - return -1; - } - - for (size_t i = 0; i < args->npaths; ++i) { - if (bftw_visit(&state, args->paths[i]) != 0) { - goto done; +static int bftw_impl(struct bftw_state *state) { + for (size_t i = 0; i < state->npaths; ++i) { + if (bftw_visit(state, state->paths[i]) != 0) { + return -1; } } - bftw_batch_finish(&state); + bftw_batch_finish(state); while (true) { - while (bftw_pop_dir(&state)) { - if (bftw_opendir(&state) != 0) { - goto done; + while (bftw_pop_dir(state)) { + if (bftw_opendir(state) != 0) { + return -1; } - while (bftw_readdir(&state) > 0) { - if (bftw_visit(&state, state.de->name) != 0) { - goto done; + while (bftw_readdir(state) > 0) { + if (bftw_visit(state, state->de->name) != 0) { + return -1; } } - if (bftw_closedir(&state) != 0) { - goto done; + if (bftw_closedir(state) != 0) { + return -1; } } - if (!bftw_pop_file(&state)) { + if (!bftw_pop_file(state)) { break; } - if (bftw_visit(&state, NULL) != 0) { + if (bftw_visit(state, NULL) != 0) { break; } } -done: + return 0; +} + +/** + * bftw() implementation for simple breadth-/depth-first search. + */ +static int bftw_walk(const struct bftw_args *args) { + struct bftw_state state; + if (bftw_state_init(&state, args) != 0) { + return -1; + } + + bftw_impl(&state); return bftw_state_destroy(&state); } @@ -1472,6 +1485,8 @@ done: * Iterative deepening search state. */ struct bftw_ids_state { + /** Nested walk state. */ + struct bftw_state nested; /** The wrapped callback. */ bftw_callback *delegate; /** The wrapped callback arguments. */ @@ -1486,12 +1501,8 @@ struct bftw_ids_state { size_t max_depth; /** The set of pruned paths. */ struct trie pruned; - /** An error code to report. */ - int error; /** Whether the bottom has been found. */ bool bottom; - /** Whether to quit the search. */ - bool quit; }; /** Iterative deepening callback function. */ @@ -1535,17 +1546,17 @@ static enum bftw_action bftw_ids_callback(const struct BFTW *ftwbuf, void *ptr) ret = BFTW_PRUNE; } break; + case BFTW_PRUNE: if (ftwbuf->type == BFS_DIR) { if (!trie_insert_str(&state->pruned, ftwbuf->path)) { - state->error = errno; - state->quit = true; + state->nested.error = errno; ret = BFTW_STOP; } } break; + case BFTW_STOP: - state->quit = true; break; } @@ -1553,7 +1564,7 @@ static enum bftw_action bftw_ids_callback(const struct BFTW *ftwbuf, void *ptr) } /** Initialize iterative deepening state. */ -static void bftw_ids_init(const struct bftw_args *args, struct bftw_ids_state *state, struct bftw_args *ids_args) { +static int bftw_ids_init(struct bftw_ids_state *state, const struct bftw_args *args) { state->delegate = args->callback; state->ptr = args->ptr; state->visit = BFTW_PRE; @@ -1561,30 +1572,19 @@ static void bftw_ids_init(const struct bftw_args *args, struct bftw_ids_state *s state->min_depth = 0; state->max_depth = 1; trie_init(&state->pruned); - state->error = 0; state->bottom = false; - state->quit = false; - *ids_args = *args; - ids_args->callback = bftw_ids_callback; - ids_args->ptr = state; - ids_args->flags &= ~BFTW_POST_ORDER; + struct bftw_args ids_args = *args; + ids_args.callback = bftw_ids_callback; + ids_args.ptr = state; + ids_args.flags &= ~BFTW_POST_ORDER; + return bftw_state_init(&state->nested, &ids_args); } /** Finish an iterative deepening search. */ -static int bftw_ids_finish(struct bftw_ids_state *state) { - int ret = 0; - - if (state->error) { - ret = -1; - } else { - state->error = errno; - } - +static int bftw_ids_destroy(struct bftw_ids_state *state) { trie_destroy(&state->pruned); - - errno = state->error; - return ret; + return bftw_state_destroy(&state->nested); } /** @@ -1592,15 +1592,15 @@ static int bftw_ids_finish(struct bftw_ids_state *state) { */ static int bftw_ids(const struct bftw_args *args) { struct bftw_ids_state state; - struct bftw_args ids_args; - bftw_ids_init(args, &state, &ids_args); + if (bftw_ids_init(&state, args) != 0) { + return -1; + } - while (!state.quit && !state.bottom) { + while (!state.bottom) { state.bottom = true; - if (bftw_impl(&ids_args) != 0) { - state.error = errno; - state.quit = true; + if (bftw_impl(&state.nested) != 0) { + goto done; } ++state.min_depth; @@ -1611,18 +1611,18 @@ static int bftw_ids(const struct bftw_args *args) { state.visit = BFTW_POST; state.force_visit = true; - while (!state.quit && state.min_depth > 0) { + while (state.min_depth > 0) { --state.max_depth; --state.min_depth; - if (bftw_impl(&ids_args) != 0) { - state.error = errno; - state.quit = true; + if (bftw_impl(&state.nested) != 0) { + goto done; } } } - return bftw_ids_finish(&state); +done: + return bftw_ids_destroy(&state); } /** @@ -1630,39 +1630,38 @@ static int bftw_ids(const struct bftw_args *args) { */ static int bftw_eds(const struct bftw_args *args) { struct bftw_ids_state state; - struct bftw_args ids_args; - bftw_ids_init(args, &state, &ids_args); + if (bftw_ids_init(&state, args) != 0) { + return -1; + } - while (!state.quit && !state.bottom) { + while (!state.bottom) { state.bottom = true; - if (bftw_impl(&ids_args) != 0) { - state.error = errno; - state.quit = true; + if (bftw_impl(&state.nested) != 0) { + goto done; } state.min_depth = state.max_depth; state.max_depth *= 2; } - if (!state.quit && (args->flags & BFTW_POST_ORDER)) { + if (args->flags & BFTW_POST_ORDER) { state.visit = BFTW_POST; state.min_depth = 0; - ids_args.flags |= BFTW_POST_ORDER; + state.nested.flags |= BFTW_POST_ORDER; - if (bftw_impl(&ids_args) != 0) { - state.error = errno; - } + bftw_impl(&state.nested); } - return bftw_ids_finish(&state); +done: + return bftw_ids_destroy(&state); } int bftw(const struct bftw_args *args) { switch (args->strategy) { case BFTW_BFS: case BFTW_DFS: - return bftw_impl(args); + return bftw_walk(args); case BFTW_IDS: return bftw_ids(args); case BFTW_EDS: -- cgit v1.2.3 From 53e7f294c6acaf8d003661ccc6f21434b6dc4981 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 14 Sep 2023 12:14:26 -0400 Subject: README: Add Guix and OpenBSD packages --- README.md | 47 +++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 812acaa..18af808 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,13 @@ Installation

+ + + + + + + + + + + + + +
LinuxmacOS
+
 Alpine Linux
 # apk add bfs
@@ -227,21 +234,45 @@ Installation
 Fedora Linux
 # dnf install bfs
 
+GNU Guix
+# guix install bfs
+
 NixOS
 # nix-env -i bfs
 
 Void Linux
 # xbps-install -S bfs
+
-FreeBSD -# pkg install bfs +
+ +
+Homebrew
+$ brew install bfs
 
 MacPorts
 # port install bfs
+
-Homebrew -$ brew install bfs +
BSD
+ +
+FreeBSD
+# pkg install bfs
+
+OpenBSD
+# pkg_add bfs
 
+ +
@@ -276,14 +307,14 @@ Here's how to install them on some common platforms: Void Linux # xbps-install -S acl-{devel,progs} attr-{devel,progs} libcap-{devel,progs} oniguruma-devel -FreeBSD -# pkg install oniguruma +Homebrew +$ brew install oniguruma MacPorts # port install oniguruma6 -Homebrew -$ brew install oniguruma +FreeBSD +# pkg install oniguruma These dependencies are technically optional, though strongly recommended. -- cgit v1.2.3 From 1f663544495ed520bfd98345a86c92fa53efb747 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Sep 2023 13:03:51 -0400 Subject: list: Use (void)(...) rather than do { ... } while (0) This makes everything usable in expression contexts. --- src/list.h | 61 ++++++++++++++++++++++++++++++------------------------------- 1 file changed, 30 insertions(+), 31 deletions(-) diff --git a/src/list.h b/src/list.h index 3b53fab..0aebd4c 100644 --- a/src/list.h +++ b/src/list.h @@ -94,16 +94,19 @@ * don't have to. */ #define SLIST_INIT(list) \ - LIST_BLOCK_(SLIST_INIT_((list))) + SLIST_INIT_((list)) -#define SLIST_INIT_(list) \ - list->head = NULL; \ - list->tail = &list->head; +/** + * Helper for SLIST_INIT(). + */ +#define SLIST_INIT_(list) LIST_VOID_( \ + list->head = NULL, \ + list->tail = &list->head) /** - * Wraps a group of statements in a block. + * Cast a list of expressions to void. */ -#define LIST_BLOCK_(block) do { block } while (0) +#define LIST_VOID_(...) ((void)(__VA_ARGS__)) /** * Insert an item into a singly-linked list. @@ -187,12 +190,12 @@ * node, and finally delegates to the actual implementation. */ #define SLIST_INSERT_(list, cursor, item, ...) \ - LIST_BLOCK_(SLIST_INSERT__((list), (cursor), (item), LIST_NEXT_(__VA_ARGS__))) + SLIST_INSERT__((list), (cursor), (item), LIST_NEXT_(__VA_ARGS__)) -#define SLIST_INSERT__(list, cursor, item, next) \ - item->next = *cursor; \ - *cursor = item; \ - list->tail = item->next ? list->tail : &item->next; +#define SLIST_INSERT__(list, cursor, item, next) LIST_VOID_( \ + item->next = *cursor, \ + *cursor = item, \ + list->tail = item->next ? list->tail : &item->next) /** * Add an item to the tail of a singly-linked list. @@ -233,14 +236,10 @@ * The source list. */ #define SLIST_EXTEND(dest, src) \ - LIST_BLOCK_(SLIST_EXTEND_((dest), (src))) + SLIST_EXTEND_((dest), (src)) #define SLIST_EXTEND_(dest, src) \ - if (src->head) { \ - *dest->tail = src->head; \ - dest->tail = src->tail; \ - SLIST_INIT(src); \ - } + (src->head ? (*dest->tail = src->head, dest->tail = src->tail, SLIST_INIT(src)) : (void)0) /** * Remove an item from a singly-linked list. @@ -295,10 +294,10 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * The list to initialize. */ #define LIST_INIT(list) \ - LIST_BLOCK_(LIST_INIT_((list))) + LIST_INIT_((list)) -#define LIST_INIT_(list) \ - list->head = list->tail = NULL; +#define LIST_INIT_(list) LIST_VOID_( \ + list->head = list->tail = NULL) /** * LIST_PREV_() => prev @@ -345,13 +344,13 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void #define LIST_INSERT(list, cursor, ...) LIST_INSERT_(list, cursor, __VA_ARGS__, ) #define LIST_INSERT_(list, cursor, item, ...) \ - LIST_BLOCK_(LIST_INSERT__((list), (cursor), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__))) + LIST_INSERT__((list), (cursor), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) -#define LIST_INSERT__(list, cursor, item, prev, next) \ - item->prev = cursor; \ - item->next = cursor ? cursor->next : list->head; \ - *(item->prev ? &item->prev->next : &list->head) = item; \ - *(item->next ? &item->next->prev : &list->tail) = item; +#define LIST_INSERT__(list, cursor, item, prev, next) LIST_VOID_( \ + item->prev = cursor, \ + item->next = cursor ? cursor->next : list->head, \ + *(item->prev ? &item->prev->next : &list->head) = item, \ + *(item->next ? &item->next->prev : &list->tail) = item) /** * Remove an item from a doubly-linked list. @@ -366,12 +365,12 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void #define LIST_REMOVE(list, ...) LIST_REMOVE_(list, __VA_ARGS__, ) #define LIST_REMOVE_(list, item, ...) \ - LIST_BLOCK_(LIST_REMOVE__((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__))) + LIST_REMOVE__((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) -#define LIST_REMOVE__(list, item, prev, next) \ - *(item->prev ? &item->prev->next : &list->head) = item->next; \ - *(item->next ? &item->next->prev : &list->tail) = item->prev; \ - item->prev = item->next = NULL; +#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) /** * Check if an item is attached to a doubly-linked list. -- cgit v1.2.3 From 72ea07d8c200bd6cd34ce02121165f3888624ac6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Sep 2023 13:16:35 -0400 Subject: list: New [S]LIST_ITEM_INIT() macros --- src/list.h | 74 +++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/list.h b/src/list.h index 0aebd4c..9f8fd20 100644 --- a/src/list.h +++ b/src/list.h @@ -21,6 +21,7 @@ * SLIST_INIT(&list); * * struct item item; + * SLIST_ITEM_INIT(&item); * SLIST_APPEND(&list, &item); * * Doubly linked lists are similar: @@ -39,6 +40,7 @@ * LIST_INIT(&list); * * struct item item; + * LIST_ITEM_INIT(&item); * LIST_APPEND(&list, &item); * * Items can be on multiple lists at once: @@ -71,7 +73,9 @@ * LIST_INIT(&items.cache); * * struct item item; + * SLIST_ITEM_INIT(&item, chain); * SLIST_APPEND(&items.queue, &item, chain); + * LIST_ITEM_INIT(&item, lru); * LIST_APPEND(&items.cache, &item, lru); */ @@ -109,14 +113,10 @@ #define LIST_VOID_(...) ((void)(__VA_ARGS__)) /** - * Insert an item into a singly-linked list. + * Initialize a singly-linked list item. * - * @param list - * The list to modify. - * @param cursor - * A pointer to the item to insert after, e.g. &list->head or list->tail. * @param item - * The item to insert. + * The item to initialize. * @param node (optional) * If specified, use item->node.next rather than item->next. * @@ -124,29 +124,21 @@ * * We play some tricks with variadic macros to handle the optional parameter: * - * SLIST_INSERT(list, cursor, item) => { - * item->next = *cursor; - * *cursor = item; - * list->tail = item->next ? list->tail : &item->next; - * } - * - * SLIST_INSERT(list, cursor, item, node) => { - * item->node.next = *cursor; - * *cursor = item; - * list->tail = item->node.next ? list->tail : &item->node.next; - * } + * SLIST_ITEM_INIT(item) => item->next = NULL + * SLIST_ITEM_INIT(item, node) => item->node.next = NULL * * The first trick is that * - * #define SLIST_INSERT(list, item, cursor, ...) + * #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_INSERT(list, cursor, item) => SLIST_INSERT_(list, cursor, item, ) - * SLIST_INSERT(list, cursor, item, node) => SLIST_INSERT_(list, cursor, item, node, ) + * SLIST_ITEM_INIT(item) => SLIST_ITEM_INIT_(item, ) + * SLIST_ITEM_INIT(item, node) => SLIST_ITEM_INIT_(item, node, ) */ -#define SLIST_INSERT(list, cursor, ...) SLIST_INSERT_(list, cursor, __VA_ARGS__, ) +#define SLIST_ITEM_INIT(...) \ + SLIST_ITEM_INIT_(__VA_ARGS__, ) /** * Now we need a way to generate either ->next or ->node.next depending on @@ -186,9 +178,30 @@ #define LIST_NODE__(dir, node, ignored, dot, ...) node dot dir /** - * SLIST_INSERT_() uses LIST_NEXT_() to generate the right name for the list + * 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) + +/** + * Insert an item into a singly-linked list. + * + * @param list + * The list to modify. + * @param cursor + * A pointer to the item to insert after, e.g. &list->head or list->tail. + * @param item + * The item to insert. + * @param node (optional) + * If specified, use item->node.next rather than item->next. + */ +#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__)) @@ -305,6 +318,23 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void */ #define LIST_PREV_(...) LIST_NODE_(prev, __VA_ARGS__) +/** + * Initialize a doubly-linked list item. + * + * @param item + * The item to initialize. + * @param 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, prev, next) \ + LIST_VOID_(item->prev = item->next = NULL) + /** * Add an item to the tail of a doubly-linked list. * -- cgit v1.2.3 From 4343832097996886d085fe1139abcec363afc9ee Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Sep 2023 13:29:03 -0400 Subject: list: New [S]LIST_EMPTY() macros --- src/list.h | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/list.h b/src/list.h index 9f8fd20..5cb6264 100644 --- a/src/list.h +++ b/src/list.h @@ -187,6 +187,15 @@ #define SLIST_ITEM_INIT__(item, next) \ LIST_VOID_(item->next = NULL) +/** + * Check if a singly-linked list is empty. + */ +#define SLIST_EMPTY(list) \ + SLIST_EMPTY_((list)) + +#define SLIST_EMPTY_(list) \ + ((void)sizeof(list->tail - &list->head), !list->head) + /** * Insert an item into a singly-linked list. * @@ -335,6 +344,15 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void #define LIST_ITEM_INIT__(item, prev, next) \ LIST_VOID_(item->prev = item->next = NULL) +/** + * Check if a doubly-linked list is empty. + */ +#define LIST_EMPTY(list) \ + LIST_EMPTY_((list)) + +#define LIST_EMPTY_(list) \ + ((void)sizeof(list->tail - list->head), !list->head) + /** * Add an item to the tail of a doubly-linked list. * -- cgit v1.2.3 From 6b341c9a7ed531cfd84301b59d2da0fd9d5474b1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Sep 2023 13:34:17 -0400 Subject: list: Unify formatting --- src/list.h | 48 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/src/list.h b/src/list.h index 5cb6264..235dc55 100644 --- a/src/list.h +++ b/src/list.h @@ -155,7 +155,8 @@ * LIST_NEXT_() => LIST_NODE_(next, ) * LIST_NEXT_(node, ) => LIST_NODE_(next, node, ) */ -#define LIST_NEXT_(...) LIST_NODE_(next, __VA_ARGS__) +#define LIST_NEXT_(...) \ + LIST_NODE_(next, __VA_ARGS__) /** * LIST_NODE_() dispatches to yet another macro: @@ -163,7 +164,8 @@ * LIST_NODE_(next, ) => LIST_NODE__(next, , . , , ) * LIST_NODE_(next, node, ) => LIST_NODE__(next, node, , . , , ) */ -#define LIST_NODE_(dir, ...) LIST_NODE__(dir, __VA_ARGS__, . , , ) +#define LIST_NODE_(dir, ...) \ + LIST_NODE__(dir, __VA_ARGS__, . , , ) /** * And finally, LIST_NODE__() adds the node and the dot if necessary. @@ -175,7 +177,8 @@ * ^ ^ ^ ^ * dir node ignored dot */ -#define LIST_NODE__(dir, node, ignored, dot, ...) node dot dir +#define LIST_NODE__(dir, node, ignored, dot, ...) \ + node dot dir /** * SLIST_ITEM_INIT_() uses LIST_NEXT_() to generate the right name for the list @@ -229,7 +232,8 @@ * @param 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, ...) \ + SLIST_APPEND_(list, __VA_ARGS__, ) #define SLIST_APPEND_(list, item, ...) \ SLIST_INSERT_(list, (list)->tail, item, __VA_ARGS__) @@ -244,7 +248,8 @@ * @param 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, ...) \ + SLIST_PREPEND_(list, __VA_ARGS__, ) #define SLIST_PREPEND_(list, item, ...) \ SLIST_INSERT_(list, &(list)->head, item, __VA_ARGS__) @@ -275,7 +280,8 @@ * @return * The removed item. */ -#define SLIST_REMOVE(list, ...) SLIST_REMOVE_(list, __VA_ARGS__, ) +#define SLIST_REMOVE(list, ...) \ + SLIST_REMOVE_(list, __VA_ARGS__, ) #define SLIST_REMOVE_(list, cursor, ...) \ SLIST_REMOVE__((list), (cursor), LIST_NEXT_(__VA_ARGS__)) @@ -304,10 +310,14 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * @return * The popped item, or NULL if the list was empty. */ -#define SLIST_POP(...) SLIST_POP_(__VA_ARGS__, ) +#define SLIST_POP(...) \ + SLIST_POP_(__VA_ARGS__, ) #define SLIST_POP_(list, ...) \ - ((list)->head ? SLIST_REMOVE_(list, &(list)->head, __VA_ARGS__) : NULL) + SLIST_POP__((list), __VA_ARGS__) + +#define SLIST_POP__(list, ...) \ + (list->head ? SLIST_REMOVE_(list, &list->head, __VA_ARGS__) : NULL) /** * Initialize a doubly-linked list. @@ -318,14 +328,15 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void #define LIST_INIT(list) \ LIST_INIT_((list)) -#define LIST_INIT_(list) LIST_VOID_( \ - list->head = list->tail = NULL) +#define LIST_INIT_(list) \ + LIST_VOID_(list->head = list->tail = NULL) /** * LIST_PREV_() => prev * LIST_PREV_(node, ) => node.prev */ -#define LIST_PREV_(...) LIST_NODE_(prev, __VA_ARGS__) +#define LIST_PREV_(...) \ + LIST_NODE_(prev, __VA_ARGS__) /** * Initialize a doubly-linked list item. @@ -363,7 +374,8 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * @param 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, ...) \ + LIST_INSERT(list, (list)->tail, __VA_ARGS__) /** * Add an item to the head of a doubly-linked list. @@ -375,7 +387,8 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * @param 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, ...) \ + LIST_INSERT(list, NULL, __VA_ARGS__) /** * Insert into a doubly-linked list after the given cursor. @@ -389,7 +402,8 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * @param 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, ...) \ + 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__)) @@ -410,7 +424,8 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * @param 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, ...) \ + LIST_REMOVE_(list, __VA_ARGS__, ) #define LIST_REMOVE_(list, item, ...) \ LIST_REMOVE__((list), (item), LIST_PREV_(__VA_ARGS__), LIST_NEXT_(__VA_ARGS__)) @@ -432,7 +447,8 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void * @return * Whether the item is attached to the list. */ -#define LIST_ATTACHED(list, ...) LIST_ATTACHED_(list, __VA_ARGS__, ) +#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__)) -- cgit v1.2.3 From e4304e8b9e74577947647743fd51937ac51956c9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Sep 2023 13:53:15 -0400 Subject: list: New for_[s]list() macros --- src/list.h | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/src/list.h b/src/list.h index 235dc55..5587543 100644 --- a/src/list.h +++ b/src/list.h @@ -190,6 +190,12 @@ #define SLIST_ITEM_INIT__(item, next) \ LIST_VOID_(item->next = NULL) +/** + * Type-checking macro for singly-linked lists. + */ +#define SLIST_CHECK_(list) \ + (void)sizeof(list->tail - &list->head) + /** * Check if a singly-linked list is empty. */ @@ -197,7 +203,7 @@ SLIST_EMPTY_((list)) #define SLIST_EMPTY_(list) \ - ((void)sizeof(list->tail - &list->head), !list->head) + (SLIST_CHECK_(list), !list->head) /** * Insert an item into a singly-linked list. @@ -319,6 +325,29 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void #define SLIST_POP__(list, ...) \ (list->head ? SLIST_REMOVE_(list, &list->head, __VA_ARGS__) : NULL) +/** + * Loop over the items in a singly-linked list. + * + * @param type + * The list item type. + * @param item + * The induction variable name. + * @param list + * The list to iterate. + * @param 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, next) \ + for (type *item = list->head, *_next; \ + item && (SLIST_CHECK_(list), _next = item->next, true); \ + item = _next) + /** * Initialize a doubly-linked list. * @@ -355,6 +384,12 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void #define LIST_ITEM_INIT__(item, prev, next) \ LIST_VOID_(item->prev = item->next = NULL) +/** + * Type-checking macro for doubly-linked lists. + */ +#define LIST_CHECK_(list) \ + (void)sizeof(list->tail - list->head) + /** * Check if a doubly-linked list is empty. */ @@ -362,7 +397,7 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void LIST_EMPTY_((list)) #define LIST_EMPTY_(list) \ - ((void)sizeof(list->tail - list->head), !list->head) + (LIST_CHECK_(list), !list->head) /** * Add an item to the tail of a doubly-linked list. @@ -456,4 +491,27 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void #define LIST_ATTACHED__(list, item, prev, next) \ (item->prev || item->next || list->head == item || list->tail == item) +/** + * Loop over the items in a doubly-linked list. + * + * @param type + * The list item type. + * @param item + * The induction variable name. + * @param list + * The list to iterate. + * @param 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, next) \ + for (type *item = list->head, *_next; \ + item && (LIST_CHECK_(list), _next = item->next, true); \ + item = _next) + #endif // BFS_LIST_H -- cgit v1.2.3 From 76253d272c50ba08002a47395e47a4f9ce4fb53e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Sep 2023 13:58:19 -0400 Subject: Use the new list macros --- src/bftw.c | 24 +++++++++++------------- src/color.c | 2 +- src/ctx.c | 4 ++-- src/trie.h | 7 +++---- src/xspawn.c | 11 +++++------ tests/trie.c | 4 ++-- 6 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index e6b8cd5..f3060ce 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -318,8 +318,7 @@ static size_t bftw_child_nameoff(const struct bftw_file *parent) { /** Destroy a cache. */ static void bftw_cache_destroy(struct bftw_cache *cache) { - bfs_assert(!cache->head); - bfs_assert(!cache->tail); + bfs_assert(LIST_EMPTY(cache)); bfs_assert(!cache->target); varena_destroy(&cache->files); @@ -347,9 +346,9 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->nameoff = 0; } - file->next = NULL; - file->to_read.next = NULL; - file->lru.prev = file->lru.next = NULL; + SLIST_ITEM_INIT(file); + SLIST_ITEM_INIT(file, to_read); + LIST_ITEM_INIT(file, lru); file->refcount = 1; file->pincount = 0; @@ -833,12 +832,11 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { SLIST_APPEND(&state->to_read, file, to_read); } - while (state->to_open.head) { - if (bftw_ioq_opendir(state, state->to_open.head) == 0) { - SLIST_POP(&state->to_open); - } else { + for_slist (struct bftw_file, dir, &state->to_open) { + if (bftw_ioq_opendir(state, dir) != 0) { break; } + SLIST_POP(&state->to_open); } } @@ -847,7 +845,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); struct bftw_cache *cache = &state->cache; - bool have_files = state->to_visit.head; + bool have_files = !SLIST_EMPTY(&state->to_visit); if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting @@ -855,9 +853,9 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } } else { - while (!state->to_read.head) { + while (SLIST_EMPTY(&state->to_read)) { // Block if we have no other files/dirs to visit, or no room in the cache - bool have_dirs = state->to_open.head; + bool have_dirs = !SLIST_EMPTY(&state->to_open); bool have_room = cache->capacity > 0 && cache->dirlimit > 0; bool block = !(have_dirs || have_files) || !have_room; @@ -1303,7 +1301,7 @@ static void bftw_list_sort(struct bftw_list *list) { bftw_list_sort(&right); // Merge - while (left.head && right.head) { + while (!SLIST_EMPTY(&left) && !SLIST_EMPTY(&right)) { struct bftw_file *lf = left.head; struct bftw_file *rf = right.head; diff --git a/src/color.c b/src/color.c index b9a788b..5e78c6c 100644 --- a/src/color.c +++ b/src/color.c @@ -328,7 +328,7 @@ fail: static int build_iext_trie(struct colors *colors) { trie_clear(&colors->iext_trie); - TRIE_FOR_EACH(&colors->ext_trie, leaf) { + for_trie (leaf, &colors->ext_trie) { size_t len = leaf->length - 1; if (colors->ext_len < len) { colors->ext_len = len; diff --git a/src/ctx.c b/src/ctx.c index a940bed..3a44e68 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -152,7 +152,7 @@ void bfs_ctx_flush(const struct bfs_ctx *ctx) { // - the user sees everything relevant before an -ok[dir] prompt // - output from commands is interleaved consistently with bfs // - executed commands can rely on I/O from other bfs actions - TRIE_FOR_EACH(&ctx->files, leaf) { + for_trie (leaf, &ctx->files) { struct bfs_ctx_file *ctx_file = leaf->value; CFILE *cfile = ctx_file->cfile; if (fflush(cfile->file) == 0) { @@ -239,7 +239,7 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { bfs_groups_free(ctx->groups); bfs_users_free(ctx->users); - TRIE_FOR_EACH(&ctx->files, leaf) { + for_trie (leaf, &ctx->files) { struct bfs_ctx_file *ctx_file = leaf->value; if (ctx_file->error) { diff --git a/src/trie.h b/src/trie.h index dfaae15..2f51db5 100644 --- a/src/trie.h +++ b/src/trie.h @@ -6,6 +6,7 @@ #include "config.h" #include "alloc.h" +#include "list.h" #include #include @@ -141,9 +142,7 @@ void trie_destroy(struct trie *trie); /** * Iterate over the leaves of a trie. */ -#define TRIE_FOR_EACH(trie, leaf) \ - for (struct trie_leaf *leaf = (trie)->head, *_next; \ - leaf && (_next = leaf->next, true); \ - leaf = _next) +#define for_trie(leaf, trie) \ + for_list(struct trie_leaf, leaf, trie) #endif // BFS_TRIE_H diff --git a/src/xspawn.c b/src/xspawn.c index 2cabdcc..80bafef 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -49,8 +49,8 @@ int bfs_spawn_init(struct bfs_spawn *ctx) { } int bfs_spawn_destroy(struct bfs_spawn *ctx) { - while (ctx->head) { - free(SLIST_POP(ctx)); + for_slist (struct bfs_spawn_action, action, ctx) { + free(action); } return 0; @@ -68,7 +68,7 @@ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_sp return NULL; } - action->next = NULL; + SLIST_ITEM_INIT(action); action->op = op; action->in_fd = -1; action->out_fd = -1; @@ -138,7 +138,7 @@ int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rli static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { xclose(pipefd[0]); - for (const struct bfs_spawn_action *action = ctx ? ctx->head : NULL; action; action = action->next) { + for_slist (const struct bfs_spawn_action, action, ctx) { // Move the error-reporting pipe out of the way if necessary... if (action->out_fd == pipefd[1]) { int fd = dup_cloexec(pipefd[1]); @@ -199,9 +199,8 @@ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char envp = environ; } - enum bfs_spawn_flags flags = ctx ? ctx->flags : 0; char *resolved = NULL; - if (flags & BFS_SPAWN_USEPATH) { + if (ctx->flags & BFS_SPAWN_USEPATH) { exe = resolved = bfs_spawn_resolve(exe); if (!resolved) { return -1; diff --git a/tests/trie.c b/tests/trie.c index e687f96..6ea94a2 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -70,7 +70,7 @@ int main(void) { { size_t i = 0; - TRIE_FOR_EACH(&trie, leaf) { + for_trie (leaf, &trie) { bfs_verify(leaf == trie_find_str(&trie, keys[i])); bfs_verify(!leaf->prev || leaf->prev->next == leaf); bfs_verify(!leaf->next || leaf->next->prev == leaf); @@ -107,7 +107,7 @@ int main(void) { } } - TRIE_FOR_EACH(&trie, leaf) { + for_trie (leaf, &trie) { bfs_verify(false); } -- cgit v1.2.3 From 8864dc5d1b9c85daa58e98c04767d2ee528f2dea Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Sep 2023 15:26:52 -0400 Subject: ci/freebsd: Switch to Tailscale OAuth --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0d15028..ca1737c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -70,7 +70,9 @@ jobs: - uses: tailscale/github-action@main with: - authkey: ${{ secrets.TAILSCALE_KEY }} + oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} + oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} + tags: tag:ci - name: Configure SSH env: -- cgit v1.2.3 From dccb52556730ff060bcccbe764cef4b13b3d5712 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 26 Sep 2023 12:48:21 -0400 Subject: dstring: New dchar typedef for dynamic strings --- src/bar.c | 2 +- src/bftw.c | 2 +- src/color.c | 6 +++--- src/color.h | 3 ++- src/diag.c | 2 +- src/dstring.c | 55 ++++++++++++++++++++++++++------------------------- src/dstring.h | 63 ++++++++++++++++++++++++++++++++++++++--------------------- src/eval.c | 4 ++-- src/exec.c | 4 ++-- src/fsade.c | 4 ++-- src/printf.c | 4 ++-- 11 files changed, 85 insertions(+), 64 deletions(-) diff --git a/src/bar.c b/src/bar.c index d2c663c..e0b9393 100644 --- a/src/bar.c +++ b/src/bar.c @@ -129,7 +129,7 @@ BFS_FORMATTER(2, 3) static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { va_list args; va_start(args, format); - char *str = dstrvprintf(format, args); + dchar *str = dstrvprintf(format, args); va_end(args); if (!str) { diff --git a/src/bftw.c b/src/bftw.c index f3060ce..5e5f4a5 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -435,7 +435,7 @@ struct bftw_state { struct bftw_list batch; /** The current path. */ - char *path; + dchar *path; /** The current file. */ struct bftw_file *file; /** The previous file. */ diff --git a/src/color.c b/src/color.c index 5e78c6c..8d0b995 100644 --- a/src/color.c +++ b/src/color.c @@ -558,8 +558,8 @@ static int unescape(char **str, const char *value, char end, const char **next) /** Parse the GNU $LS_COLORS format. */ static int parse_gnu_ls_colors(struct colors *colors, const char *ls_colors) { int ret = -1; - char *key = NULL; - char *value = NULL; + dchar *key = NULL; + dchar *value = NULL; for (const char *chunk = ls_colors, *next; chunk; chunk = next) { if (chunk[0] == '*') { @@ -943,7 +943,7 @@ static ssize_t first_broken_offset(const char *path, const struct BFTW *ftwbuf, goto out; } - char *at_path; + dchar *at_path; int at_fd; if (path == ftwbuf->path) { if (ftwbuf->depth == 0) { diff --git a/src/color.h b/src/color.h index 0d46c33..b118f77 100644 --- a/src/color.h +++ b/src/color.h @@ -9,6 +9,7 @@ #define BFS_COLOR_H #include "config.h" +#include "dstring.h" #include #include @@ -41,7 +42,7 @@ typedef struct CFILE { /** The color table to use, if any. */ const struct colors *colors; /** A buffer for colored formatting. */ - char *buffer; + dchar *buffer; /** Whether the next ${rs} is actually necessary. */ bool need_reset; /** Whether to close the underlying stream. */ diff --git a/src/diag.c b/src/diag.c index 0590847..fa9db39 100644 --- a/src/diag.c +++ b/src/diag.c @@ -158,7 +158,7 @@ static void bfs_argv_diag(const struct bfs_ctx *ctx, const bool args[], bool war bfs_error_prefix(ctx); } - char **argv = ZALLOC_ARRAY(char *, ctx->argc); + dchar **argv = ZALLOC_ARRAY(dchar *, ctx->argc); if (!argv) { return; } diff --git a/src/dstring.c b/src/dstring.c index 60a7df9..ef4e733 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -4,6 +4,7 @@ #include "dstring.h" #include "alloc.h" #include "bit.h" +#include "config.h" #include "diag.h" #include #include @@ -16,11 +17,11 @@ struct dstring { size_t capacity; size_t length; - char data[]; + alignas(dchar) char data[]; }; /** Get the string header from the string data pointer. */ -static struct dstring *dstrheader(const char *dstr) { +static struct dstring *dstrheader(const dchar *dstr) { return (struct dstring *)(dstr - offsetof(struct dstring, data)); } @@ -30,7 +31,7 @@ static size_t dstrsize(size_t capacity) { } /** Allocate a dstring with the given contents. */ -static char *dstralloc_impl(size_t capacity, size_t length, const char *data) { +static dchar *dstralloc_impl(size_t capacity, size_t length, const char *data) { // Avoid reallocations for small strings if (capacity < 7) { capacity = 7; @@ -49,31 +50,31 @@ static char *dstralloc_impl(size_t capacity, size_t length, const char *data) { return header->data; } -char *dstralloc(size_t capacity) { +dchar *dstralloc(size_t capacity) { return dstralloc_impl(capacity, 0, ""); } -char *dstrdup(const char *str) { +dchar *dstrdup(const char *str) { return dstrxdup(str, strlen(str)); } -char *dstrndup(const char *str, size_t n) { +dchar *dstrndup(const char *str, size_t n) { return dstrxdup(str, strnlen(str, n)); } -char *dstrddup(const char *dstr) { +dchar *dstrddup(const dchar *dstr) { return dstrxdup(dstr, dstrlen(dstr)); } -char *dstrxdup(const char *str, size_t len) { +dchar *dstrxdup(const char *str, size_t len) { return dstralloc_impl(len, len, str); } -size_t dstrlen(const char *dstr) { +size_t dstrlen(const dchar *dstr) { return dstrheader(dstr)->length; } -int dstreserve(char **dstr, size_t capacity) { +int dstreserve(dchar **dstr, size_t capacity) { if (!*dstr) { *dstr = dstralloc(capacity); return *dstr ? 0 : -1; @@ -96,7 +97,7 @@ int dstreserve(char **dstr, size_t capacity) { return 0; } -int dstresize(char **dstr, size_t length) { +int dstresize(dchar **dstr, size_t length) { if (dstreserve(dstr, length) != 0) { return -1; } @@ -107,19 +108,19 @@ int dstresize(char **dstr, size_t length) { return 0; } -int dstrcat(char **dest, const char *src) { +int dstrcat(dchar **dest, const char *src) { return dstrxcat(dest, src, strlen(src)); } -int dstrncat(char **dest, const char *src, size_t n) { +int dstrncat(dchar **dest, const char *src, size_t n) { return dstrxcat(dest, src, strnlen(src, n)); } -int dstrdcat(char **dest, const char *src) { +int dstrdcat(dchar **dest, const dchar *src) { return dstrxcat(dest, src, dstrlen(src)); } -int dstrxcat(char **dest, const char *src, size_t len) { +int dstrxcat(dchar **dest, const char *src, size_t len) { size_t oldlen = dstrlen(*dest); size_t newlen = oldlen + len; @@ -131,23 +132,23 @@ int dstrxcat(char **dest, const char *src, size_t len) { return 0; } -int dstrapp(char **str, char c) { +int dstrapp(dchar **str, char c) { return dstrxcat(str, &c, 1); } -int dstrcpy(char **dest, const char *src) { +int dstrcpy(dchar **dest, const char *src) { return dstrxcpy(dest, src, strlen(src)); } -int dstrncpy(char **dest, const char *src, size_t n) { +int dstrncpy(dchar **dest, const char *src, size_t n) { return dstrxcpy(dest, src, strnlen(src, n)); } -int dstrdcpy(char **dest, const char *src) { +int dstrdcpy(dchar **dest, const dchar *src) { return dstrxcpy(dest, src, dstrlen(src)); } -int dstrxcpy(char **dest, const char *src, size_t len) { +int dstrxcpy(dchar **dest, const char *src, size_t len) { if (dstresize(dest, len) != 0) { return -1; } @@ -160,7 +161,7 @@ char *dstrprintf(const char *format, ...) { va_list args; va_start(args, format); - char *str = dstrvprintf(format, args); + dchar *str = dstrvprintf(format, args); va_end(args); return str; @@ -168,7 +169,7 @@ char *dstrprintf(const char *format, ...) { char *dstrvprintf(const char *format, va_list args) { // Guess a capacity to try to avoid reallocating - char *str = dstralloc(2*strlen(format)); + dchar *str = dstralloc(2*strlen(format)); if (!str) { return NULL; } @@ -181,7 +182,7 @@ char *dstrvprintf(const char *format, va_list args) { return str; } -int dstrcatf(char **str, const char *format, ...) { +int dstrcatf(dchar **str, const char *format, ...) { va_list args; va_start(args, format); @@ -191,7 +192,7 @@ int dstrcatf(char **str, const char *format, ...) { return ret; } -int dstrvcatf(char **str, const char *format, va_list args) { +int dstrvcatf(dchar **str, const char *format, va_list args) { // Guess a capacity to try to avoid calling vsnprintf() twice size_t len = dstrlen(*str); dstreserve(str, len + 2*strlen(format)); @@ -232,11 +233,11 @@ fail: return -1; } -int dstrescat(char **dest, const char *str, enum wesc_flags flags) { +int dstrescat(dchar **dest, const char *str, enum wesc_flags flags) { return dstrnescat(dest, str, SIZE_MAX, flags); } -int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags) { +int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags) { size_t len = *dest ? dstrlen(*dest) : 0; // Worst case growth is `ccc...` => $'\xCC\xCC\xCC...' @@ -254,7 +255,7 @@ int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags) { return dstresize(dest, cur - *dest); } -void dstrfree(char *dstr) { +void dstrfree(dchar *dstr) { if (dstr) { free(dstrheader(dstr)); } diff --git a/src/dstring.h b/src/dstring.h index 88ca79f..91a600c 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -13,13 +13,32 @@ #include #include +/** Marker type for dynamic strings. */ +#if __clang__ +// Abuse __attribute__(aligned) to make a type that allows +// +// dchar * -> char * +// +// conversions, but warns on +// +// char * -> dchar * +// +// (with Clang's -Walign-mismatch). The alignment is not a lie, due to the +// layout of struct dstring, but we only enable this on Clang because GCC +// tracks alignment through array accesses, reporting UBSan errors on (and +// maybe even miscompiling) dstr[1]. +typedef __attribute__((aligned(alignof(size_t)))) char dchar; +#else +typedef char dchar; +#endif + /** * Allocate a dynamic string. * * @param capacity * The initial capacity of the string. */ -char *dstralloc(size_t capacity); +dchar *dstralloc(size_t capacity); /** * Create a dynamic copy of a string. @@ -27,7 +46,7 @@ char *dstralloc(size_t capacity); * @param str * The NUL-terminated string to copy. */ -char *dstrdup(const char *str); +dchar *dstrdup(const char *str); /** * Create a length-limited dynamic copy of a string. @@ -37,7 +56,7 @@ char *dstrdup(const char *str); * @param n * The maximum number of characters to copy from str. */ -char *dstrndup(const char *str, size_t n); +dchar *dstrndup(const char *str, size_t n); /** * Create a dynamic copy of a dynamic string. @@ -45,7 +64,7 @@ char *dstrndup(const char *str, size_t n); * @param dstr * The dynamic string to copy. */ -char *dstrddup(const char *dstr); +dchar *dstrddup(const dchar *dstr); /** * Create an exact-sized dynamic copy of a string. @@ -55,7 +74,7 @@ char *dstrddup(const char *dstr); * @param len * The length of the string, which may include internal NUL bytes. */ -char *dstrxdup(const char *str, size_t len); +dchar *dstrxdup(const char *str, size_t len); /** * Get a dynamic string's length. @@ -65,7 +84,7 @@ char *dstrxdup(const char *str, size_t len); * @return * The length of dstr. */ -size_t dstrlen(const char *dstr); +size_t dstrlen(const dchar *dstr); /** * Reserve some capacity in a dynamic string. @@ -77,7 +96,7 @@ size_t dstrlen(const char *dstr); * @return * 0 on success, -1 on failure. */ -int dstreserve(char **dstr, size_t capacity); +int dstreserve(dchar **dstr, size_t capacity); /** * Resize a dynamic string. @@ -89,7 +108,7 @@ int dstreserve(char **dstr, size_t capacity); * @return * 0 on success, -1 on failure. */ -int dstresize(char **dstr, size_t length); +int dstresize(dchar **dstr, size_t length); /** * Append to a dynamic string. @@ -100,7 +119,7 @@ int dstresize(char **dstr, size_t length); * The string to append. * @return 0 on success, -1 on failure. */ -int dstrcat(char **dest, const char *src); +int dstrcat(dchar **dest, const char *src); /** * Append to a dynamic string. @@ -114,7 +133,7 @@ int dstrcat(char **dest, const char *src); * @return * 0 on success, -1 on failure. */ -int dstrncat(char **dest, const char *src, size_t n); +int dstrncat(dchar **dest, const char *src, size_t n); /** * Append a dynamic string to another dynamic string. @@ -126,7 +145,7 @@ int dstrncat(char **dest, const char *src, size_t n); * @return * 0 on success, -1 on failure. */ -int dstrdcat(char **dest, const char *src); +int dstrdcat(dchar **dest, const dchar *src); /** * Append to a dynamic string. @@ -140,7 +159,7 @@ int dstrdcat(char **dest, const char *src); * @return * 0 on success, -1 on failure. */ -int dstrxcat(char **dest, const char *src, size_t len); +int dstrxcat(dchar **dest, const char *src, size_t len); /** * Append a single character to a dynamic string. @@ -152,7 +171,7 @@ int dstrxcat(char **dest, const char *src, size_t len); * @return * 0 on success, -1 on failure. */ -int dstrapp(char **str, char c); +int dstrapp(dchar **str, char c); /** * Copy a string into a dynamic string. @@ -164,7 +183,7 @@ int dstrapp(char **str, char c); * @returns * 0 on success, -1 on failure. */ -int dstrcpy(char **dest, const char *str); +int dstrcpy(dchar **dest, const char *str); /** * Copy a dynamic string into another one. @@ -176,7 +195,7 @@ int dstrcpy(char **dest, const char *str); * @returns * 0 on success, -1 on failure. */ -int dstrdcpy(char **dest, const char *str); +int dstrdcpy(dchar **dest, const dchar *str); /** * Copy a string into a dynamic string. @@ -190,7 +209,7 @@ int dstrdcpy(char **dest, const char *str); * @returns * 0 on success, -1 on failure. */ -int dstrncpy(char **dest, const char *str, size_t n); +int dstrncpy(dchar **dest, const char *str, size_t n); /** * Copy a string into a dynamic string. @@ -204,7 +223,7 @@ int dstrncpy(char **dest, const char *str, size_t n); * @returns * 0 on success, -1 on failure. */ -int dstrxcpy(char **dest, const char *str, size_t len); +int dstrxcpy(dchar **dest, const char *str, size_t len); /** * Create a dynamic string from a format string. @@ -245,7 +264,7 @@ char *dstrvprintf(const char *format, va_list args); * 0 on success, -1 on failure. */ BFS_FORMATTER(2, 3) -int dstrcatf(char **str, const char *format, ...); +int dstrcatf(dchar **str, const char *format, ...); /** * Format some text from a va_list onto the end of a dynamic string. @@ -260,7 +279,7 @@ int dstrcatf(char **str, const char *format, ...); * 0 on success, -1 on failure. */ BFS_FORMATTER(2, 0) -int dstrvcatf(char **str, const char *format, va_list args); +int dstrvcatf(dchar **str, const char *format, va_list args); /** * Concatenate while shell-escaping. @@ -274,7 +293,7 @@ int dstrvcatf(char **str, const char *format, va_list args); * @return * 0 on success, -1 on failure. */ -int dstrescat(char **dest, const char *str, enum wesc_flags flags); +int dstrescat(dchar **dest, const char *str, enum wesc_flags flags); /** * Concatenate while shell-escaping. @@ -290,7 +309,7 @@ int dstrescat(char **dest, const char *str, enum wesc_flags flags); * @return * 0 on success, -1 on failure. */ -int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags); +int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags); /** * Free a dynamic string. @@ -298,6 +317,6 @@ int dstrnescat(char **dest, const char *str, size_t n, enum wesc_flags flags); * @param dstr * The string to free. */ -void dstrfree(char *dstr); +void dstrfree(dchar *dstr); #endif // BFS_DSTRING_H diff --git a/src/eval.c b/src/eval.c index 0faf533..3550751 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1115,7 +1115,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time const struct BFTW *ftwbuf = state->ftwbuf; - char *rhs = dstrprintf(" (visited: %zu, depth: %2zu)", count, ftwbuf->depth); + dchar *rhs = dstrprintf(" (visited: %zu, depth: %2zu)", count, ftwbuf->depth); if (!rhs) { return; } @@ -1126,7 +1126,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time rhslen = 0; } - char *status = dstralloc(0); + dchar *status = dstralloc(0); if (!status) { goto out_rhs; } diff --git a/src/exec.c b/src/exec.c index ea7f897..7b55522 100644 --- a/src/exec.c +++ b/src/exec.c @@ -227,7 +227,7 @@ static char *bfs_exec_format_arg(char *arg, const char *path) { return arg; } - char *ret = dstralloc(0); + dchar *ret = dstralloc(0); if (!ret) { return NULL; } @@ -259,7 +259,7 @@ err: /** Free a formatted argument. */ static void bfs_exec_free_arg(char *arg, const char *tmpl) { if (arg != tmpl) { - dstrfree(arg); + dstrfree((dchar *)arg); } } diff --git a/src/fsade.c b/src/fsade.c index c401426..8dec5a8 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -37,7 +37,7 @@ static const char *fake_at(const struct BFTW *ftwbuf) { static atomic int proc_works = -1; - char *path = NULL; + dchar *path = NULL; if (ftwbuf->at_fd == AT_FDCWD || load(&proc_works, relaxed) == 0) { goto fail; } @@ -69,7 +69,7 @@ fail: static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { - dstrfree((char *)path); + dstrfree((dchar *)path); } } diff --git a/src/printf.c b/src/printf.c index f0910fa..5de5a28 100644 --- a/src/printf.c +++ b/src/printf.c @@ -38,7 +38,7 @@ struct bfs_printf { /** The printing function to invoke. */ bfs_printf_fn *fn; /** String data associated with this directive. */ - char *str; + dchar *str; /** The stat field to print. */ enum bfs_stat_field stat_field; /** Character data associated with this directive. */ @@ -596,7 +596,7 @@ static int append_directive(const struct bfs_ctx *ctx, struct bfs_printf **forma int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const char *format) { expr->printf = NULL; - char *literal = dstralloc(0); + dchar *literal = dstralloc(0); if (!literal) { bfs_perror(ctx, "dstralloc()"); goto error; -- cgit v1.2.3 From e7c3e124a13d95dcdf9ae2f1408b4780d2037401 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 26 Sep 2023 15:05:43 -0400 Subject: bit: Use predefined __*_WIDTH__ macros if we can --- src/bit.h | 93 ++++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 69 insertions(+), 24 deletions(-) diff --git a/src/bit.h b/src/bit.h index 8cde9b3..e50a776 100644 --- a/src/bit.h +++ b/src/bit.h @@ -53,51 +53,96 @@ #ifndef CHAR_WIDTH # define CHAR_WIDTH CHAR_BIT #endif + +// See https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html + +#ifndef USHRT_WIDTH +# ifdef __SHRT_WIDTH__ +# define USHRT_WIDTH __SHRT_WIDTH__ +# else +# define USHRT_WIDTH UMAX_WIDTH(USHRT_MAX) +# endif +#endif + +#ifndef UINT_WIDTH +# ifdef __INT_WIDTH__ +# define UINT_WIDTH __INT_WIDTH__ +# else +# define UINT_WIDTH UMAX_WIDTH(UINT_MAX) +# endif +#endif + +#ifndef ULONG_WIDTH +# ifdef __LONG_WIDTH__ +# define ULONG_WIDTH __LONG_WIDTH__ +# else +# define ULONG_WIDTH UMAX_WIDTH(ULONG_MAX) +# endif +#endif + +#ifndef ULLONG_WIDTH +# ifdef __LONG_LONG_WIDTH__ +# define ULLONG_WIDTH __LONG_LONG_WIDTH__ +# elif defined(__LLONG_WIDTH__) // Clang +# define ULLONG_WIDTH __LLONG_WIDTH__ +# else +# define ULLONG_WIDTH UMAX_WIDTH(ULLONG_MAX) +# endif +#endif + +#ifndef SIZE_WIDTH +# ifdef __SIZE_WIDTH__ +# define SIZE_WIDTH __SIZE_WIDTH__ +# else +# define SIZE_WIDTH UMAX_WIDTH(SIZE_MAX) +# endif +#endif + +#ifndef PTRDIFF_WIDTH +# ifdef __PTRDIFF_WIDTH__ +# define PTRDIFF_WIDTH __PTRDIFF_WIDTH__ +# else +# define PTRDIFF_WIDTH UMAX_WIDTH(PTRDIFF_MAX) +# endif +#endif + +#ifndef UINTPTR_WIDTH +# ifdef __INTPTR_WIDTH__ +# define INTPTR_WIDTH __INTPTR_WIDTH__ +# else +# define UINTPTR_WIDTH UMAX_WIDTH(UINTPTR_MAX) +# endif +#endif + +#ifndef UINTMAX_WIDTH +# ifdef __INTMAX_WIDTH__ +# define UINTMAX_WIDTH __INTMAX_WIDTH__ +# else +# define UINTMAX_WIDTH UMAX_WIDTH(UINTMAX_MAX) +# endif +#endif + #ifndef UCHAR_WIDTH # define UCHAR_WIDTH CHAR_WIDTH #endif #ifndef SCHAR_WIDTH # define SCHAR_WIDTH CHAR_WIDTH #endif -#ifndef USHRT_WIDTH -# define USHRT_WIDTH UMAX_WIDTH(USHRT_MAX) -#endif #ifndef SHRT_WIDTH # define SHRT_WIDTH USHRT_WIDTH #endif -#ifndef UINT_WIDTH -# define UINT_WIDTH UMAX_WIDTH(UINT_MAX) -#endif #ifndef INT_WIDTH # define INT_WIDTH UINT_WIDTH #endif -#ifndef ULONG_WIDTH -# define ULONG_WIDTH UMAX_WIDTH(ULONG_MAX) -#endif #ifndef LONG_WIDTH # define LONG_WIDTH ULONG_WIDTH #endif -#ifndef ULLONG_WIDTH -# define ULLONG_WIDTH UMAX_WIDTH(ULLONG_MAX) -#endif #ifndef LLONG_WIDTH # define LLONG_WIDTH ULLONG_WIDTH #endif -#ifndef SIZE_WIDTH -# define SIZE_WIDTH UMAX_WIDTH(SIZE_MAX) -#endif -#ifndef PTRDIFF_WIDTH -# define PTRDIFF_WIDTH (UMAX_WIDTH(PTRDIFF_MAX) + 1) -#endif -#ifndef UINTPTR_WIDTH -# define UINTPTR_WIDTH UMAX_WIDTH(UINTPTR_MAX) -#endif #ifndef INTPTR_WIDTH # define INTPTR_WIDTH UINTPTR_WIDTH #endif -#ifndef UINTMAX_WIDTH -# define UINTMAX_WIDTH UMAX_WIDTH(UINTMAX_MAX) -#endif #ifndef INTMAX_WIDTH # define INTMAX_WIDTH UINTMAX_WIDTH #endif -- cgit v1.2.3 From b395bb33e6f7d875307b18a4f9318ed0d34934ca Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 26 Sep 2023 15:42:35 -0400 Subject: bit: Fix UINTPTR_WIDTH typo --- src/bit.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bit.h b/src/bit.h index e50a776..3f756f6 100644 --- a/src/bit.h +++ b/src/bit.h @@ -108,7 +108,7 @@ #ifndef UINTPTR_WIDTH # ifdef __INTPTR_WIDTH__ -# define INTPTR_WIDTH __INTPTR_WIDTH__ +# define UINTPTR_WIDTH __INTPTR_WIDTH__ # else # define UINTPTR_WIDTH UMAX_WIDTH(UINTPTR_MAX) # endif -- cgit v1.2.3 From 52de184ba28551734e1cb13233588504ab5f62ec Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Sep 2023 12:11:15 -0400 Subject: Formatting fixes --- src/color.c | 6 +++--- src/ctx.c | 1 - src/darray.c | 4 ++-- src/diag.c | 8 ++++---- src/diag.h | 2 +- src/dstring.c | 4 ++-- src/eval.c | 15 +++++++-------- src/exec.c | 16 ++++++++-------- src/fsade.c | 6 +++--- src/ioq.c | 2 +- src/opt.c | 11 +++++------ src/opt.h | 1 - src/parse.c | 45 ++++++++++++++++++++++----------------------- src/printf.c | 46 +++++++++++++++++++++++----------------------- src/pwcache.c | 1 - src/stat.c | 2 +- src/trie.c | 2 +- src/trie.h | 4 ++-- src/xregex.c | 2 +- src/xspawn.c | 2 +- src/xtime.c | 12 ++++++------ tests/bfstd.c | 2 +- tests/mksock.c | 2 +- tests/trie.c | 4 ++-- tests/xtimegm.c | 6 +++--- 25 files changed, 100 insertions(+), 106 deletions(-) diff --git a/src/color.c b/src/color.c index 8d0b995..788d35d 100644 --- a/src/color.c +++ b/src/color.c @@ -1143,11 +1143,11 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { if (verbose) { double rate = 0.0, time = 0.0; if (expr->evaluations) { - rate = 100.0*expr->successes/expr->evaluations; - time = (1.0e9*expr->elapsed.tv_sec + expr->elapsed.tv_nsec)/expr->evaluations; + rate = 100.0 * expr->successes / expr->evaluations; + time = (1.0e9 * expr->elapsed.tv_sec + expr->elapsed.tv_nsec) / expr->evaluations; } if (cbuff(cfile, " [${ylw}%zu${rs}/${ylw}%zu${rs}=${ylw}%g%%${rs}; ${ylw}%gns${rs}]", - expr->successes, expr->evaluations, rate, time)) { + expr->successes, expr->evaluations, rate, time)) { return -1; } } diff --git a/src/ctx.c b/src/ctx.c index 3a44e68..9a24a33 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -168,7 +168,6 @@ void bfs_ctx_flush(const struct bfs_ctx *ctx) { } else if (cfile == ctx->cout) { bfs_error(ctx, "(standard output): %m.\n"); } - } // Flush the user/group caches, in case the executed command edits the diff --git a/src/darray.c b/src/darray.c index 42b8397..3e66a55 100644 --- a/src/darray.c +++ b/src/darray.c @@ -55,7 +55,7 @@ void *darray_push(void *da, const void *item, size_t size) { size_t i = header->length++; if (i >= capacity) { capacity *= 2; - header = realloc(header, sizeof(*header) + capacity*size); + header = realloc(header, sizeof(*header) + capacity * size); if (!header) { // This failure will be detected by darray_check() return da; @@ -64,7 +64,7 @@ void *darray_push(void *da, const void *item, size_t size) { } char *data = darray_data(header); - memcpy(data + i*size, item, size); + memcpy(data + i * size, item, size); return data; } diff --git a/src/diag.c b/src/diag.c index fa9db39..fa66525 100644 --- a/src/diag.c +++ b/src/diag.c @@ -4,9 +4,9 @@ #include "diag.h" #include "alloc.h" #include "bfstd.h" -#include "ctx.h" #include "color.h" #include "config.h" +#include "ctx.h" #include "dstring.h" #include "expr.h" #include @@ -31,14 +31,14 @@ void bfs_perror(const struct bfs_ctx *ctx, const char *str) { bfs_error(ctx, "%s: %m.\n", str); } -void bfs_error(const struct bfs_ctx *ctx, const char *format, ...) { +void bfs_error(const struct bfs_ctx *ctx, const char *format, ...) { va_list args; va_start(args, format); bfs_verror(ctx, format, args); va_end(args); } -bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...) { +bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...) { va_list args; va_start(args, format); bool ret = bfs_vwarning(ctx, format, args); @@ -46,7 +46,7 @@ bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...) { return ret; } -bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, ...) { +bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, ...) { va_list args; va_start(args, format); bool ret = bfs_vdebug(ctx, flag, format, args); diff --git a/src/diag.h b/src/diag.h index e019db0..fea8847 100644 --- a/src/diag.h +++ b/src/diag.h @@ -8,8 +8,8 @@ #ifndef BFS_DIAG_H #define BFS_DIAG_H -#include "ctx.h" #include "config.h" +#include "ctx.h" #include /** diff --git a/src/dstring.c b/src/dstring.c index ef4e733..f947741 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -169,7 +169,7 @@ char *dstrprintf(const char *format, ...) { char *dstrvprintf(const char *format, va_list args) { // Guess a capacity to try to avoid reallocating - dchar *str = dstralloc(2*strlen(format)); + dchar *str = dstralloc(2 * strlen(format)); if (!str) { return NULL; } @@ -195,7 +195,7 @@ int dstrcatf(dchar **str, const char *format, ...) { int dstrvcatf(dchar **str, const char *format, va_list args) { // Guess a capacity to try to avoid calling vsnprintf() twice size_t len = dstrlen(*str); - dstreserve(str, len + 2*strlen(format)); + dstreserve(str, len + 2 * strlen(format)); size_t cap = dstrheader(*str)->capacity; va_list copy; diff --git a/src/eval.c b/src/eval.c index 3550751..9f4896a 100644 --- a/src/eval.c +++ b/src/eval.c @@ -239,7 +239,7 @@ bool eval_time(const struct bfs_expr *expr, struct bfs_eval *state) { time_t diff = timespec_diff(&expr->reftime, time); switch (expr->time_unit) { case BFS_DAYS: - diff /= 60*24; + diff /= 60 * 24; fallthru; case BFS_MINUTES: diff /= 60; @@ -271,7 +271,7 @@ bool eval_used(const struct bfs_expr *expr, struct bfs_eval *state) { return false; } - long long day_seconds = 60*60*24; + long long day_seconds = 60 * 60 * 24; diff = (diff + day_seconds - 1) / day_seconds; return bfs_expr_cmp(expr, diff); } @@ -685,7 +685,7 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { uintmax_t ino = statbuf->ino; uintmax_t block_size = ctx->posixly_correct ? 512 : 1024; - uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + block_size - 1)/block_size; + uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + block_size - 1) / block_size; char mode[11]; xstrmode(statbuf->mode, mode); char acl = bfs_check_acl(ftwbuf) > 0 ? '+' : ' '; @@ -721,8 +721,8 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { time_t time = statbuf->mtime.tv_sec; time_t now = ctx->now.tv_sec; - time_t six_months_ago = now - 6*30*24*60*60; - time_t tomorrow = now + 24*60*60; + time_t six_months_ago = now - 6 * 30 * 24 * 60 * 60; + time_t tomorrow = now + 24 * 60 * 60; struct tm tm; if (xlocaltime(&time, &tm) != 0) { goto error; @@ -823,7 +823,6 @@ bool eval_fprintx(const struct bfs_expr *expr, struct bfs_eval *state) { ++path; } - if (fputc('\n', file) == EOF) { goto error; } @@ -905,7 +904,7 @@ bool eval_size(const struct bfs_expr *expr, struct bfs_eval *state) { }; off_t scale = scales[expr->size_unit]; - off_t size = (statbuf->size + scale - 1)/scale; // Round up + off_t size = (statbuf->size + scale - 1) / scale; // Round up return bfs_expr_cmp(expr, size); } @@ -918,7 +917,7 @@ bool eval_sparse(const struct bfs_expr *expr, struct bfs_eval *state) { return false; } - blkcnt_t expected = (statbuf->size + BFS_STAT_BLKSIZE - 1)/BFS_STAT_BLKSIZE; + blkcnt_t expected = (statbuf->size + BFS_STAT_BLKSIZE - 1) / BFS_STAT_BLKSIZE; return statbuf->blocks < expected; } diff --git a/src/exec.c b/src/exec.c index 7b55522..0e0d585 100644 --- a/src/exec.c +++ b/src/exec.c @@ -5,9 +5,9 @@ #include "alloc.h" #include "bfstd.h" #include "bftw.h" -#include "ctx.h" #include "color.h" #include "config.h" +#include "ctx.h" #include "diag.h" #include "dstring.h" #include "xspawn.h" @@ -348,7 +348,7 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { if (execbuf->flags & BFS_EXEC_MULTI) { bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments] (size %zu)\n", - execbuf->argv[0], execbuf->argc - 1, execbuf->arg_size); + execbuf->argv[0], execbuf->argc - 1, execbuf->arg_size); } else { bfs_exec_debug(execbuf, "Executing '%s' ... [%zu arguments]\n", execbuf->argv[0], execbuf->argc - 1); } @@ -471,7 +471,7 @@ static bool bfs_exec_args_remain(const struct bfs_exec *execbuf) { static size_t bfs_exec_estimate_max(const struct bfs_exec *execbuf) { size_t min = execbuf->arg_min; size_t max = execbuf->arg_max; - return min + (max - min)/2; + return min + (max - min) / 2; } /** Update the ARG_MAX lower bound from a successful execution. */ @@ -486,7 +486,7 @@ static void bfs_exec_update_min(struct bfs_exec *execbuf) { size_t estimate = bfs_exec_estimate_max(execbuf); bfs_exec_debug(execbuf, "ARG_MAX between [%zu, %zu], trying %zu\n", - execbuf->arg_min, execbuf->arg_max, estimate); + execbuf->arg_min, execbuf->arg_max, estimate); } } @@ -502,7 +502,7 @@ static size_t bfs_exec_update_max(struct bfs_exec *execbuf) { // Trim a fraction off the max size to avoid repeated failures near the // top end of the working range - size -= size/16; + size -= size / 16; if (size < execbuf->arg_max) { execbuf->arg_max = size; @@ -515,7 +515,7 @@ static size_t bfs_exec_update_max(struct bfs_exec *execbuf) { // Binary search for a more precise bound size_t estimate = bfs_exec_estimate_max(execbuf); bfs_exec_debug(execbuf, "ARG_MAX between [%zu, %zu], trying %zu\n", - execbuf->arg_min, execbuf->arg_max, estimate); + execbuf->arg_min, execbuf->arg_max, estimate); return estimate; } @@ -589,7 +589,7 @@ static bool bfs_exec_would_overflow(const struct bfs_exec *execbuf, const char * size_t next_size = execbuf->arg_size + bfs_exec_arg_size(arg); if (next_size > arg_max) { bfs_exec_debug(execbuf, "Command size (%zu) would exceed maximum (%zu), executing buffered command\n", - next_size, arg_max); + next_size, arg_max); return true; } @@ -601,7 +601,7 @@ static int bfs_exec_push(struct bfs_exec *execbuf, char *arg) { execbuf->argv[execbuf->argc] = arg; if (execbuf->argc + 1 >= execbuf->argv_cap) { - size_t cap = 2*execbuf->argv_cap; + size_t cap = 2 * execbuf->argv_cap; char **argv = realloc(execbuf->argv, sizeof_array(char *, cap)); if (!argv) { return -1; diff --git a/src/fsade.c b/src/fsade.c index 8dec5a8..cbff47b 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -3,9 +3,9 @@ #include "fsade.h" #include "atomic.h" -#include "config.h" #include "bfstd.h" #include "bftw.h" +#include "config.h" #include "dir.h" #include "dstring.h" #include "sanity.h" @@ -292,7 +292,7 @@ int bfs_check_xattrs(const struct BFTW *ftwbuf) { ssize_t len; #if BFS_USE_SYS_EXTATTR_H - ssize_t (*extattr_list)(const char *, int, void*, size_t) = + ssize_t (*extattr_list)(const char *, int, void *, size_t) = ftwbuf->type == BFS_LNK ? extattr_list_link : extattr_list_file; len = extattr_list(path, EXTATTR_NAMESPACE_SYSTEM, NULL, 0); @@ -331,7 +331,7 @@ int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { ssize_t len; #if BFS_USE_SYS_EXTATTR_H - ssize_t (*extattr_get)(const char *, int, const char *, void*, size_t) = + ssize_t (*extattr_get)(const char *, int, const char *, void *, size_t) = ftwbuf->type == BFS_LNK ? extattr_get_link : extattr_get_file; len = extattr_get(path, EXTATTR_NAMESPACE_SYSTEM, name, NULL, 0); diff --git a/src/ioq.c b/src/ioq.c index f7ca8c6..d3ba2de 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -9,8 +9,8 @@ #include "config.h" #include "diag.h" #include "dir.h" -#include "thread.h" #include "sanity.h" +#include "thread.h" #include #include #include diff --git a/src/opt.c b/src/opt.c index 14de081..77c2798 100644 --- a/src/opt.c +++ b/src/opt.c @@ -535,8 +535,8 @@ static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct expr->pure = lhs->pure && rhs->pure; expr->always_true = lhs->always_true && rhs->always_true; expr->always_false = lhs->always_false || rhs->always_false; - expr->cost = lhs->cost + lhs->probability*rhs->cost; - expr->probability = lhs->probability*rhs->probability; + expr->cost = lhs->cost + lhs->probability * rhs->cost; + expr->probability = lhs->probability * rhs->probability; return expr; } @@ -606,8 +606,8 @@ static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct b expr->pure = lhs->pure && rhs->pure; expr->always_true = lhs->always_true || rhs->always_true; expr->always_false = lhs->always_false && rhs->always_false; - expr->cost = lhs->cost + (1 - lhs->probability)*rhs->cost; - expr->probability = lhs->probability + rhs->probability - lhs->probability*rhs->probability; + expr->cost = lhs->cost + (1 - lhs->probability) * rhs->cost; + expr->probability = lhs->probability + rhs->probability - lhs->probability * rhs->probability; return expr; } @@ -1078,7 +1078,6 @@ static const struct { {eval_xattrname, 0.01}, }; - /** * Table of simple predicates. */ @@ -1331,7 +1330,7 @@ static bool reorder_expr_recursive(const struct opt_state *state, struct bfs_exp if (expr->eval_fn == eval_and || expr->eval_fn == eval_or) { if (lhs->pure && rhs->pure) { float rhs_prob = expr->eval_fn == eval_and ? rhs->probability : 1.0 - rhs->probability; - float swapped_cost = rhs->cost + rhs_prob*lhs->cost; + float swapped_cost = rhs->cost + rhs_prob * lhs->cost; ret |= reorder_expr(state, expr, swapped_cost); } } diff --git a/src/opt.h b/src/opt.h index 28cadb9..4aac129 100644 --- a/src/opt.h +++ b/src/opt.h @@ -21,4 +21,3 @@ struct bfs_ctx; int bfs_optimize(struct bfs_ctx *ctx); #endif // BFS_OPT_H - diff --git a/src/parse.c b/src/parse.c index 3416d9e..7766a7b 100644 --- a/src/parse.c +++ b/src/parse.c @@ -41,8 +41,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -1797,10 +1797,10 @@ static int parse_reftime(const struct parser_state *state, struct bfs_expr *expr #else int gmtoff = -timezone; #endif - int tz_hour = gmtoff/3600; - int tz_min = (labs(gmtoff)/60)%60; + int tz_hour = gmtoff / 3600; + int tz_min = (labs(gmtoff) / 60) % 60; fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d%+03d:%02d\n", - year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz_hour, tz_min); + year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz_hour, tz_min); if (xgmtime(&state->now.tv_sec, &tm) != 0) { parse_perror(state, "xgmtime()"); @@ -1832,8 +1832,8 @@ static struct bfs_expr *parse_newerxy(struct parser_state *state, int arg1, int expr->stat_field = parse_newerxy_field(arg[6]); if (!expr->stat_field) { parse_expr_error(state, expr, - "For ${blu}-newer${bld}XY${rs}, ${bld}X${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, or ${bld}B${rs}, not ${err}%c${rs}.\n", - arg[6]); + "For ${blu}-newer${bld}XY${rs}, ${bld}X${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, or ${bld}B${rs}, not ${err}%c${rs}.\n", + arg[6]); goto fail; } @@ -1845,8 +1845,8 @@ static struct bfs_expr *parse_newerxy(struct parser_state *state, int arg1, int enum bfs_stat_field field = parse_newerxy_field(arg[7]); if (!field) { parse_expr_error(state, expr, - "For ${blu}-newer${bld}XY${rs}, ${bld}Y${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, ${bld}B${rs}, or ${bld}t${rs}, not ${err}%c${rs}.\n", - arg[7]); + "For ${blu}-newer${bld}XY${rs}, ${bld}Y${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, ${bld}B${rs}, or ${bld}t${rs}, not ${err}%c${rs}.\n", + arg[7]); goto fail; } @@ -1855,7 +1855,6 @@ static struct bfs_expr *parse_newerxy(struct parser_state *state, int arg1, int goto fail; } - const struct timespec *reftime = bfs_stat_time(&sb, field); if (!reftime) { parse_expr_error(state, expr, "Couldn't get file %s.\n", bfs_stat_field_name(field)); @@ -1905,7 +1904,7 @@ static struct bfs_expr *parse_nohidden(struct parser_state *state, int arg1, int */ static struct bfs_expr *parse_noleaf(struct parser_state *state, int arg1, int arg2) { parse_warning(state, "${ex}%s${rs} does not apply the optimization that ${blu}%s${rs} inhibits.\n\n", - BFS_COMMAND, state->argv[0]); + BFS_COMMAND, state->argv[0]); return parse_nullary_option(state); } @@ -2730,12 +2729,12 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg } cfprintf(cout, "Usage: ${ex}%s${rs} [${cyn}flags${rs}...] [${mag}paths${rs}...] [${blu}expression${rs}...]\n\n", - state->command); + state->command); cfprintf(cout, "${ex}%s${rs} is compatible with ${ex}find${rs}, with some extensions. " - "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" - "and ${blu}expressions${rs} may be freely mixed in any order.\n\n", - BFS_COMMAND); + "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" + "and ${blu}expressions${rs} may be freely mixed in any order.\n\n", + BFS_COMMAND); cfprintf(cout, "${bld}Flags:${rs}\n\n"); @@ -2807,7 +2806,7 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg cfprintf(cout, " ${blu}-ignore_readdir_race${rs}\n"); cfprintf(cout, " ${blu}-noignore_readdir_race${rs}\n"); cfprintf(cout, " Whether to report an error if ${ex}%s${rs} detects that the file tree is modified\n", - BFS_COMMAND); + BFS_COMMAND); cfprintf(cout, " during the search (default: ${blu}-noignore_readdir_race${rs})\n"); cfprintf(cout, " ${blu}-maxdepth${rs} ${bld}N${rs}\n"); cfprintf(cout, " ${blu}-mindepth${rs} ${bld}N${rs}\n"); @@ -3462,14 +3461,14 @@ static struct bfs_expr *parse_whole_expr(struct parser_state *state) { if (state->mount_arg && state->xdev_arg) { parse_conflict_warning(state, state->mount_arg, 1, state->xdev_arg, 1, - "${blu}%s${rs} is redundant in the presence of ${blu}%s${rs}.\n\n", - state->xdev_arg[0], state->mount_arg[0]); + "${blu}%s${rs} is redundant in the presence of ${blu}%s${rs}.\n\n", + state->xdev_arg[0], state->mount_arg[0]); } if (state->ctx->warn && state->depth_arg && state->prune_arg) { parse_conflict_warning(state, state->depth_arg, 1, state->prune_arg, 1, - "${blu}%s${rs} does not work in the presence of ${blu}%s${rs}.\n", - state->prune_arg[0], state->depth_arg[0]); + "${blu}%s${rs} does not work in the presence of ${blu}%s${rs}.\n", + state->prune_arg[0], state->depth_arg[0]); if (state->interactive) { bfs_warning(state->ctx, "Do you want to continue? "); @@ -3483,8 +3482,8 @@ static struct bfs_expr *parse_whole_expr(struct parser_state *state) { if (state->ok_expr && state->files0_stdin_arg) { parse_conflict_error(state, state->ok_expr->argv, state->ok_expr->argc, state->files0_stdin_arg, 2, - "${blu}%s${rs} conflicts with ${blu}%s${rs} ${bld}%s${rs}.\n", - state->ok_expr->argv[0], state->files0_stdin_arg[0], state->files0_stdin_arg[1]); + "${blu}%s${rs} conflicts with ${blu}%s${rs} ${bld}%s${rs}.\n", + state->ok_expr->argv[0], state->files0_stdin_arg[0], state->files0_stdin_arg[1]); goto fail; } @@ -3644,7 +3643,7 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { static void dump_costs(const struct bfs_ctx *ctx) { const struct bfs_expr *expr = ctx->expr; bfs_debug(ctx, DEBUG_COST, " Cost: ~${ylw}%g${rs}\n", expr->cost); - bfs_debug(ctx, DEBUG_COST, "Probability: ~${ylw}%g%%${rs}\n", 100.0*expr->probability); + bfs_debug(ctx, DEBUG_COST, "Probability: ~${ylw}%g%%${rs}\n", 100.0 * expr->probability); } struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { @@ -3654,7 +3653,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { goto fail; } - static char* default_argv[] = {BFS_COMMAND, NULL}; + static char *default_argv[] = {BFS_COMMAND, NULL}; if (argc < 1) { argc = 1; argv = default_argv; diff --git a/src/printf.c b/src/printf.c index 5de5a28..98bcb0f 100644 --- a/src/printf.c +++ b/src/printf.c @@ -113,14 +113,14 @@ static int bfs_printf_ctime(CFILE *cfile, const struct bfs_printf *directive, co } BFS_PRINTF_BUF(buf, "%s %s %2d %.2d:%.2d:%.2d.%09ld0 %4d", - days[tm.tm_wday], - months[tm.tm_mon], - tm.tm_mday, - tm.tm_hour, - tm.tm_min, - tm.tm_sec, - (long)ts->tv_nsec, - 1900 + tm.tm_year); + days[tm.tm_wday], + months[tm.tm_mon], + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + (long)ts->tv_nsec, + 1900 + tm.tm_year); return dyn_fprintf(cfile->file, directive, buf); } @@ -152,19 +152,19 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, break; case '+': ret = snprintf(buf, sizeof(buf), "%4d-%.2d-%.2d+%.2d:%.2d:%.2d.%09ld0", - 1900 + tm.tm_year, - tm.tm_mon + 1, - tm.tm_mday, - tm.tm_hour, - tm.tm_min, - tm.tm_sec, - (long)ts->tv_nsec); + 1900 + tm.tm_year, + tm.tm_mon + 1, + tm.tm_mday, + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + (long)ts->tv_nsec); break; case 'k': ret = snprintf(buf, sizeof(buf), "%2d", tm.tm_hour); break; case 'l': - ret = snprintf(buf, sizeof(buf), "%2d", (tm.tm_hour + 11)%12 + 1); + ret = snprintf(buf, sizeof(buf), "%2d", (tm.tm_hour + 11) % 12 + 1); break; case 's': ret = snprintf(buf, sizeof(buf), "%lld", (long long)ts->tv_sec); @@ -174,10 +174,10 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, break; case 'T': ret = snprintf(buf, sizeof(buf), "%.2d:%.2d:%.2d.%09ld0", - tm.tm_hour, - tm.tm_min, - tm.tm_sec, - (long)ts->tv_nsec); + tm.tm_hour, + tm.tm_min, + tm.tm_sec, + (long)ts->tv_nsec); break; // POSIX strftime() features @@ -202,7 +202,7 @@ static int bfs_printf_b(CFILE *cfile, const struct bfs_printf *directive, const return -1; } - uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 511)/512; + uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + 511) / 512; BFS_PRINTF_BUF(buf, "%ju", blocks); return dyn_fprintf(cfile->file, directive, buf); } @@ -338,7 +338,7 @@ static int bfs_printf_k(CFILE *cfile, const struct bfs_printf *directive, const return -1; } - uintmax_t blocks = ((uintmax_t)statbuf->blocks*BFS_STAT_BLKSIZE + 1023)/1024; + uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + 1023) / 1024; BFS_PRINTF_BUF(buf, "%ju", blocks); return dyn_fprintf(cfile->file, directive, buf); } @@ -452,7 +452,7 @@ static int bfs_printf_S(CFILE *cfile, const struct bfs_printf *directive, const if (statbuf->size == 0 && statbuf->blocks == 0) { sparsity = 1.0; } else { - sparsity = (double)BFS_STAT_BLKSIZE*statbuf->blocks/statbuf->size; + sparsity = (double)BFS_STAT_BLKSIZE * statbuf->blocks / statbuf->size; } return dyn_fprintf(cfile->file, directive, sparsity); } diff --git a/src/pwcache.c b/src/pwcache.c index 0e2f5c1..c728ba9 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -4,7 +4,6 @@ #include "pwcache.h" #include "alloc.h" #include "config.h" -#include "darray.h" #include "trie.h" #include #include diff --git a/src/stat.c b/src/stat.c index e8f48ee..d7387c6 100644 --- a/src/stat.c +++ b/src/stat.c @@ -10,8 +10,8 @@ #include #include #include -#include #include +#include #if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30) # define BFS_HAS_LIBC_STATX true diff --git a/src/trie.c b/src/trie.c index 77aa2d0..55544e6 100644 --- a/src/trie.c +++ b/src/trie.c @@ -700,7 +700,7 @@ static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { } if (child_index < parent_size) { - memmove(child, child + 1, (parent_size - child_index)*sizeof(*child)); + memmove(child, child + 1, (parent_size - child_index) * sizeof(*child)); } if (has_single_bit(parent_size)) { diff --git a/src/trie.h b/src/trie.h index 2f51db5..02088f1 100644 --- a/src/trie.h +++ b/src/trie.h @@ -4,8 +4,8 @@ #ifndef BFS_TRIE_H #define BFS_TRIE_H -#include "config.h" #include "alloc.h" +#include "config.h" #include "list.h" #include #include @@ -143,6 +143,6 @@ void trie_destroy(struct trie *trie); * Iterate over the leaves of a trie. */ #define for_trie(leaf, trie) \ - for_list(struct trie_leaf, leaf, trie) + for_list (struct trie_leaf, leaf, trie) #endif // BFS_TRIE_H diff --git a/src/xregex.c b/src/xregex.c index beb6676..b9c04bf 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -5,8 +5,8 @@ #include "alloc.h" #include "config.h" #include "diag.h" -#include "thread.h" #include "sanity.h" +#include "thread.h" #include #include #include diff --git a/src/xspawn.c b/src/xspawn.c index 80bafef..7fb63e0 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -187,7 +187,7 @@ fail: // In case of a write error, the parent will still see that we exited // unsuccessfully, but won't know why - (void) xwrite(pipefd[1], &error, sizeof(error)); + (void)xwrite(pipefd[1], &error, sizeof(error)); xclose(pipefd[1]); _Exit(127); diff --git a/src/xtime.c b/src/xtime.c index 79dafad..e90bdb1 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -81,7 +81,7 @@ static int safe_add(int *value, int delta) { static int floor_div(int n, int d) { int a = n < 0; - return (n + a)/d - a; + return (n + a) / d - a; } static int wrap(int *value, int max, int *next) { @@ -93,7 +93,7 @@ static int wrap(int *value, int max, int *next) { static int month_length(int year, int month) { static const int month_lengths[] = {31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; int ret = month_lengths[month]; - if (month == 1 && year%4 == 0 && (year%100 != 0 || (year + 300)%400 == 0)) { + if (month == 1 && year % 4 == 0 && (year % 100 != 0 || (year + 300) % 400 == 0)) { ++ret; } return ret; @@ -156,13 +156,13 @@ int xtimegm(struct tm *tm, time_t *timep) { leap_days = floor_div(tm->tm_year + 3, 4) - floor_div(tm->tm_year + 99, 100) + floor_div(tm->tm_year + 299, 400) - 17; } - long long epoch_days = 365LL*(tm->tm_year - 70) + leap_days + tm->tm_yday; - tm->tm_wday = (epoch_days + 4)%7; + long long epoch_days = 365LL * (tm->tm_year - 70) + leap_days + tm->tm_yday; + tm->tm_wday = (epoch_days + 4) % 7; if (tm->tm_wday < 0) { tm->tm_wday += 7; } - long long epoch_time = tm->tm_sec + 60*(tm->tm_min + 60*(tm->tm_hour + 24*epoch_days)); + long long epoch_time = tm->tm_sec + 60 * (tm->tm_min + 60 * (tm->tm_hour + 24 * epoch_days)); *timep = (time_t)epoch_time; if ((long long)*timep != epoch_time) { goto overflow; @@ -296,7 +296,7 @@ end: goto error; } - int offset = 60*tz_hour + tz_min; + int offset = 60 * tz_hour + tz_min; if (tz_negative) { result->tv_sec -= offset; } else { diff --git a/tests/bfstd.c b/tests/bfstd.c index 83964e5..33b3792 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -7,8 +7,8 @@ #include #include #include -#include #include +#include #include #include diff --git a/tests/mksock.c b/tests/mksock.c index 42ef322..7023b4f 100644 --- a/tests/mksock.c +++ b/tests/mksock.c @@ -9,8 +9,8 @@ #include "../src/bfstd.h" #include #include -#include #include +#include #include #include #include diff --git a/tests/trie.c b/tests/trie.c index 6ea94a2..656fd85 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -120,11 +120,11 @@ int main(void) { bfs_verify(!trie_find_mem(&trie, longstr, longsize)); bfs_verify(trie_insert_mem(&trie, longstr, longsize)); - memset(longstr + longsize/2, 0xAB, longsize/2); + memset(longstr + longsize / 2, 0xAB, longsize / 2); bfs_verify(!trie_find_mem(&trie, longstr, longsize)); bfs_verify(trie_insert_mem(&trie, longstr, longsize)); - memset(longstr, 0xAA, longsize/2); + memset(longstr, 0xAA, longsize / 2); bfs_verify(!trie_find_mem(&trie, longstr, longsize)); bfs_verify(trie_insert_mem(&trie, longstr, longsize)); diff --git a/tests/xtimegm.c b/tests/xtimegm.c index b2479b7..973b2eb 100644 --- a/tests/xtimegm.c +++ b/tests/xtimegm.c @@ -42,9 +42,9 @@ static bool tm_equal(const struct tm *tma, const struct tm *tmb) { static void tm_print(FILE *file, const struct tm *tm) { fprintf(file, "Y%d M%d D%d h%d m%d s%d wd%d yd%d%s\n", - tm->tm_year, tm->tm_mon, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec, - tm->tm_wday, tm->tm_yday, + tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, + tm->tm_wday, tm->tm_yday, tm->tm_isdst ? (tm->tm_isdst < 0 ? " (DST?)" : " (DST)") : ""); } -- cgit v1.2.3 From 25660f5c062cf1e0d0982457614c9aa4584fa565 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 29 Sep 2023 15:13:41 -0400 Subject: tests/xtouch: Try creating the immediate parent first --- tests/xtouch.c | 39 ++++++++++++++++++++++++--------------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/tests/xtouch.c b/tests/xtouch.c index 4a02bf3..80fad8d 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -48,31 +48,40 @@ static int at_flags(const struct args *args) { /** Create any parent directories of the given path. */ static int mkdirs(const char *path, mode_t mode) { - char *copy = strdup(path); - if (!copy) { - return -1; + int ret = -1; + char *dir = xdirname(path); + if (!dir) { + goto err; } - int ret = -1; - char *cur = copy + strspn(copy, "/"); - while (true) { - cur += strcspn(cur, "/"); + if (strcmp(dir, ".") == 0) { + goto done; + } + + // Optimistically try the immediate parent first + if (mkdir(dir, mode) == 0 || errno == EEXIST) { + goto done; + } + // Create the parents one-at-a-time + char *cur = dir + strspn(dir, "/"); + while (*cur) { + cur += strcspn(cur, "/"); char *next = cur + strspn(cur, "/"); - if (!*next) { - ret = 0; - break; - } + char c = *cur; *cur = '\0'; - if (mkdir(copy, mode) != 0 && errno != EEXIST) { - break; + if (mkdir(dir, mode) != 0 && errno != EEXIST) { + goto err; } - *cur = '/'; + *cur = c; cur = next; } - free(copy); +done: + ret = 0; +err: + free(dir); return ret; } -- cgit v1.2.3 From 28f519d9650004787be480f15e990f835d324d7f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 28 Sep 2023 17:39:34 -0400 Subject: bench: New script to clone a git repo without file contents --- bench/clone-tree.sh | 143 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100755 bench/clone-tree.sh diff --git a/bench/clone-tree.sh b/bench/clone-tree.sh new file mode 100755 index 0000000..744b5f4 --- /dev/null +++ b/bench/clone-tree.sh @@ -0,0 +1,143 @@ +#!/usr/bin/env bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Creates a directory tree that matches a git repo, but with empty files. E.g. +# +# $ ./bench/clone-tree.sh "https://.../linux.git" v6.5 ./linux ./linux.git +# +# will create or update a shallow clone at ./linux.git, then create a directory +# tree at ./linux with the same directory tree as the tag v6.5, except all files +# will be empty. + +set -eu + +if (($# != 4)); then + printf 'Usage: %s https://url/of/repo.git path/to/checkout path/to/repo.git\n' "$0" >&2 + exit 1 +fi + +URL="$1" +TAG="$2" +DIR="$3" +REPO="$4" + +BENCH=$(dirname -- "${BASH_SOURCE[0]}") +BIN=$(realpath -- "$BENCH/../bin") +BFS="$BIN/bfs" +XTOUCH="$BIN/tests/xtouch" + +if [ "${NPROC-}" ]; then + # Use fewer cores in recursive calls + export NPROC=$(((NPROC + 1) / 2)) +else + export NPROC=$(nproc) +fi + +JOBS=$((NPROC < 8 ? NPROC : 8)) + +do-git() { + git -C "$REPO" "$@" +} + +if ! [ -e "$REPO" ]; then + mkdir -p -- "$REPO" + do-git init -q --bare +fi + +has-ref() { + do-git rev-list --quiet -1 --missing=allow-promisor "$1" &>/dev/null +} + +sparse-fetch() { + do-git -c fetch.negotiationAlgorithm=noop fetch -q --filter=blob:none --depth=1 --no-tags --no-write-fetch-head --no-auto-gc "$@" +} + +if ! has-ref "$TAG"; then + printf 'Fetching %s ...\n' "$TAG" >&2 + do-git config remote.origin.url "$URL" + if ((${#TAG} >= 40)); then + sparse-fetch origin "$TAG" + else + sparse-fetch origin tag "$TAG" + fi +fi + +# Delete a tree in parallel +clean() { + local d=5 + "$BFS" -f "$1" -mindepth $d -maxdepth $d -type d -print0 \ + | xargs -0r -n1 -P$JOBS -- "$BFS" -j1 -mindepth 1 -delete -f + "$BFS" -f "$1" -delete +} + +if [ -e "$DIR" ]; then + printf 'Cleaning old directory tree %s ...\n' "$DIR" >&2 + TMP=$(mktemp -dp "$(dirname -- "$DIR")") + mv -- "$DIR" "$TMP" + clean "$TMP" & +fi + +# List gitlinks (submodule references) in the tree +ls-gitlinks() { + do-git ls-tree -zr "$TAG" \ + | sed -zn 's/.* commit //p' +} + +# Get the submodule ID for a path +submodule-for-path() { + do-git config --blob "$TAG:.gitmodules" \ + --name-only \ + --fixed-value \ + --get-regexp 'submodule\..**\.path' "$1" \ + | sed -En 's/submodule\.(.*)\.path/\1/p' +} + +# Get the URL for a submodule +submodule-url() { + # - https://chrome-internal.googlesource.com/ + # - not publicly accessible + # - https://chromium.googlesource.com/external/github.com/WebKit/webkit.git + # - is accessible, but the commit (59e9de61b7b3) isn't + # - https://android.googlesource.com/ + # - is accessible, but you need an account + + do-git config --blob "$TAG:.gitmodules" \ + --get "submodule.$1.url" \ + | sed -E \ + -e '\|^https://chrome-internal.googlesource.com/|Q1' \ + -e '\|^https://chromium.googlesource.com/external/github.com/WebKit/webkit.git|Q1' \ + -e '\|^https://android.googlesource.com/|Q1' +} + +# Recursively checkout submodules +while read -rd '' SUBREF SUBDIR; do + SUBNAME=$(submodule-for-path "$SUBDIR") + SUBURL=$(submodule-url "$SUBNAME") || continue + + if (($(jobs -pr | wc -w) >= JOBS)); then + wait -n + fi + "$0" "$SUBURL" "$SUBREF" "$DIR/$SUBDIR" "$REPO/modules/$SUBNAME" & +done < <(ls-gitlinks) + +# Touch files in parallel +xtouch() ( + cd "$DIR" + if ((JOBS > 1)); then + xargs -0r -n4096 -P$JOBS -- "$XTOUCH" -p -- + else + xargs -0r -- "$XTOUCH" -p -- + fi +) + +# Check out files +printf 'Checking out %s ...\n' "$DIR" >&2 +mkdir -p -- "$DIR" +do-git ls-tree -zr "$TAG"\ + | sed -zn 's/.* blob .*\t//p' \ + | xtouch + +# Wait for cleaning/submodules +wait -- cgit v1.2.3 From fd2a86bb93f42a2c64b202f959dd5c8cbb724c69 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 29 Sep 2023 14:13:06 -0400 Subject: bench: Add benchmarking script --- bench/.gitignore | 3 + bench/bench.sh | 491 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 494 insertions(+) create mode 100644 bench/.gitignore create mode 100644 bench/bench.sh diff --git a/bench/.gitignore b/bench/.gitignore new file mode 100644 index 0000000..170d850 --- /dev/null +++ b/bench/.gitignore @@ -0,0 +1,3 @@ +/corpus/ +/results/ +/worktree/ diff --git a/bench/bench.sh b/bench/bench.sh new file mode 100644 index 0000000..09564ac --- /dev/null +++ b/bench/bench.sh @@ -0,0 +1,491 @@ +#!/hint/bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +declare -gA URLS=( + [chromium]="https://chromium.googlesource.com/chromium/src.git" + [linux]="https://github.com/torvalds/linux.git" + [rust]="https://github.com/rust-lang/rust.git" +) + +declare -gA TAGS=( + [chromium]=119.0.6036.2 + [linux]=v6.5 + [rust]=1.72.1 +) + +COMPLETE_DEFAULT=(linux rust chromium) +EARLY_QUIT_DEFAULT=(chromium) +PRINT_DEFAULT=(linux) +STRATEGIES_DEFAULT=(linux) + +usage() { + printf 'Usage: tailfin run %s [--default]\n' "${BASH_SOURCE[0]}" + printf ' [--complete] [--early-quit] [--print] [--strategies]\n' + printf ' [--build=...] [--bfs] [--find] [--fd]\n' + printf ' [--no-clean] [--help]\n\n' + + printf ' --default\n' + printf ' Run the default set of benchmarks\n\n' + + printf ' --complete[=CORPUS]\n' + printf ' Complete traversal benchmark. \n' + printf ' Default corpus is --complete="%s"\n\n' "${COMPLETE_DEFAULT[*]}" + + printf ' --early-quit[=CORPUS]\n' + printf ' Early quitting benchmark. \n' + printf ' Default corpus is --early-quit=chromium\n\n' "${EARLY_QUIT_DEFAULT[*]}" + + printf ' --print[=CORPUS]\n' + printf ' Path printing benchmark. \n' + printf ' Default corpus is --print=linux\n\n' "${PRINT_DEFAULT[*]}" + + printf ' --strategies[=CORPUS]\n' + printf ' Search strategy benchmark.\n' + printf ' Default corpus is --strategies=linux\n\n' "${STRATEGIES_DEFAULT[*]}" + + printf ' --build=COMMIT\n' + printf ' Build this bfs commit and benchmark it. Specify multiple times to\n' + printf ' compare, e.g. --build=3.0.1 --build=3.0.2\n\n' + + printf ' --bfs[=COMMAND]\n' + printf ' Benchmark an existing build of bfs\n\n' + + printf ' --find[=COMMAND]\n' + printf ' Compare against find\n\n' + + printf ' --fd[=COMMAND]\n' + printf ' Compare against fd\n\n' + + printf ' --no-clean\n' + printf ' Use any existing corpora as-is\n\n' + + printf ' --help\n' + printf ' This message\n\n' +} + +# Hack to export an array +export_array() { + local str=$(declare -p "$1" | sed 's/ -a / -ga /') + unset "$1" + export "$1=$str" +} + +# Hack to import an array +import_array() { + local cmd="${!1}" + unset "$1" + eval "$cmd" +} + +# Set up the benchmarks +setup() { + ROOT=$(realpath -- "$(dirname -- "${BASH_SOURCE[0]}")/..") + if ! [ "$PWD" -ef "$ROOT" ]; then + printf 'error: Please run this script from %s\n\n' "$ROOT" >&2 + usage >&2 + exit $EX_USAGE + fi + + nproc=$(nproc) + + # Options + + CLEAN=1 + + BUILD=() + BFS=() + FIND=() + FD=() + + COMPLETE=() + EARLY_QUIT=() + PRINT=() + STRATEGIES=() + + for arg; do + case "$arg" in + # Flags + --no-clean) + CLEAN=0 + ;; + # bfs commits/tags to benchmark + --build=*) + BUILD+=("${arg#*=}") + BFS+=("bfs-${arg#*=}") + ;; + # Utilities to benchmark against + --bfs) + BFS+=(bfs) + ;; + --bfs=*) + BFS+=("${arg#*=}") + ;; + --find) + FIND+=(find) + ;; + --find=*) + FIND+=("${arg#*=}") + ;; + --fd) + FD+=(fd) + ;; + --fd=*) + FD+=("${arg#*=}") + ;; + # Benchmark groups + --complete) + COMPLETE=("${COMPLETE_DEFAULT[@]}") + ;; + --complete=*) + read -ra COMPLETE <<<"${arg#*=}" + ;; + --early-quit) + EARLY_QUIT=("${EARLY_QUIT_DEFAULT[@]}") + ;; + --early-quit=*) + read -ra EARLY_QUIT <<<"${arg#*=}" + ;; + --print) + PRINT=("${PRINT_DEFAULT[@]}") + ;; + --print=*) + read -ra PRINT <<<"${arg#*=}" + ;; + --strategies) + STRATEGIES=("${STRATEGIES_DEFAULT[@]}") + ;; + --strategies=*) + read -ra STRATEGIES <<<"${arg#*=}" + ;; + --default) + COMPLETE=("${COMPLETE_DEFAULT[@]}") + EARLY_QUIT=("${EARLY_QUIT_DEFAULT[@]}") + PRINT=("${PRINT_DEFAULT[@]}") + STRATEGIES=("${STRATEGIES_DEFAULT[@]}") + ;; + --help) + usage + exit + ;; + *) + printf 'error: Unknown option %q\n\n' "$arg" >&2 + usage >&2 + exit $EX_USAGE + ;; + esac + done + + if ((UID == 0)); then + max-freq + fi + + echo "Building bfs ..." + as-user make -s -j"$nproc" release all + + as-user mkdir -p bench/corpus + + declare -A cloned=() + for corpus in "${COMPLETE[@]}" "${EARLY_QUIT[@]}" "${PRINT[@]}" "${STRATEGIES[@]}"; do + if ((cloned["$corpus"])); then + continue + fi + cloned["$corpus"]=1 + + dir="bench/corpus/$corpus" + if ((CLEAN)) || ! [ -e "$dir" ]; then + as-user ./bench/clone-tree.sh "${URLS[$corpus]}" "${TAGS[$corpus]}" "$dir"{,.git} + fi + done + + if ((${#BUILD[@]} > 0)); then + echo "Creating bfs worktree ..." + + worktree="bench/worktree" + as-user git worktree add -qd "$worktree" + at-exit as-user git worktree remove "$worktree" + + bin="$(realpath -- "$SETUP_DIR")/bin" + as-user mkdir "$bin" + + for commit in "${BUILD[@]}"; do + ( + echo "Building bfs $commit ..." + cd "$worktree" + as-user git checkout -qd "$commit" -- + as-user make -s -j"$nproc" release + as-user cp ./bin/bfs "$bin/bfs-$commit" + as-user make -s clean + ) + done + + # $SETUP_DIR contains `:` so it won't work in $PATH + # Work around this with a symlink + tmp=$(as-user mktemp) + as-user ln -sf "$bin" "$tmp" + at-exit rm "$tmp" + export PATH="$tmp:$PATH" + fi + + export_array BFS + export_array FIND + export_array FD + + export_array COMPLETE + export_array EARLY_QUIT + export_array PRINT + export_array STRATEGIES + + if ((UID == 0)); then + turbo-off + fi + + sync +} + +# Runs hyperfine and saves the output +do-hyperfine() { + local tmp_md="$BENCH_DIR/.bench.md" + local md="$BENCH_DIR/bench.md" + local tmp_json="$BENCH_DIR/.bench.json" + local json="$BENCH_DIR/bench.json" + + if (($# == 0)); then + printf 'Nothing to do\n\n' | tee -a "$md" + return 1 + fi + + hyperfine -w2 -M20 --export-markdown="$tmp_md" --export-json="$tmp_json" "$@" &>/dev/tty + cat "$tmp_md" >>"$md" + cat "$tmp_json" >>"$json" + rm "$tmp_md" "$tmp_json" + + printf '\n' | tee -a "$md" +} + +# Print the header for a benchmark group +group() { + printf '## %s\n\n' "$1" | tee -a "$BENCH_DIR/bench.md" +} + +# Print the header for a benchmark subgroup +subgroup() { + printf '### %s\n\n' "$1" | tee -a "$BENCH_DIR/bench.md" +} + +# Print the header for a benchmark sub-subgroup +subsubgroup() { + printf '#### %s\n\n' "$1" | tee -a "$BENCH_DIR/bench.md" +} + +# Benchmark the complete traversal of a directory tree +# (without printing anything) +bench-complete-corpus() { + total=$(./bin/bfs "$2" -printf '.' | wc -c) + + subgroup "$1 ($total files)" + + cmds=() + for bfs in "${BFS[@]}"; do + cmds+=("$bfs $2 -false") + done + + for find in "${FIND[@]}"; do + cmds+=("$find $2 -false") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -u '^$' $2") + done + + do-hyperfine "${cmds[@]}" +} + +# All complete traversal benchmarks +bench-complete() { + if (($#)); then + group "Complete traversal" + + for corpus; do + bench-complete-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" + done + fi +} + +# Benchmark quiting as soon as a file is seen +bench-early-quit-corpus() { + dir="$2" + max_depth=$(./bin/bfs "$dir" -printf '%d\n' | sort -rn | head -n1) + + subgroup "$1 (depth $max_depth)" + + # Save the list of unique filenames, along with their depth + UNIQ="$BENCH_DIR/uniq" + ./bin/bfs "$dir" -printf '%d %f\n' | sort -k2 | uniq -uf1 >"$UNIQ" + + for ((i = 2; i <= max_depth; i *= 2)); do + subsubgroup "Depth $i" + + # Sample random uniquely-named files at depth $i + export FILES="$BENCH_DIR/uniq-$i" + sed -n "s/^$i //p" "$UNIQ" | shuf -n20 >"$FILES" + if ! [ -s "$FILES" ]; then + continue + fi + + cmds=() + for bfs in "${BFS[@]}"; do + cmds+=("$bfs $dir -name \$(shuf -n1 \$FILES) -print -quit") + done + + for find in "${FIND[@]}"; do + cmds+=("$find $dir -name \$(shuf -n1 \$FILES) -print -quit") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -usg1 \$(shuf -n1 \$FILES) $dir") + done + + do-hyperfine "${cmds[@]}" + done +} + +# All early-quitting benchmarks +bench-early-quit() { + if (($#)); then + group "Early termination" + + for corpus; do + bench-early-quit-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" + done + fi +} + +# Benchmark printing paths without colors +bench-print-nocolor() { + subsubgroup "$1" + + cmds=() + for bfs in "${BFS[@]}"; do + cmds+=("$bfs $2") + done + + for find in "${FIND[@]}"; do + cmds+=("$find $2") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -u --search-path $2") + done + + do-hyperfine "${cmds[@]}" +} + +# Benchmark printing paths with colors +bench-print-color() { + subsubgroup "$1" + + cmds=() + for bfs in "${BFS[@]}"; do + cmds+=("$bfs $2 -color") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -u --search-path $2 --color=always") + done + + do-hyperfine "${cmds[@]}" +} + +# All printing benchmarks +bench-print() { + if (($#)); then + group "Printing paths" + + subgroup "Without colors" + for corpus; do + bench-print-nocolor "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" + done + + subgroup "With colors" + for corpus; do + bench-print-color "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" + done + fi +} + +# Benchmark search strategies +bench-strategies-corpus() { + subgroup "$1" + + for bfs in "${BFS[@]}"; do + subsubgroup "$bfs" + cmds=("$bfs -S "{bfs,dfs,ids,eds}" $2") + do-hyperfine "${cmds[@]}" + done +} + +# All search strategy benchmarks +bench-strategies() { + if (($#)); then + group "Search strategies" + + for corpus; do + bench-strategies-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" + done + fi +} + +# Print benchmarked versions +bench-versions() { + subgroup "Versions" + + local md="$BENCH_DIR/bench.md" + + printf '```console\n' >>"$md" + + { + for bfs in "${BFS[@]}"; do + printf '$ %s --version | head -n1\n' "$bfs" + "$bfs" --version | head -n1 + done + + for find in "${FIND[@]}"; do + printf '$ %s --version | head -n1\n' "$find" + "$find" --version | head -n1 + done + + for fd in "${FD[@]}"; do + printf '$ %s --version\n' "$fd" + "$fd" --version + done + } | tee -a "$md" + + printf '```' >>"$md" +} + +# Print benchmark details +bench-details() { + group "Details" + + bench-versions +} + +# Run all the benchmarks +bench() { + import_array BFS + import_array FIND + import_array FD + + import_array COMPLETE + import_array EARLY_QUIT + import_array PRINT + import_array STRATEGIES + + bench-complete "${COMPLETE[@]}" + bench-early-quit "${EARLY_QUIT[@]}" + bench-print "${PRINT[@]}" + bench-strategies "${STRATEGIES[@]}" + bench-details +} -- cgit v1.2.3 From 692098fdb922c464949fad7c5b9e36b531ea6f68 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 2 Oct 2023 10:49:41 -0400 Subject: bench: Add a README --- bench/README.md | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 bench/README.md diff --git a/bench/README.md b/bench/README.md new file mode 100644 index 0000000..56157a0 --- /dev/null +++ b/bench/README.md @@ -0,0 +1,51 @@ +This directory contains a suite of benchmarks used to evaluate `bfs` and detect performance regressions. +To run them, you'll need the [tailfin] benchmark harness. +You can read the full usage information with + +[tailfin]: https://github.com/tavianator/tailfin + +```console +$ tailfin -n run bench/bench.sh --help +Usage: tailfin run bench/bench.sh [--default] + [--complete] [--early-quit] [--print] [--strategies] + [--build=...] [--bfs] [--find] [--fd] + [--no-clean] [--help] +... +``` + +The benchmarks use various git repositories to have a realistic and reproducible directory structure as a corpus. +Currently, those are the [Linux], [Rust], and [Chromium] repos. +The scripts will automatically clone those repos using [partial clone] filters to avoid downloading the actual file contents, saving bandwidth and space. + +[Linux]: https://github.com/torvalds/linux.git +[Rust]: https://github.com/rust-lang/rust.git +[Chromium]: https://chromium.googlesource.com/chromium/src.git +[partial clone]: https://git-scm.com/docs/partial-clone + +You can try out a quick benchmark by running + +```console +$ tailfin run bench/bench.sh --build=main --complete=linux +``` + +This will build the `main` branch, and measure the complete traversal of the Linux repo. +Results will be both printed to the console and saved in a Markdown file, which you can find by running + +```console +$ tailfin latest +results/2023/09/29/15:32:49 +$ cat results/2023/09/29/15:32:49/runs/1/bench.md +## Complete traversal +... +``` + +To measure performance improvements/regressions of a change, compare the `main` branch to the topic branch on the full benchmark suite: + +```console +$ tailfin run bench/bench.sh --build=main --build=branch --default +``` + +This will take a few minutes. +Results from the full benchmark suite can be seen in performance-related pull requests, for example [#126]. + +[#126]: https://github.com/tavianator/bfs/pull/126 -- cgit v1.2.3 From dba692ca0fb44678fdcc7634d821f04eac2f8042 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 11 Jul 2023 14:30:52 -0400 Subject: build: Add liburing on Linux --- .github/workflows/ci.yml | 5 +++-- .github/workflows/codecov.yml | 3 ++- .github/workflows/codeql.yml | 3 ++- GNUmakefile | 8 +++++++- README.md | 12 ++++++------ docs/BUILDING.md | 2 ++ 6 files changed, 22 insertions(+), 11 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ca1737c..971a4df 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,10 @@ jobs: libcap-dev \ libcap2:i386 \ libonig-dev \ - libonig5:i386 + libonig5:i386 \ + liburing-dev # Ubuntu doesn't let you install the -dev packages for both amd64 and - # i386 at once, so we make our own symlinks to fix -m32 -lacl -lattr -lcap + # i386 at once, so we make our own symlinks to fix -m32 -lacl -l... sudo ln -s libacl.so.1 /lib/i386-linux-gnu/libacl.so sudo ln -s libattr.so.1 /lib/i386-linux-gnu/libattr.so sudo ln -s libcap.so.2 /lib/i386-linux-gnu/libcap.so diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 783cc43..b06ea62 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -20,7 +20,8 @@ jobs: libattr1-dev \ libcap2-bin \ libcap-dev \ - libonig-dev + libonig-dev \ + liburing-dev - name: Generate coverage run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 88e9f3f..3a2f81f 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -37,7 +37,8 @@ jobs: libattr1-dev \ libcap2-bin \ libcap-dev \ - libonig-dev + libonig-dev \ + liburing-dev - name: Initialize CodeQL uses: github/codeql-action/init@v2 diff --git a/GNUmakefile b/GNUmakefile index 6902979..8154240 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -126,6 +126,7 @@ ifndef NOLIBS USE_ACL := y USE_ATTR := y USE_LIBCAP := y +USE_LIBURING := y endif ifdef USE_ACL @@ -146,6 +147,11 @@ else LOCAL_CPPFLAGS += -DBFS_USE_SYS_CAPABILITY_H=0 endif +ifdef USE_LIBURING +LOCAL_CPPFLAGS += -DBFS_USE_LIBURING=1 +LOCAL_LDLIBS += -luring +endif + LOCAL_LDFLAGS += -Wl,--as-needed LOCAL_LDLIBS += -lrt endif # Linux @@ -287,7 +293,7 @@ ifneq ($(OS),Darwin) endif +$(MAKE) -B tsan ubsan check CC=clang $(DISTCHECK_FLAGS) ifeq ($(OS) $(ARCH),Linux x86_64) - +$(MAKE) -B check EXTRA_CFLAGS="-m32" ONIG_CONFIG= $(DISTCHECK_FLAGS) + +$(MAKE) -B check EXTRA_CFLAGS="-m32" ONIG_CONFIG= USE_LIBURING= $(DISTCHECK_FLAGS) endif +$(MAKE) -B release check $(DISTCHECK_FLAGS) +$(MAKE) -B check $(DISTCHECK_FLAGS) diff --git a/README.md b/README.md index 18af808..a7ca335 100644 --- a/README.md +++ b/README.md @@ -290,22 +290,22 @@ Here's how to install them on some common platforms:
 Alpine Linux
-# apk add acl{,-dev} attr{,-dev} libcap{,-dev} oniguruma-dev
+# apk add acl{,-dev} attr{,-dev} libcap{,-dev} liburing-dev oniguruma-dev
 
 Arch Linux
-# pacman -S acl attr libcap oniguruma
+# pacman -S acl attr libcap liburing oniguruma
 
 Debian/Ubuntu
-# apt install acl libacl1-dev attr libattr1-dev libcap2-bin libcap-dev libonig-dev
+# apt install acl libacl1-dev attr libattr1-dev libcap2-bin libcap-dev liburing-dev libonig-dev
 
 Fedora
-# dnf install acl libacl-devel libattr-devel libcap-devel oniguruma-devel
+# dnf install acl libacl-devel libattr-devel libcap-devel liburing-devel oniguruma-devel
 
 NixOS
-# nix-env -i acl attr libcap oniguruma
+# nix-env -i acl attr libcap liburing oniguruma
 
 Void Linux
-# xbps-install -S acl-{devel,progs} attr-{devel,progs} libcap-{devel,progs} oniguruma-devel
+# xbps-install -S acl-{devel,progs} attr-{devel,progs} libcap-{devel,progs} liburing-devel oniguruma-devel
 
 Homebrew
 $ brew install oniguruma
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index b19ef00..02f9756 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -75,11 +75,13 @@ These dependencies are optional, and can be turned off at build time if necessar
 | [acl]       | Linux only | `USE_ACL`       |
 | [attr]      | Linux only | `USE_ATTR`      |
 | [libcap]    | Linux only | `USE_LIBCAP`    |
+| [liburing]  | Linux only | `USE_LIBURING`  |
 | [Oniguruma] | All        | `USE_ONIGURUMA` |
 
 [acl]: https://savannah.nongnu.org/projects/acl
 [attr]: https://savannah.nongnu.org/projects/attr
 [libcap]: https://sites.google.com/site/fullycapable/
+[liburing]: https://github.com/axboe/liburing
 [Oniguruma]: https://github.com/kkos/oniguruma
 
 ### Dependency tracking
-- 
cgit v1.2.3


From 1afa241472709b32baf5e3e1fd3ba6ebd5fd1bf6 Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Tue, 11 Jul 2023 14:04:40 -0400
Subject: ioq: Use io_uring

Closes #65.
---
 src/bftw.c |  23 ++++-
 src/ioq.c  | 307 +++++++++++++++++++++++++++++++++++++++++++++++++++++--------
 2 files changed, 288 insertions(+), 42 deletions(-)

diff --git a/src/bftw.c b/src/bftw.c
index 5e5f4a5..902a3fa 100644
--- a/src/bftw.c
+++ b/src/bftw.c
@@ -470,21 +470,34 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg
 
 	state->error = 0;
 
-	if (args->nopenfd < 1) {
+	if (args->nopenfd < 2) {
 		errno = EMFILE;
 		return -1;
 	}
-	bftw_cache_init(&state->cache, args->nopenfd);
 
-	state->nthreads = args->nthreads;
-	if (state->nthreads > 0) {
-		state->ioq = ioq_create(4096, state->nthreads);
+	size_t nopenfd = args->nopenfd;
+	size_t qdepth = 4096;
+	size_t nthreads = args->nthreads;
+
+#if BFS_USE_LIBURING
+	// io_uring uses one fd per ring, ioq uses one ring per thread
+	if (nthreads >= nopenfd - 1) {
+		nthreads = nopenfd - 2;
+	}
+	nopenfd -= nthreads;
+#endif
+
+	bftw_cache_init(&state->cache, nopenfd);
+
+	if (nthreads > 0) {
+		state->ioq = ioq_create(qdepth, nthreads);
 		if (!state->ioq) {
 			return -1;
 		}
 	} else {
 		state->ioq = NULL;
 	}
+	state->nthreads = nthreads;
 
 	SLIST_INIT(&state->to_open);
 	SLIST_INIT(&state->to_read);
diff --git a/src/ioq.c b/src/ioq.c
index d3ba2de..04b9c0d 100644
--- a/src/ioq.c
+++ b/src/ioq.c
@@ -16,6 +16,10 @@
 #include 
 #include 
 
+#if BFS_USE_LIBURING
+#  include 
+#endif
+
 /**
  * A monitor for an I/O queue slot.
  */
@@ -280,6 +284,21 @@ static struct ioq_ent *ioqq_trypop(struct ioqq *ioqq) {
 /** Sentinel stop command. */
 static struct ioq_ent IOQ_STOP;
 
+/** I/O queue thread-specific data. */
+struct ioq_thread {
+	/** The thread handle. */
+	pthread_t id;
+	/** Pointer back to the I/O queue. */
+	struct ioq *parent;
+
+#if BFS_USE_LIBURING
+	/** io_uring instance. */
+	struct io_uring ring;
+	/** Any error that occurred initializing the ring. */
+	int ring_err;
+#endif
+};
+
 struct ioq {
 	/** The depth of the queue. */
 	size_t depth;
@@ -299,60 +318,247 @@ struct ioq {
 	/** The number of background threads. */
 	size_t nthreads;
 	/** The background threads themselves. */
-	pthread_t threads[];
+	struct ioq_thread threads[];
 };
 
-/** Background thread entry point. */
-static void *ioq_work(void *ptr) {
-	struct ioq *ioq = ptr;
+/** Cancel a request if we need to. */
+static bool ioq_check_cancel(struct ioq *ioq, struct ioq_ent *ent) {
+	if (!load(&ioq->cancel, relaxed)) {
+		return false;
+	}
 
-	while (true) {
-		struct ioq_ent *ent = ioqq_pop(ioq->pending);
-		if (ent == &IOQ_STOP) {
-			break;
+	// Always close(), even if we're cancelled, just like a real EINTR
+	if (ent->op == IOQ_CLOSE || ent->op == IOQ_CLOSEDIR) {
+		return false;
+	}
+
+	ent->ret = -1;
+	ent->error = EINTR;
+	ioqq_push(ioq->ready, ent);
+	return true;
+}
+
+/** Handle a single request synchronously. */
+static void ioq_handle(struct ioq *ioq, struct ioq_ent *ent) {
+	int ret;
+
+	switch (ent->op) {
+	case IOQ_CLOSE:
+		ret = xclose(ent->close.fd);
+		break;
+
+	case IOQ_OPENDIR:
+		ret = bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path);
+		if (ret == 0) {
+			bfs_polldir(ent->opendir.dir);
 		}
+		break;
+
+	case IOQ_CLOSEDIR:
+		ret = bfs_closedir(ent->closedir.dir);
+		break;
+
+	default:
+		bfs_bug("Unknown ioq_op %d", (int)ent->op);
+		ret = -1;
+		errno = ENOSYS;
+		break;
+	}
+
+	ent->ret = ret;
+	ent->error = ret == 0 ? 0 : errno;
+
+	ioqq_push(ioq->ready, ent);
+}
 
-		bool cancel = load(&ioq->cancel, relaxed);
+#if BFS_USE_LIBURING
+/** io_uring worker state. */
+struct ioq_ring_state {
+	/** The I/O queue. */
+	struct ioq *ioq;
+	/** The io_uring. */
+	struct io_uring *ring;
+	/** The current ioq->pending slot. */
+	ioq_slot *slot;
+	/** Number of prepped, unsubmitted SQEs. */
+	size_t prepped;
+	/** Number of submitted, unreaped SQEs. */
+	size_t submitted;
+	/** Whether to stop the loop. */
+	bool stop;
+};
+
+/** Pop a request for ioq_ring_prep(). */
+static struct ioq_ent *ioq_ring_pop(struct ioq_ring_state *state) {
+	if (state->stop) {
+		return NULL;
+	}
+
+	// Advance to the next slot if necessary
+	struct ioq *ioq = state->ioq;
+	if (!state->slot) {
+		state->slot = ioqq_read(ioq->pending);
+	}
+
+	// Block if we have nothing else to do
+	bool block = !state->prepped && !state->submitted;
+	struct ioq_ent *ret = ioq_slot_pop(ioq->pending, state->slot, block);
+
+	if (ret) {
+		// Got an entry, move to the next slot next time
+		state->slot = NULL;
+	}
+
+	if (ret == &IOQ_STOP) {
+		state->stop = true;
+		ret = NULL;
+	}
+
+	return ret;
+}
 
-		ent->ret = -1;
+/** Prep a single SQE. */
+static void ioq_prep_sqe(struct io_uring_sqe *sqe, struct ioq_ent *ent) {
+	switch (ent->op) {
+	case IOQ_CLOSE:
+		io_uring_prep_close(sqe, ent->close.fd);
+		break;
+
+	case IOQ_OPENDIR:
+		io_uring_prep_openat(sqe, ent->opendir.dfd, ent->opendir.path, O_RDONLY | O_CLOEXEC | O_DIRECTORY, 0);
+		break;
+
+#if BFS_USE_UNWRAPDIR
+	case IOQ_CLOSEDIR:
+		io_uring_prep_close(sqe, bfs_unwrapdir(ent->closedir.dir));
+		break;
+#endif
+
+	default:
+		bfs_bug("Unknown ioq_op %d", (int)ent->op);
+		io_uring_prep_nop(sqe);
+		break;
+	}
 
-		switch (ent->op) {
-		case IOQ_CLOSE:
-			// Always close(), even if we're cancelled, just like a real EINTR
-			ent->ret = xclose(ent->close.fd);
+	io_uring_sqe_set_data(sqe, ent);
+}
+
+/** Prep a batch of SQEs. */
+static bool ioq_ring_prep(struct ioq_ring_state *state) {
+	struct ioq *ioq = state->ioq;
+	struct io_uring *ring = state->ring;
+
+	while (io_uring_sq_space_left(ring)) {
+		struct ioq_ent *ent = ioq_ring_pop(state);
+		if (!ent) {
 			break;
+		}
+
+		if (ioq_check_cancel(ioq, ent)) {
+			continue;
+		}
 
-		case IOQ_OPENDIR:
-			if (!cancel) {
-				struct ioq_opendir *args = &ent->opendir;
-				ent->ret = bfs_opendir(args->dir, args->dfd, args->path);
-				if (ent->ret == 0) {
-					bfs_polldir(args->dir);
-				}
+#if !BFS_USE_UNWRAPDIR
+		if (ent->op == IOQ_CLOSEDIR) {
+			ioq_handle(ioq, ent);
+			continue;
+		}
+#endif
+
+		struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
+		ioq_prep_sqe(sqe, ent);
+		++state->prepped;
+	}
+
+	return state->prepped || state->submitted;
+}
+
+/** Reap a batch of SQEs. */
+static void ioq_ring_reap(struct ioq_ring_state *state) {
+	struct ioq *ioq = state->ioq;
+	struct io_uring *ring = state->ring;
+
+	while (state->prepped) {
+		int ret = io_uring_submit_and_wait(ring, 1);
+		if (ret > 0) {
+			state->prepped -= ret;
+			state->submitted += ret;
+		}
+	}
+
+	while (state->submitted) {
+		struct io_uring_cqe *cqe;
+		if (io_uring_wait_cqe(ring, &cqe) < 0) {
+			continue;
+		}
+
+		struct ioq_ent *ent = io_uring_cqe_get_data(cqe);
+		ent->ret = cqe->res >= 0 ? cqe->res : -1;
+		ent->error = cqe->res < 0 ? -cqe->res : 0;
+		io_uring_cqe_seen(ring, cqe);
+		--state->submitted;
+
+		if (ent->op == IOQ_OPENDIR && ent->ret >= 0) {
+			int fd = ent->ret;
+			if (ioq_check_cancel(ioq, ent)) {
+				xclose(fd);
+				continue;
 			}
-			break;
 
-		case IOQ_CLOSEDIR:
-			ent->ret = bfs_closedir(ent->closedir.dir);
-			break;
+			ent->ret = bfs_opendir(ent->opendir.dir, fd, NULL);
+			if (ent->ret == 0) {
+				// TODO: io_uring_prep_getdents()
+				bfs_polldir(ent->opendir.dir);
+			} else {
+				ent->error = errno;
+			}
+		}
+
+		ioqq_push(ioq->ready, ent);
+	}
+}
+
+/** io_uring worker loop. */
+static void ioq_ring_work(struct ioq_thread *thread) {
+	struct ioq_ring_state state = {
+		.ioq = thread->parent,
+		.ring = &thread->ring,
+	};
 
-		default:
-			bfs_bug("Unknown ioq_op %d", (int)ent->op);
-			errno = ENOSYS;
+	while (ioq_ring_prep(&state)) {
+		ioq_ring_reap(&state);
+	}
+}
+#endif
+
+/** Synchronous syscall loop. */
+static void ioq_sync_work(struct ioq_thread *thread) {
+	struct ioq *ioq = thread->parent;
+
+	while (true) {
+		struct ioq_ent *ent = ioqq_pop(ioq->pending);
+		if (ent == &IOQ_STOP) {
 			break;
 		}
 
-		if (cancel) {
-			ent->error = EINTR;
-		} else if (ent->ret < 0) {
-			ent->error = errno;
-		} else {
-			ent->error = 0;
+		if (!ioq_check_cancel(ioq, ent)) {
+			ioq_handle(ioq, ent);
 		}
+	}
+}
 
-		ioqq_push(ioq->ready, ent);
+/** Background thread entry point. */
+static void *ioq_work(void *ptr) {
+	struct ioq_thread *thread = ptr;
+
+#if BFS_USE_LIBURING
+	if (thread->ring_err == 0) {
+		ioq_ring_work(thread);
+		return NULL;
 	}
+#endif
 
+	ioq_sync_work(thread);
 	return NULL;
 }
 
@@ -376,7 +582,30 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) {
 	}
 
 	for (size_t i = 0; i < nthreads; ++i) {
-		if (thread_create(&ioq->threads[i], NULL, ioq_work, ioq) != 0) {
+		struct ioq_thread *thread = &ioq->threads[i];
+		thread->parent = ioq;
+
+#if BFS_USE_LIBURING
+		struct ioq_thread *prev = i ? &ioq->threads[i - 1] : NULL;
+		if (prev && prev->ring_err) {
+			thread->ring_err = prev->ring_err;
+		} else {
+			// Share io-wq workers between rings
+			struct io_uring_params params = {0};
+			if (prev) {
+				params.flags |= IORING_SETUP_ATTACH_WQ;
+				params.wq_fd = prev->ring.ring_fd;
+			}
+
+			size_t entries = depth / nthreads;
+			if (entries < 16) {
+				entries = 16;
+			}
+			thread->ring_err = -io_uring_queue_init_params(entries, &thread->ring, ¶ms);
+		}
+#endif
+
+		if (thread_create(&thread->id, NULL, ioq_work, thread) != 0) {
 			goto fail;
 		}
 		++ioq->nthreads;
@@ -496,7 +725,11 @@ void ioq_destroy(struct ioq *ioq) {
 	ioq_cancel(ioq);
 
 	for (size_t i = 0; i < ioq->nthreads; ++i) {
-		thread_join(ioq->threads[i], NULL);
+		struct ioq_thread *thread = &ioq->threads[i];
+		thread_join(thread->id, NULL);
+#if BFS_USE_LIBURING
+		io_uring_queue_exit(&thread->ring);
+#endif
 	}
 
 	ioqq_destroy(ioq->ready);
-- 
cgit v1.2.3


From 9907004a1bb047e0644bf6d180aeb8b6ac23b1bb Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Mon, 2 Oct 2023 12:57:26 -0400
Subject: tests: Don't cd into paths longer than PATH_MAX

This fixes a warning on at least Alpine Linux that looks like

    cd: error retrieving current directory: getcwd: cannot access parent directories: Filename too long
---
 tests/tests.sh | 20 ++++++++------------
 1 file changed, 8 insertions(+), 12 deletions(-)

diff --git a/tests/tests.sh b/tests/tests.sh
index 66a79a8..121cd85 100755
--- a/tests/tests.sh
+++ b/tests/tests.sh
@@ -393,18 +393,14 @@ function make_deep() {
     local names="$name/$name/$name/$name"
 
     for i in {0..9} A B C D E F; do
-        (
-            mkdir "$1/$i"
-            cd "$1/$i"
-
-            # 4 * 1024 == 4096 == PATH_MAX
-            for _ in {1..4}; do
-                mkdir -p "$names"
-                cd "$names"
-            done
-
-            "$XTOUCH" "$name"
-        )
+        "$XTOUCH" -p "$1/$i/$name"
+
+        # 4 * 1024 == 4096 == PATH_MAX
+        for _ in {1..4}; do
+            mv "$1/$i/$name" "$1/"
+            mkdir -p "$1/$i/$names"
+            mv "$1/$name" "$1/$i/$names/"
+        done
     done
 }
 make_deep "$TMP/deep"
-- 
cgit v1.2.3


From c1103834065bbd1182a90b9723dbd89a4a908731 Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Mon, 2 Oct 2023 14:13:49 -0400
Subject: tests: Fix make_deep() on FreeBSD

I keep forgetting that PATH_MAX is only 1024 there.
---
 tests/tests.sh | 19 ++++++++++---------
 1 file changed, 10 insertions(+), 9 deletions(-)

diff --git a/tests/tests.sh b/tests/tests.sh
index 121cd85..663f5aa 100755
--- a/tests/tests.sh
+++ b/tests/tests.sh
@@ -389,18 +389,19 @@ function make_deep() {
     name="${name}${name}${name}${name}"
     name="${name:0:255}"
 
-    # 4 * 256 - 1 == 1023
-    local names="$name/$name/$name/$name"
-
     for i in {0..9} A B C D E F; do
         "$XTOUCH" -p "$1/$i/$name"
 
-        # 4 * 1024 == 4096 == PATH_MAX
-        for _ in {1..4}; do
-            mv "$1/$i/$name" "$1/"
-            mkdir -p "$1/$i/$names"
-            mv "$1/$name" "$1/$i/$names/"
-        done
+        (
+            cd "$1/$i"
+
+            # 8 * 512 == 4096 >= PATH_MAX
+            for _ in {1..8}; do
+                mv "$name" ..
+                mkdir -p "$name/$name"
+                mv "../$name" "$name/$name/"
+            done
+        )
     done
 }
 make_deep "$TMP/deep"
-- 
cgit v1.2.3


From ee6eea07f8a2c73d0b49f8e2199016116a411557 Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Tue, 3 Oct 2023 10:11:42 -0400
Subject: ci/macos: Don't install coreutils

Since we build our own touch(1) implementation, we no longer need to
work around the macOS bug.
---
 .github/workflows/ci.yml | 4 ----
 1 file changed, 4 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 971a4df..db7baca 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -49,10 +49,6 @@ jobs:
     steps:
       - uses: actions/checkout@v3
 
-      - name: Install dependencies
-        run: |
-          brew install coreutils
-
       - name: Run tests
         run: |
           make -j$(sysctl -n hw.ncpu) distcheck
-- 
cgit v1.2.3


From 9b1ee97d13985f42b09f02c8a25459a910004eb5 Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Tue, 3 Oct 2023 10:17:12 -0400
Subject: ci/freebsd: Use tailscale ssh

---
 .github/workflows/ci.yml | 13 +++----------
 1 file changed, 3 insertions(+), 10 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index db7baca..e781055 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -71,17 +71,10 @@ jobs:
           oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
           tags: tag:ci
 
-      - name: Configure SSH
-        env:
-          SSH_KEY: ${{ secrets.SSH_KEY }}
-        run: |
-          mkdir ~/.ssh
-          printf '%s' "$SSH_KEY" >~/.ssh/github-actions
-          chmod 0600 ~/.ssh/github-actions
-          printf 'Host %s\n\tStrictHostKeyChecking=accept-new\n\tUser github\n\tIdentityFile ~/.ssh/github-actions\n' "$(tailscale ip -6 spurion)" >~/.ssh/config
-
       - name: Run tests
         run: |
           spurion=$(tailscale ip -6 spurion)
+          mkdir ~/.ssh
+          printf 'Host %s\n\tStrictHostKeyChecking=accept-new\n\tUser github\n' "$spurion" >~/.ssh/config
           rsync -rl --delete . "[$spurion]:bfs"
-          ssh "$spurion" 'gmake -C bfs -j$(sysctl -n hw.ncpu) distcheck CC=clang16'
+          ssh "$spurion" '. ~/.ssh/rc; gmake -C bfs -j$(sysctl -n hw.ncpu) distcheck CC=clang16'
-- 
cgit v1.2.3


From a96ddeb7dc528d29d05f52a9b8857b2f8924fddc Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Tue, 3 Oct 2023 13:07:21 -0400
Subject: thread: s/call_once/invoke_once/

call_once() is a reserved identifier from C11.
---
 src/bfstd.c  | 2 +-
 src/thread.h | 2 +-
 src/xregex.c | 2 +-
 3 files changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/bfstd.c b/src/bfstd.c
index 1a5a67d..fcf4a6d 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -651,7 +651,7 @@ static bool xiswprint(wchar_t c, enum wesc_flags flags) {
 /** Get the length of the longest printable prefix of a string. */
 static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) {
 	static pthread_once_t once = PTHREAD_ONCE_INIT;
-	call_once(&once, char_cache_init);
+	invoke_once(&once, char_cache_init);
 
 	// Fast path: avoid multibyte checks
 	size_t i, word;
diff --git a/src/thread.h b/src/thread.h
index b2edf17..45b5e1f 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -97,7 +97,7 @@
 /**
  * Wrapper for pthread_once().
  */
-#define call_once(once, fn) \
+#define invoke_once(once, fn) \
 	thread_verify(pthread_once(once, fn), errno == 0)
 
 #endif // BFS_THREAD_H
diff --git a/src/xregex.c b/src/xregex.c
index b9c04bf..87b692e 100644
--- a/src/xregex.c
+++ b/src/xregex.c
@@ -107,7 +107,7 @@ static void bfs_onig_once(void) {
 /** Initialize Oniguruma. */
 static int bfs_onig_initialize(OnigEncoding *enc) {
 	static pthread_once_t once = PTHREAD_ONCE_INIT;
-	call_once(&once, bfs_onig_once);
+	invoke_once(&once, bfs_onig_once);
 
 	*enc = bfs_onig_enc;
 	return bfs_onig_status;
-- 
cgit v1.2.3


From e3ebe734ab047418a3c26cd7eca53fc7f4f60111 Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Tue, 3 Oct 2023 16:25:56 -0400
Subject: build: New lint flag

---
 GNUmakefile | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/GNUmakefile b/GNUmakefile
index 8154240..c1296f9 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -166,6 +166,13 @@ LOCAL_CFLAGS += --coverage
 LOCAL_CFLAGS := $(patsubst -std=c%,-std=gnu%,$(LOCAL_CFLAGS))
 endif
 
+ifneq ($(filter lint,$(MAKECMDGOALS)),)
+LOCAL_CPPFLAGS += \
+    -D_FORTIFY_SOURCE=3 \
+    -DBFS_LINT
+LOCAL_CFLAGS += -Werror -O2
+endif
+
 ifneq ($(filter release,$(MAKECMDGOALS)),)
 LOCAL_CPPFLAGS += -DNDEBUG
 CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto=auto
@@ -181,7 +188,7 @@ bfs: $(BIN)/bfs
 .PHONY: bfs
 
 # Goals that are treated like flags by this makefile
-FLAG_GOALS := asan lsan msan tsan ubsan gcov release
+FLAG_GOALS := asan lsan msan tsan ubsan gcov lint release
 
 # These are the remaining non-flag goals
 GOALS := $(filter-out $(FLAG_GOALS),$(MAKECMDGOALS))
-- 
cgit v1.2.3


From 2c3bf8d6f064c5ccab1d57f3eb82381ff445cc4a Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Tue, 3 Oct 2023 16:26:05 -0400
Subject: dstring: Limit the special dchar typedef to lint builds

Clang still thinks that alignof(dstr[1]) == 2, so out of an abundance of
caution, don't mess with dchar alignment in normal builds.
---
 src/dstring.h | 9 ++-------
 1 file changed, 2 insertions(+), 7 deletions(-)

diff --git a/src/dstring.h b/src/dstring.h
index 91a600c..6496a4f 100644
--- a/src/dstring.h
+++ b/src/dstring.h
@@ -14,19 +14,14 @@
 #include 
 
 /** Marker type for dynamic strings. */
-#if __clang__
+#if BFS_LINT && __clang__
 // Abuse __attribute__(aligned) to make a type that allows
 //
 //     dchar * -> char *
 //
-// conversions, but warns on
+// conversions, but warns (with Clang's -Walign-mismatch) on
 //
 //     char * -> dchar *
-//
-// (with Clang's -Walign-mismatch).  The alignment is not a lie, due to the
-// layout of struct dstring, but we only enable this on Clang because GCC
-// tracks alignment through array accesses, reporting UBSan errors on (and
-// maybe even miscompiling) dstr[1].
 typedef __attribute__((aligned(alignof(size_t)))) char dchar;
 #else
 typedef char dchar;
-- 
cgit v1.2.3


From 14f790c80acf0f9f005ce9e7a94246954533163b Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Thu, 5 Oct 2023 12:47:31 -0400
Subject: config: Fold !__FreeBSD__ into BFS_USE_SYS_CAPABILITY_H

---
 src/config.h | 2 +-
 src/fsade.h  | 2 +-
 2 files changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/config.h b/src/config.h
index 14c9305..e3048c4 100644
--- a/src/config.h
+++ b/src/config.h
@@ -88,7 +88,7 @@
 #  define BFS_USE_SYS_ACL_H BFS_HAS_SYS_ACL_H
 #endif
 #ifndef BFS_USE_SYS_CAPABILITY_H
-#  define BFS_USE_SYS_CAPABILITY_H BFS_HAS_SYS_CAPABILITY_H
+#  define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__)
 #endif
 #ifndef BFS_USE_SYS_EXTATTR_H
 #  define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H
diff --git a/src/fsade.h b/src/fsade.h
index 0d9ecaf..413938d 100644
--- a/src/fsade.h
+++ b/src/fsade.h
@@ -13,7 +13,7 @@
 
 #define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H
 
-#if !defined(BFS_CAN_CHECK_CAPABILITIES) && BFS_USE_SYS_CAPABILITY_H && !__FreeBSD__
+#if !defined(BFS_CAN_CHECK_CAPABILITIES) && BFS_USE_SYS_CAPABILITY_H
 #  include 
 #  ifdef CAP_CHOWN
 #    define BFS_CAN_CHECK_CAPABILITIES true
-- 
cgit v1.2.3


From 634359bb169311646f6369b21f0c90a9819fe2ce Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Thu, 5 Oct 2023 12:55:56 -0400
Subject: thread: Define thread_local

---
 src/config.h | 7 +++++++
 src/thread.h | 9 +++++++++
 2 files changed, 16 insertions(+)

diff --git a/src/config.h b/src/config.h
index e3048c4..862a839 100644
--- a/src/config.h
+++ b/src/config.h
@@ -59,6 +59,9 @@
 #if __has_include()
 #  define BFS_HAS_SYS_XATTR_H true
 #endif
+#if __has_include()
+#  define BFS_HAS_THREADS_H true
+#endif
 #if __has_include()
 #  define BFS_HAS_UTIL_H true
 #endif
@@ -74,6 +77,7 @@
 #define BFS_HAS_SYS_PARAM_H true
 #define BFS_HAS_SYS_SYSMACROS_H __GLIBC__
 #define BFS_HAS_SYS_XATTR_H __linux__
+#define BFS_HAS_THREADS_H (!__STDC_NO_THREADS__)
 #define BFS_HAS_UTIL_H __NetBSD__
 
 #endif // !__has_include
@@ -105,6 +109,9 @@
 #ifndef BFS_USE_SYS_XATTR_H
 #  define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H
 #endif
+#ifndef BFS_USE_THREADS_H
+#  define BFS_USE_THREADS_H BFS_HAS_THREADS_H
+#endif
 #ifndef BFS_USE_UTIL_H
 #  define BFS_USE_UTIL_H BFS_HAS_UTIL_H
 #endif
diff --git a/src/thread.h b/src/thread.h
index 45b5e1f..ab95a79 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -8,11 +8,20 @@
 #ifndef BFS_THREAD_H
 #define BFS_THREAD_H
 
+#include "config.h"
 #include "diag.h"
 #include 
 #include 
 #include 
 
+#if __STDC_VERSION__ < 202311L && !defined(thread_local)
+#  if BFS_USE_THREADS_H
+#    include 
+#  else
+#    define thread_local _Thread_local
+#  endif
+#endif
+
 #define thread_verify(expr, cond) \
 	bfs_verify((errno = (expr), (cond)), "%s: %s", #expr, strerror(errno))
 
-- 
cgit v1.2.3


From 428cf9c206beee3407ea3c5480b00f4cfbea95f5 Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Thu, 5 Oct 2023 12:56:36 -0400
Subject: bfstd: Add a thread-safe wrapper for strerror()

---
 src/bfstd.c    | 43 +++++++++++++++++++++++++++++++++++++++++++
 src/bfstd.h    | 11 +++++++++++
 src/color.c    |  2 +-
 src/eval.c     |  2 +-
 src/parse.c    |  4 ++--
 src/printf.c   |  2 +-
 src/thread.h   |  3 ++-
 src/xregex.c   |  3 ++-
 tests/bfstd.c  |  4 ++--
 tests/mksock.c |  2 +-
 tests/xtouch.c |  6 +++---
 11 files changed, 69 insertions(+), 13 deletions(-)

diff --git a/src/bfstd.c b/src/bfstd.c
index fcf4a6d..e9214d4 100644
--- a/src/bfstd.c
+++ b/src/bfstd.c
@@ -12,6 +12,7 @@
 #include 
 #include 
 #include 
+#include 
 #include 
 #include 
 #include 
@@ -276,6 +277,48 @@ char *xstpencpy(char *dest, char *end, const char *src, size_t n) {
 	}
 }
 
+const char *xstrerror(int errnum) {
+	int saved = errno;
+	const char *ret = NULL;
+	static thread_local char buf[256];
+
+#if __APPLE__
+	// No strerror_l() on macOS
+	if (strerror_r(errnum, buf, sizeof(buf)) == 0) {
+		ret = buf;
+	}
+#else
+#  if __NetBSD__
+	// NetBSD has no thread-specific locales
+	locale_t loc = LC_GLOBAL_LOCALE;
+#  else
+	locale_t loc = uselocale((locale_t)0);
+#  endif
+
+	locale_t copy = loc;
+	if (copy == LC_GLOBAL_LOCALE) {
+		copy = duplocale(copy);
+	}
+
+	if (copy != (locale_t)0) {
+		ret = strerror_l(errnum, loc);
+	}
+
+	if (loc == LC_GLOBAL_LOCALE) {
+		freelocale(copy);
+	}
+#endif
+
+	if (!ret) {
+		// Fallback for strerror_[lr]() or duplocale() failures
+		snprintf(buf, sizeof(buf), "Unknown error %d", errnum);
+		ret = buf;
+	}
+
+	errno = saved;
+	return ret;
+}
+
 void xstrmode(mode_t mode, char str[11]) {
 	strcpy(str, "----------");
 
diff --git a/src/bfstd.h b/src/bfstd.h
index fb77399..abde24e 100644
--- a/src/bfstd.h
+++ b/src/bfstd.h
@@ -166,6 +166,17 @@ char *xstpecpy(char *dest, char *end, const char *src);
  */
 char *xstpencpy(char *dest, char *end, const char *src, size_t n);
 
+/**
+ * Thread-safe strerror().
+ *
+ * @param errnum
+ *         An error number.
+ * @return
+ *         A string describing that error, which remains valid until the next
+ *         xstrerror() call in the same thread.
+ */
+const char *xstrerror(int errnum);
+
 /**
  * Format a mode like ls -l (e.g. -rw-r--r--).
  *
diff --git a/src/color.c b/src/color.c
index 788d35d..fbb5edf 100644
--- a/src/color.c
+++ b/src/color.c
@@ -1239,7 +1239,7 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) {
 				break;
 
 			case 'm':
-				if (dstrcat(&cfile->buffer, strerror(error)) != 0) {
+				if (dstrcat(&cfile->buffer, xstrerror(error)) != 0) {
 					return -1;
 				}
 				break;
diff --git a/src/eval.c b/src/eval.c
index 9f4896a..e0dd97b 100644
--- a/src/eval.c
+++ b/src/eval.c
@@ -1376,7 +1376,7 @@ static enum bftw_action eval_callback(const struct BFTW *ftwbuf, void *ptr) {
 
 	if (ftwbuf->type == BFS_ERROR) {
 		if (!eval_should_ignore(&state, ftwbuf->error)) {
-			eval_error(&state, "%s.\n", strerror(ftwbuf->error));
+			eval_error(&state, "%s.\n", xstrerror(ftwbuf->error));
 		}
 		state.action = BFTW_PRUNE;
 		goto done;
diff --git a/src/parse.c b/src/parse.c
index 7766a7b..976f7cb 100644
--- a/src/parse.c
+++ b/src/parse.c
@@ -1127,7 +1127,7 @@ static struct bfs_expr *parse_color(struct parser_state *state, int color, int a
 
 	if (color) {
 		if (!colors) {
-			parse_expr_error(state, expr, "Error parsing $$LS_COLORS: %s.\n", strerror(ctx->colors_error));
+			parse_expr_error(state, expr, "Error parsing $$LS_COLORS: %s.\n", xstrerror(ctx->colors_error));
 			bfs_expr_free(expr);
 			return NULL;
 		}
@@ -3741,7 +3741,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) {
 	}
 
 	if (state.use_color == COLOR_AUTO && !ctx->colors) {
-		bfs_warning(ctx, "Error parsing $$LS_COLORS: %s.\n\n", strerror(ctx->colors_error));
+		bfs_warning(ctx, "Error parsing $$LS_COLORS: %s.\n\n", xstrerror(ctx->colors_error));
 	}
 
 	if (bfs_optimize(ctx) != 0) {
diff --git a/src/printf.c b/src/printf.c
index 98bcb0f..704e26d 100644
--- a/src/printf.c
+++ b/src/printf.c
@@ -744,7 +744,7 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha
 				if (!directive.ptr) {
 					int error = errno;
 					bfs_expr_error(ctx, expr);
-					bfs_error(ctx, "Couldn't parse the mount table: %s.\n", strerror(error));
+					bfs_error(ctx, "Couldn't parse the mount table: %s.\n", xstrerror(error));
 					goto directive_error;
 				}
 				break;
diff --git a/src/thread.h b/src/thread.h
index ab95a79..a59033c 100644
--- a/src/thread.h
+++ b/src/thread.h
@@ -8,6 +8,7 @@
 #ifndef BFS_THREAD_H
 #define BFS_THREAD_H
 
+#include "bfstd.h"
 #include "config.h"
 #include "diag.h"
 #include 
@@ -23,7 +24,7 @@
 #endif
 
 #define thread_verify(expr, cond) \
-	bfs_verify((errno = (expr), (cond)), "%s: %s", #expr, strerror(errno))
+	bfs_verify((errno = (expr), (cond)), "%s: %s", #expr, xstrerror(errno))
 
 /**
  * Wrapper for pthread_create().
diff --git a/src/xregex.c b/src/xregex.c
index 87b692e..3df27f0 100644
--- a/src/xregex.c
+++ b/src/xregex.c
@@ -3,6 +3,7 @@
 
 #include "xregex.h"
 #include "alloc.h"
+#include "bfstd.h"
 #include "config.h"
 #include "diag.h"
 #include "sanity.h"
@@ -274,7 +275,7 @@ void bfs_regfree(struct bfs_regex *regex) {
 
 char *bfs_regerror(const struct bfs_regex *regex) {
 	if (!regex) {
-		return strdup(strerror(ENOMEM));
+		return strdup(xstrerror(ENOMEM));
 	}
 
 #if BFS_USE_ONIGURUMA
diff --git a/tests/bfstd.c b/tests/bfstd.c
index 33b3792..c386279 100644
--- a/tests/bfstd.c
+++ b/tests/bfstd.c
@@ -15,12 +15,12 @@
 /** Check the result of xdirname()/xbasename(). */
 static void check_base_dir(const char *path, const char *dir, const char *base) {
 	char *xdir = xdirname(path);
-	bfs_verify(xdir, "xdirname(): %s", strerror(errno));
+	bfs_verify(xdir, "xdirname(): %s", xstrerror(errno));
 	bfs_verify(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir);
 	free(xdir);
 
 	char *xbase = xbasename(path);
-	bfs_verify(xbase, "xbasename(): %s", strerror(errno));
+	bfs_verify(xbase, "xbasename(): %s", xstrerror(errno));
 	bfs_verify(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base);
 	free(xbase);
 }
diff --git a/tests/mksock.c b/tests/mksock.c
index 7023b4f..f3b61da 100644
--- a/tests/mksock.c
+++ b/tests/mksock.c
@@ -19,7 +19,7 @@
  * Print an error message.
  */
 static void errmsg(const char *cmd, const char *path) {
-	fprintf(stderr, "%s: '%s': %s.\n", cmd, path, strerror(errno));
+	fprintf(stderr, "%s: '%s': %s.\n", cmd, path, xstrerror(errno));
 }
 
 /**
diff --git a/tests/xtouch.c b/tests/xtouch.c
index 80fad8d..a4c4d40 100644
--- a/tests/xtouch.c
+++ b/tests/xtouch.c
@@ -197,14 +197,14 @@ int main(int argc, char *argv[]) {
 	if (rarg) {
 		struct stat buf;
 		if (fstatat(AT_FDCWD, rarg, &buf, at_flags(&args)) != 0) {
-			fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, strerror(errno));
+			fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, xstrerror(errno));
 			return EXIT_FAILURE;
 		}
 		times[0] = buf.st_atim;
 		times[1] = buf.st_mtim;
 	} else if (darg) {
 		if (xgetdate(darg, ×[0]) != 0) {
-			fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, strerror(errno));
+			fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, xstrerror(errno));
 			return EXIT_FAILURE;
 		}
 		times[1] = times[0];
@@ -237,7 +237,7 @@ int main(int argc, char *argv[]) {
 	for (; optind < argc; ++optind) {
 		const char *path = argv[optind];
 		if (xtouch(&args, path) != 0) {
-			fprintf(stderr, "%s: '%s': %s\n", cmd, path, strerror(errno));
+			fprintf(stderr, "%s: '%s': %s\n", cmd, path, xstrerror(errno));
 			ret = EXIT_FAILURE;
 		}
 	}
-- 
cgit v1.2.3


From 3f15559a82484d46942a68e18ba18c7e1c31aab3 Mon Sep 17 00:00:00 2001
From: Tavian Barnes 
Date: Thu, 5 Oct 2023 14:44:55 -0400
Subject: README: Realign badges

A recent GitHub change broke the nice alignment of the badges to the top
heading, so just put the badges underneath it for now.
---
 README.md | 7 +++----
 1 file changed, 3 insertions(+), 4 deletions(-)

diff --git a/README.md b/README.md
index a7ca335..44fbb79 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,12 @@
 
-

-bfs -
+# `bfs` + Version License CI Status Code coverage -

+
**[Features]   •   [Installation]   •   [Usage]   •   [Building]   •   [Hacking]   •   [Changelog]** -- cgit v1.2.3 From 8ddd85627f537131e3c8fdd3957fd263dac3dffe Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 5 Oct 2023 16:25:21 -0400 Subject: tests: Respect $NO_COLOR --- tests/tests.sh | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 663f5aa..61f17b4 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -19,7 +19,11 @@ export UBSAN_OPTIONS="$SAN_OPTIONS" export LS_COLORS="" unset BFS_COLORS -if [ -t 1 ]; then +function color_fd() { + [ -z "${NO_COLOR:-}" ] && [ -t "$1" ] +} + +if color_fd 1; then BLD=$'\033[01m' RED=$'\033[01;31m' GRN=$'\033[01;32m' @@ -443,7 +447,7 @@ fi function bfs_verbose() { if [ "$VERBOSE_COMMANDS" ]; then - if [ -t 3 ]; then + if color_fd 3; then printf "${GRN}%q${RST} " "${BFS[@]}" >&3 local expr_started= @@ -493,7 +497,7 @@ function check_exit() { } # Detect colored diff support -if [ -t 2 ] && diff --color=always /dev/null /dev/null 2>/dev/null; then +if color_fd 2 && diff --color=always /dev/null /dev/null 2>/dev/null; then DIFF="diff --color=always" else DIFF="diff" @@ -634,7 +638,7 @@ function update_eol() { if [ "$VERBOSE_TESTS" ]; then BOL='' -elif [ -t 1 ]; then +elif color_fd 1; then BOL='\r\033[K' # Workaround for bash 4: checkwinsize is off by default. We can turn it on, @@ -651,12 +655,14 @@ passed=0 failed=0 skipped=0 +if color_fd 1 || [ "$VERBOSE_TESTS" ]; then + TEST_FMT="${BOL}${YLW}%s${RST}${EOL}" +else + TEST_FMT="." +fi + for TEST in "${TEST_CASES[@]}"; do - if [[ -t 1 || "$VERBOSE_TESTS" ]]; then - printf "${BOL}${YLW}%s${RST}${EOL}" "$TEST" - else - printf "." - fi + printf "$TEST_FMT" "$TEST" OUT="$TMP/$TEST.out" mkdir -p "${OUT%/*}" -- cgit v1.2.3 From 5a16a1e2b33733f0e6dff4ddfaed2662c3ba7ccb Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 5 Oct 2023 16:25:56 -0400 Subject: tests/bfs: Add tests for color autodetection --- tests/bfs/color_auto.out | 27 +++++++++++++++++++++++++++ tests/bfs/color_auto.sh | 6 ++++++ tests/bfs/nocolor_env.out | 27 +++++++++++++++++++++++++++ tests/bfs/nocolor_env.sh | 5 +++++ 4 files changed, 65 insertions(+) create mode 100644 tests/bfs/color_auto.out create mode 100644 tests/bfs/color_auto.sh create mode 100644 tests/bfs/nocolor_env.out create mode 100644 tests/bfs/nocolor_env.sh diff --git a/tests/bfs/color_auto.out b/tests/bfs/color_auto.out new file mode 100644 index 0000000..a439814 --- /dev/null +++ b/tests/bfs/color_auto.out @@ -0,0 +1,27 @@ +$'rainbow/\e[1m' +$'rainbow/\e[1m/'$'\e[0m' +rainbow +rainbow/exec.sh +rainbow/socket +rainbow/broken +rainbow/chardev_link +rainbow/link.txt +rainbow/sticky_ow +rainbow/sgid +rainbow/pipe +rainbow/ow +rainbow/sugid +rainbow/suid +rainbow/sticky +rainbow/file.dat +rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ +rainbow/mh1 +rainbow/mh2 +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/color_auto.sh b/tests/bfs/color_auto.sh new file mode 100644 index 0000000..aa2eb02 --- /dev/null +++ b/tests/bfs/color_auto.sh @@ -0,0 +1,6 @@ +command -v unbuffer &>/dev/null || skip + +unset NO_COLOR +unbuffer "${BFS[@]}" rainbow >"$OUT" +sort_output +diff_output diff --git a/tests/bfs/nocolor_env.out b/tests/bfs/nocolor_env.out new file mode 100644 index 0000000..d51d24d --- /dev/null +++ b/tests/bfs/nocolor_env.out @@ -0,0 +1,27 @@ +rainbow +rainbow/ +rainbow// +rainbow/broken +rainbow/chardev_link +rainbow/exec.sh +rainbow/file.dat +rainbow/file.txt +rainbow/link.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ +rainbow/mh1 +rainbow/mh2 +rainbow/ow +rainbow/pipe +rainbow/sgid +rainbow/socket +rainbow/sticky +rainbow/sticky_ow +rainbow/sugid +rainbow/suid +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/nocolor_env.sh b/tests/bfs/nocolor_env.sh new file mode 100644 index 0000000..399bdb0 --- /dev/null +++ b/tests/bfs/nocolor_env.sh @@ -0,0 +1,5 @@ +command -v unbuffer &>/dev/null || skip + +NO_COLOR=1 unbuffer "${BFS[@]}" rainbow >"$OUT" +sort_output +diff_output -- cgit v1.2.3 From 6e12ff030a380d0f13c505b5285ceb589761b68d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 5 Oct 2023 17:18:40 -0400 Subject: tests/bfs: Add a test that runs -status --- tests/bfs/status.sh | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 tests/bfs/status.sh diff --git a/tests/bfs/status.sh b/tests/bfs/status.sh new file mode 100644 index 0000000..30bdfa7 --- /dev/null +++ b/tests/bfs/status.sh @@ -0,0 +1,3 @@ +command -v unbuffer &>/dev/null || skip + +unbuffer "${BFS[@]}" basic -status >"$OUT" -- cgit v1.2.3 From 2d0cd364b63e26d7ede70cc1d6f85bea87850d84 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 5 Oct 2023 17:29:52 -0400 Subject: ci: Install expect for unbuffer --- .github/workflows/ci.yml | 5 +++++ .github/workflows/codecov.yml | 1 + 2 files changed, 6 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e781055..065fcfd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,6 +16,7 @@ jobs: sudo dpkg --add-architecture i386 sudo apt-get update -y sudo apt-get install -y \ + expect \ gcc-multilib \ libgcc-s1:i386 \ acl \ @@ -49,6 +50,10 @@ jobs: steps: - uses: actions/checkout@v3 + - name: Install dependencies + run: | + brew install expect + - name: Run tests run: | make -j$(sysctl -n hw.ncpu) distcheck diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index b06ea62..073479a 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -13,6 +13,7 @@ jobs: run: | sudo apt-get update -y sudo apt-get install -y \ + expect \ gcc \ acl \ libacl1-dev \ -- cgit v1.2.3 From 5e7b5eeb59f9f46ce916aaf968dd42570570580a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 6 Oct 2023 11:42:06 -0400 Subject: tests: New bfs_pty wrapper for unbuffer --- tests/bfs/color_auto.sh | 4 +--- tests/bfs/nocolor_env.sh | 2 +- tests/bfs/status.sh | 4 +--- tests/tests.sh | 14 ++++++++++++++ 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/tests/bfs/color_auto.sh b/tests/bfs/color_auto.sh index aa2eb02..7e875cc 100644 --- a/tests/bfs/color_auto.sh +++ b/tests/bfs/color_auto.sh @@ -1,6 +1,4 @@ -command -v unbuffer &>/dev/null || skip - unset NO_COLOR -unbuffer "${BFS[@]}" rainbow >"$OUT" +bfs_pty rainbow >"$OUT" sort_output diff_output diff --git a/tests/bfs/nocolor_env.sh b/tests/bfs/nocolor_env.sh index 399bdb0..0a17fb2 100644 --- a/tests/bfs/nocolor_env.sh +++ b/tests/bfs/nocolor_env.sh @@ -1,5 +1,5 @@ command -v unbuffer &>/dev/null || skip -NO_COLOR=1 unbuffer "${BFS[@]}" rainbow >"$OUT" +NO_COLOR=1 bfs_pty rainbow >"$OUT" sort_output diff_output diff --git a/tests/bfs/status.sh b/tests/bfs/status.sh index 30bdfa7..d3859e2 100644 --- a/tests/bfs/status.sh +++ b/tests/bfs/status.sh @@ -1,3 +1 @@ -command -v unbuffer &>/dev/null || skip - -unbuffer "${BFS[@]}" basic -status >"$OUT" +bfs_pty basic -status >"$OUT" diff --git a/tests/tests.sh b/tests/tests.sh index 61f17b4..5297446 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -488,6 +488,20 @@ function invoke_bfs() { fi } +function bfs_pty() { + command -v unbuffer &>/dev/null || skip + + bfs_verbose "$@" + unbuffer bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" + local status="$?" + + if ((status > 125)); then + exit "$status" + else + return "$status" + fi +} + function check_exit() { local expected="$1" local actual="0" -- cgit v1.2.3 From 14c610d1e2849c1d162adf6f43b72e023780ffde Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 6 Oct 2023 11:56:31 -0400 Subject: tests: Check for expect_unbuffer too That's where FreeBSD installs it. --- tests/tests.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 5297446..662efd9 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -488,11 +488,17 @@ function invoke_bfs() { fi } +if command -v unbuffer &>/dev/null; then + UNBUFFER=unbuffer +elif command -v expect_unbuffer &>/dev/null; then + UNBUFFER=expect_unbuffer +fi + function bfs_pty() { - command -v unbuffer &>/dev/null || skip + test -n "$UNBUFFER" || skip bfs_verbose "$@" - unbuffer bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" + "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" local status="$?" if ((status > 125)); then -- cgit v1.2.3 From bcfe9c4e846bc97f97967c7df95e6b0a08a9a0ad Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 6 Oct 2023 12:00:02 -0400 Subject: tests/bfs/status: Try to test SIGWINCH --- tests/bfs/status.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/bfs/status.sh b/tests/bfs/status.sh index d3859e2..83e12d3 100644 --- a/tests/bfs/status.sh +++ b/tests/bfs/status.sh @@ -1 +1 @@ -bfs_pty basic -status >"$OUT" +bfs_pty basic -status -print -depth 0 -exec stty cols 123 rows 14 \; >"$OUT" -- cgit v1.2.3 From 054ef7f719ce6fd2167f1c1b4433feaa438bebfc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 6 Oct 2023 12:37:01 -0400 Subject: alloc: Test allocation size overflows --- src/alloc.h | 4 ++-- tests/alloc.c | 12 ++++++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/src/alloc.h b/src/alloc.h index 5f0c423..fd3e5f0 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -132,11 +132,11 @@ void *zalloc(size_t align, size_t size); /** Allocate memory for an array. */ #define ALLOC_ARRAY(type, count) \ - (type *)alloc(alignof(type), sizeof_array(type, count)); + (type *)alloc(alignof(type), sizeof_array(type, count)) /** Allocate zeroed memory for an array. */ #define ZALLOC_ARRAY(type, count) \ - (type *)zalloc(alignof(type), sizeof_array(type, count)); + (type *)zalloc(alignof(type), sizeof_array(type, count)) /** Allocate memory for a flexible struct. */ #define ALLOC_FLEX(type, member, count) \ diff --git a/tests/alloc.c b/tests/alloc.c index 9e6e892..382131f 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -3,6 +3,7 @@ #include "../src/alloc.h" #include "../src/diag.h" +#include #include int main(void) { @@ -13,13 +14,20 @@ int main(void) { }; bfs_verify(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible)); bfs_verify(sizeof_flex(struct flexible, bar, 16) % alignof(struct flexible) == 0); - bfs_verify(sizeof_flex(struct flexible, bar, SIZE_MAX / sizeof(int) + 1) - == align_floor(alignof(struct flexible), SIZE_MAX)); + + size_t too_many = SIZE_MAX / sizeof(int) + 1; + bfs_verify(sizeof_flex(struct flexible, bar, too_many) == align_floor(alignof(struct flexible), SIZE_MAX)); // Corner case: sizeof(type) > align_ceil(alignof(type), offsetof(type, member)) // Doesn't happen in typical ABIs bfs_verify(flex_size(8, 16, 4, 4, 1) == 16); + // Make sure we detect allocation size overflows + bfs_verify(ALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); + bfs_verify(ZALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); + bfs_verify(ALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW); + bfs_verify(ZALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW); + // varena tests struct varena varena; VARENA_INIT(&varena, struct flexible, bar); -- cgit v1.2.3 From 5e1d22f464fa6ded047dcfecbd93dce7a088a4f3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 6 Oct 2023 19:08:33 -0400 Subject: bar: Replace volatile sig_atomic_t with actual atomics --- src/bar.c | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/bar.c b/src/bar.c index e0b9393..57cc4dd 100644 --- a/src/bar.c +++ b/src/bar.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "bar.h" +#include "atomic.h" #include "bfstd.h" #include "bit.h" #include "config.h" @@ -16,8 +17,8 @@ struct bfs_bar { int fd; - volatile sig_atomic_t width; - volatile sig_atomic_t height; + atomic unsigned int width; + atomic unsigned int height; }; /** The global status bar instance. */ @@ -33,8 +34,8 @@ static int bfs_bar_getsize(struct bfs_bar *bar) { return -1; } - bar->width = ws.ws_col; - bar->height = ws.ws_row; + store(&bar->width, ws.ws_col, relaxed); + store(&bar->height, ws.ws_row, relaxed); return 0; #else errno = ENOTSUP; @@ -74,8 +75,9 @@ static int bfs_bar_resize(struct bfs_bar *bar) { "\033[;"; // DECSTBM: Set scrollable region // DECSTBM takes the height as the second argument + unsigned int height = load(&bar->height, relaxed); char *ptr = esc_seq + strlen(esc_seq); - ptr = ass_itoa(ptr, bar->height - 1); + ptr = ass_itoa(ptr, height - 1); strcpy(ptr, "r" // DECSTBM @@ -178,13 +180,14 @@ struct bfs_bar *bfs_bar_show(void) { sigaction(SIGWINCH, &sa, NULL); #endif + unsigned int height = load(&the_bar.height, relaxed); bfs_bar_printf(&the_bar, "\n" // Make space for the bar "\0337" // DECSC: Save cursor "\033[;%ur" // DECSTBM: Set scrollable region "\0338" // DECRC: Restore cursor "\033[1A", // CUU: Move cursor up 1 row - (unsigned int)(the_bar.height - 1) + height - 1 ); return &the_bar; @@ -197,10 +200,11 @@ fail: } unsigned int bfs_bar_width(const struct bfs_bar *bar) { - return bar->width; + return load(&bar->width, relaxed); } int bfs_bar_update(struct bfs_bar *bar, const char *str) { + unsigned int height = load(&bar->height, relaxed); return bfs_bar_printf(bar, "\0337" // DECSC: Save cursor "\033[%u;0f" // HVP: Move cursor to row, column @@ -209,7 +213,7 @@ int bfs_bar_update(struct bfs_bar *bar, const char *str) { "%s" "\033[27m" // SGR reverse video off "\0338", // DECRC: Restore cursor - (unsigned int)bar->height, + height, str ); } -- cgit v1.2.3 From 20cf56fd225d341ab1ae1c6924e73b7e28583b1c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 10 Oct 2023 11:14:18 -0400 Subject: bench: Allow format specifiers in (sub)group headers --- bench/bench.sh | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/bench/bench.sh b/bench/bench.sh index 09564ac..e0943d4 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -266,17 +266,17 @@ do-hyperfine() { # Print the header for a benchmark group group() { - printf '## %s\n\n' "$1" | tee -a "$BENCH_DIR/bench.md" + printf "## $1\\n\\n" "${@:2}" | tee -a "$BENCH_DIR/bench.md" } # Print the header for a benchmark subgroup subgroup() { - printf '### %s\n\n' "$1" | tee -a "$BENCH_DIR/bench.md" + printf "### $1\\n\\n" "${@:2}" | tee -a "$BENCH_DIR/bench.md" } # Print the header for a benchmark sub-subgroup subsubgroup() { - printf '#### %s\n\n' "$1" | tee -a "$BENCH_DIR/bench.md" + printf "#### $1\\n\\n" "${@:2}" | tee -a "$BENCH_DIR/bench.md" } # Benchmark the complete traversal of a directory tree @@ -284,7 +284,7 @@ subsubgroup() { bench-complete-corpus() { total=$(./bin/bfs "$2" -printf '.' | wc -c) - subgroup "$1 ($total files)" + subgroup "%s (%'d files)" "$1" "$total" cmds=() for bfs in "${BFS[@]}"; do @@ -318,14 +318,14 @@ bench-early-quit-corpus() { dir="$2" max_depth=$(./bin/bfs "$dir" -printf '%d\n' | sort -rn | head -n1) - subgroup "$1 (depth $max_depth)" + subgroup '%s (depth %d)' "$1" "$max_depth" # Save the list of unique filenames, along with their depth UNIQ="$BENCH_DIR/uniq" ./bin/bfs "$dir" -printf '%d %f\n' | sort -k2 | uniq -uf1 >"$UNIQ" for ((i = 2; i <= max_depth; i *= 2)); do - subsubgroup "Depth $i" + subsubgroup 'Depth %d' "$i" # Sample random uniquely-named files at depth $i export FILES="$BENCH_DIR/uniq-$i" @@ -364,7 +364,7 @@ bench-early-quit() { # Benchmark printing paths without colors bench-print-nocolor() { - subsubgroup "$1" + subsubgroup '%s' "$1" cmds=() for bfs in "${BFS[@]}"; do @@ -384,7 +384,7 @@ bench-print-nocolor() { # Benchmark printing paths with colors bench-print-color() { - subsubgroup "$1" + subsubgroup '%s' "$1" cmds=() for bfs in "${BFS[@]}"; do @@ -417,10 +417,10 @@ bench-print() { # Benchmark search strategies bench-strategies-corpus() { - subgroup "$1" + subgroup '%s' "$1" for bfs in "${BFS[@]}"; do - subsubgroup "$bfs" + subsubgroup '%s' "$bfs" cmds=("$bfs -S "{bfs,dfs,ids,eds}" $2") do-hyperfine "${cmds[@]}" done -- cgit v1.2.3 From cad3a75f16a4f970e96830359cb62f3802ec0b6c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 10 Oct 2023 11:21:38 -0400 Subject: bench: Don't print paths in the --strategies benchmark --- bench/bench.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/bench.sh b/bench/bench.sh index e0943d4..1294fd1 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -421,7 +421,7 @@ bench-strategies-corpus() { for bfs in "${BFS[@]}"; do subsubgroup '%s' "$bfs" - cmds=("$bfs -S "{bfs,dfs,ids,eds}" $2") + cmds=("$bfs -S "{bfs,dfs,ids,eds}" $2 -false") do-hyperfine "${cmds[@]}" done } -- cgit v1.2.3 From 8719b66e6f0c5aac71e5245490269d4a644f6fe1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 10 Oct 2023 11:34:26 -0400 Subject: bench: Group by strategy in comparison mode --- bench/bench.sh | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/bench/bench.sh b/bench/bench.sh index 1294fd1..8fe5ab9 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -419,11 +419,20 @@ bench-print() { bench-strategies-corpus() { subgroup '%s' "$1" - for bfs in "${BFS[@]}"; do - subsubgroup '%s' "$bfs" - cmds=("$bfs -S "{bfs,dfs,ids,eds}" $2 -false") + if ((${#BFS[@]} == 1)); then + cmds=("$BFS -S "{bfs,dfs,ids,eds}" $2 -false") do-hyperfine "${cmds[@]}" - done + else + for S in bfs dfs ids eds; do + subsubgroup '`-S %s`' "$S" + + cmds=() + for bfs in "${BFS[@]}"; do + cmds+=("$bfs -S $S $2 -false") + done + do-hyperfine "${cmds[@]}" + done + fi } # All search strategy benchmarks -- cgit v1.2.3 From 04786f44089a3d650330255770ee9dc5d22e4c02 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 10 Oct 2023 11:37:40 -0400 Subject: bench: New --jobs benchmark --- bench/bench.sh | 68 ++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 4 deletions(-) diff --git a/bench/bench.sh b/bench/bench.sh index 8fe5ab9..7042319 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -19,6 +19,7 @@ COMPLETE_DEFAULT=(linux rust chromium) EARLY_QUIT_DEFAULT=(chromium) PRINT_DEFAULT=(linux) STRATEGIES_DEFAULT=(linux) +JOBS_DEFAULT=(rust) usage() { printf 'Usage: tailfin run %s [--default]\n' "${BASH_SOURCE[0]}" @@ -35,15 +36,19 @@ usage() { printf ' --early-quit[=CORPUS]\n' printf ' Early quitting benchmark. \n' - printf ' Default corpus is --early-quit=chromium\n\n' "${EARLY_QUIT_DEFAULT[*]}" + printf ' Default corpus is --early-quit=%s\n\n' "${EARLY_QUIT_DEFAULT[*]}" printf ' --print[=CORPUS]\n' printf ' Path printing benchmark. \n' - printf ' Default corpus is --print=linux\n\n' "${PRINT_DEFAULT[*]}" + printf ' Default corpus is --print=%s\n\n' "${PRINT_DEFAULT[*]}" printf ' --strategies[=CORPUS]\n' printf ' Search strategy benchmark.\n' - printf ' Default corpus is --strategies=linux\n\n' "${STRATEGIES_DEFAULT[*]}" + printf ' Default corpus is --strategies=%s\n\n' "${STRATEGIES_DEFAULT[*]}" + + printf ' --jobs[=CORPUS]\n' + printf ' Parallelism benchmark.\n' + printf ' Default corpus is --jobs=%s\n\n' "${JOBS_DEFAULT[*]}" printf ' --build=COMMIT\n' printf ' Build this bfs commit and benchmark it. Specify multiple times to\n' @@ -103,6 +108,7 @@ setup() { EARLY_QUIT=() PRINT=() STRATEGIES=() + JOBS=() for arg; do case "$arg" in @@ -159,11 +165,18 @@ setup() { --strategies=*) read -ra STRATEGIES <<<"${arg#*=}" ;; + --jobs) + JOBS=("${JOBS_DEFAULT[@]}") + ;; + --jobs=*) + read -ra JOBS <<<"${arg#*=}" + ;; --default) COMPLETE=("${COMPLETE_DEFAULT[@]}") EARLY_QUIT=("${EARLY_QUIT_DEFAULT[@]}") PRINT=("${PRINT_DEFAULT[@]}") STRATEGIES=("${STRATEGIES_DEFAULT[@]}") + JOBS=("${JOBS_DEFAULT[@]}") ;; --help) usage @@ -187,7 +200,7 @@ setup() { as-user mkdir -p bench/corpus declare -A cloned=() - for corpus in "${COMPLETE[@]}" "${EARLY_QUIT[@]}" "${PRINT[@]}" "${STRATEGIES[@]}"; do + for corpus in "${COMPLETE[@]}" "${EARLY_QUIT[@]}" "${PRINT[@]}" "${STRATEGIES[@]}" "${JOBS[@]}"; do if ((cloned["$corpus"])); then continue fi @@ -236,6 +249,7 @@ setup() { export_array EARLY_QUIT export_array PRINT export_array STRATEGIES + export_array JOBS if ((UID == 0)); then turbo-off @@ -446,6 +460,50 @@ bench-strategies() { fi } +# Benchmark parallelism +bench-jobs-corpus() { + subgroup '%s' "$1" + + if ((${#BFS[@]} + ${#FD[@]} == 1)); then + cmds=() + for bfs in "${BFS[@]}"; do + cmds+=("$bfs -j"{1,2,3,4,6,8,12,16}" $2 -false") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -j"{1,2,3,4,6,8,12,16}" -u '^$' $2") + done + + do-hyperfine "${cmds[@]}" + else + for j in 1 2 3 4 6 8 12 16; do + subsubgroup '`-j%d`' $j + + cmds=() + for bfs in "${BFS[@]}"; do + cmds+=("$bfs -j$j $2 -false") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -j$j -u '^$' $2") + done + + do-hyperfine "${cmds[@]}" + done + fi +} + +# All parallelism benchmarks +bench-jobs() { + if (($#)); then + group "Parallelism" + + for corpus; do + bench-jobs-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" + done + fi +} + # Print benchmarked versions bench-versions() { subgroup "Versions" @@ -491,10 +549,12 @@ bench() { import_array EARLY_QUIT import_array PRINT import_array STRATEGIES + import_array JOBS bench-complete "${COMPLETE[@]}" bench-early-quit "${EARLY_QUIT[@]}" bench-print "${PRINT[@]}" bench-strategies "${STRATEGIES[@]}" + bench-jobs "${JOBS[@]}" bench-details } -- cgit v1.2.3 From 778659e5fc758515428db40c353f45b201264802 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 10 Oct 2023 11:51:34 -0400 Subject: bench: Default to --strategies=rust --- bench/bench.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bench/bench.sh b/bench/bench.sh index 7042319..be5a15b 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -18,7 +18,7 @@ declare -gA TAGS=( COMPLETE_DEFAULT=(linux rust chromium) EARLY_QUIT_DEFAULT=(chromium) PRINT_DEFAULT=(linux) -STRATEGIES_DEFAULT=(linux) +STRATEGIES_DEFAULT=(rust) JOBS_DEFAULT=(rust) usage() { -- cgit v1.2.3 From ed2a50d63b25a7b72a32be07a33331544f587296 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 09:44:27 -0400 Subject: bftw: Let iterative deepening work depth-first when sorting --- src/bftw.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bftw.c b/src/bftw.c index 902a3fa..c58001d 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -862,7 +862,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting - if (state->strategy != BFTW_DFS && have_files) { + if (state->strategy == BFTW_BFS && have_files) { return false; } } else { -- cgit v1.2.3 From a029d95b5736a74879f32089514a5a6b63d6efbc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 11:24:49 -0400 Subject: bftw: Fix unbuffered depth-first searches bftw() implements depth-first search by appending files to a batch, then prepending the batch to the queue. When we switched to separate file/ directory queues, this was only implemented for the file queue. Unbuffered searches don't use the file queue, so they were all breadth- first in practice. This meant that iterative deepening (-S ids) was actually "iterative deepening *breadth*-first search," a horrible strategy with no advantage over regular breadth-first search. Now it performs iterative deepening *depth*-first search, which at least limits its memory consumption. Fixes: c1b16b49988ecff17ae30978ea14798d95b80018 --- src/bftw.c | 56 +++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index c58001d..6eec42c 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -422,6 +422,8 @@ struct bftw_state { /** The number of I/O threads. */ size_t nthreads; + /** A batch of directories to open. */ + struct bftw_list dir_batch; /** The queue of directories to open. */ struct bftw_list to_open; /** The queue of directories to read. */ @@ -429,10 +431,10 @@ struct bftw_state { /** The queue of unpinned directories to unwrap. */ struct bftw_list to_close; + /** A batch of files to enqueue. */ + struct bftw_list file_batch; /** The queue of files to visit. */ struct bftw_list to_visit; - /** A batch of files to enqueue. */ - struct bftw_list batch; /** The current path. */ dchar *path; @@ -499,12 +501,14 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } state->nthreads = nthreads; + + SLIST_INIT(&state->dir_batch); SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); SLIST_INIT(&state->to_close); + SLIST_INIT(&state->file_batch); SLIST_INIT(&state->to_visit); - SLIST_INIT(&state->batch); state->path = NULL; state->file = NULL; @@ -833,11 +837,32 @@ fail: return -1; } +/** Open a batch of directories asynchronously. */ +static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) { + for_slist (struct bftw_file, dir, queue) { + if (bftw_ioq_opendir(state, dir) != 0) { + break; + } + SLIST_POP(queue); + } +} + /** Push a directory onto the queue. */ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bfs_assert(file->type == BFS_DIR); - SLIST_APPEND(&state->to_open, file); + struct bftw_list *queue; + if (state->strategy == BFTW_BFS || (state->flags & BFTW_BUFFER)) { + // In breadth-first mode, or if we're already buffering files, + // we can push directly to the to_open queue + queue = &state->to_open; + } else { + // For a depth-first, unbuffered search, add directories to a + // batch, then push the patch to the front of the queue + queue = &state->dir_batch; + } + + SLIST_APPEND(queue, file); if (state->flags & BFTW_SORT) { // When sorting, directories are kept in order on the to_read @@ -845,12 +870,7 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { SLIST_APPEND(&state->to_read, file, to_read); } - for_slist (struct bftw_file, dir, &state->to_open) { - if (bftw_ioq_opendir(state, dir) != 0) { - break; - } - SLIST_POP(&state->to_open); - } + bftw_ioq_opendirs(state, queue); } /** Pop a directory to read from the queue. */ @@ -1333,13 +1353,18 @@ static void bftw_list_sort(struct bftw_list *list) { /** Finish adding a batch of files. */ static void bftw_batch_finish(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - bftw_list_sort(&state->batch); + bftw_list_sort(&state->file_batch); } if (state->strategy != BFTW_BFS) { - SLIST_EXTEND(&state->batch, &state->to_visit); + SLIST_EXTEND(&state->dir_batch, &state->to_open); + SLIST_EXTEND(&state->file_batch, &state->to_visit); } - SLIST_EXTEND(&state->to_visit, &state->batch); + + SLIST_EXTEND(&state->to_open, &state->dir_batch); + SLIST_EXTEND(&state->to_visit, &state->file_batch); + + bftw_ioq_opendirs(state, &state->to_open); } /** Close the current directory. */ @@ -1381,7 +1406,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - SLIST_APPEND(&state->batch, file); + SLIST_APPEND(&state->file_batch, file); return 0; } @@ -1429,7 +1454,8 @@ static int bftw_state_destroy(struct bftw_state *state) { state->ioq = NULL; } - SLIST_EXTEND(&state->to_visit, &state->batch); + SLIST_EXTEND(&state->to_open, &state->dir_batch); + SLIST_EXTEND(&state->to_visit, &state->file_batch); do { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); -- cgit v1.2.3 From 257227326fe60fe70e80433fd34d1ebcb2f9f623 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 10:41:04 -0400 Subject: bftw: Don't force buffering for parallel dfs --- src/bftw.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 6eec42c..c0bff75 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -456,6 +456,33 @@ struct bftw_state { struct BFTW ftwbuf; }; +/** Check if we have to buffer files before visiting them. */ +static bool bftw_must_buffer(const struct bftw_state *state) { + if (state->flags & BFTW_SORT) { + // Have to buffer the files to sort them + return true; + } + + if (state->strategy == BFTW_DFS && state->nthreads == 0) { + // Without buffering, we would get a not-quite-depth-first + // ordering: + // + // a + // b + // a/c + // a/c/d + // b/e + // b/e/f + // + // This is okay for iterative deepening, since the caller only + // sees files at the target depth. We also deem it okay for + // parallel searches, since the order is unpredictable anyway. + return true; + } + + return false; +} + /** Initialize the bftw() state. */ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *args) { state->paths = args->paths; @@ -465,11 +492,6 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->flags = args->flags; state->strategy = args->strategy; state->mtab = args->mtab; - - if ((state->flags & BFTW_SORT) || state->strategy == BFTW_DFS) { - state->flags |= BFTW_BUFFER; - } - state->error = 0; if (args->nopenfd < 2) { @@ -501,6 +523,9 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } state->nthreads = nthreads; + if (bftw_must_buffer(state)) { + state->flags |= BFTW_BUFFER; + } SLIST_INIT(&state->dir_batch); SLIST_INIT(&state->to_open); -- cgit v1.2.3 From da5c9dd34f65989c842cfb831b8592157dd8ed34 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 13:09:11 -0400 Subject: diag: Move enum debug_flags out of ctx.h --- src/ctx.c | 25 ------------------------- src/ctx.h | 28 +--------------------------- src/diag.c | 25 +++++++++++++++++++++++++ src/diag.h | 29 ++++++++++++++++++++++++++++- tests/alloc.c | 1 + 5 files changed, 55 insertions(+), 53 deletions(-) diff --git a/src/ctx.c b/src/ctx.c index 9a24a33..561ecae 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -17,31 +17,6 @@ #include #include -const char *debug_flag_name(enum debug_flags flag) { - switch (flag) { - case DEBUG_COST: - return "cost"; - case DEBUG_EXEC: - return "exec"; - case DEBUG_OPT: - return "opt"; - case DEBUG_RATES: - return "rates"; - case DEBUG_SEARCH: - return "search"; - case DEBUG_STAT: - return "stat"; - case DEBUG_TREE: - return "tree"; - - case DEBUG_ALL: - break; - } - - bfs_bug("Unrecognized debug flag"); - return "???"; -} - struct bfs_ctx *bfs_ctx_new(void) { struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx); if (!ctx) { diff --git a/src/ctx.h b/src/ctx.h index 2b8e8cb..96406bd 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -10,38 +10,12 @@ #include "bftw.h" #include "config.h" +#include "diag.h" #include "trie.h" #include #include #include -/** - * Various debugging flags. - */ -enum debug_flags { - /** Print cost estimates. */ - DEBUG_COST = 1 << 0, - /** Print executed command details. */ - DEBUG_EXEC = 1 << 1, - /** Print optimization details. */ - DEBUG_OPT = 1 << 2, - /** Print rate information. */ - DEBUG_RATES = 1 << 3, - /** Trace the filesystem traversal. */ - DEBUG_SEARCH = 1 << 4, - /** Trace all stat() calls. */ - DEBUG_STAT = 1 << 5, - /** Print the parse tree. */ - DEBUG_TREE = 1 << 6, - /** All debug flags. */ - DEBUG_ALL = (1 << 7) - 1, -}; - -/** - * Convert a debug flag to a string. - */ -const char *debug_flag_name(enum debug_flags flag); - /** * The execution context for bfs. */ diff --git a/src/diag.c b/src/diag.c index fa66525..bf2343d 100644 --- a/src/diag.c +++ b/src/diag.c @@ -27,6 +27,31 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { abort(); } +const char *debug_flag_name(enum debug_flags flag) { + switch (flag) { + case DEBUG_COST: + return "cost"; + case DEBUG_EXEC: + return "exec"; + case DEBUG_OPT: + return "opt"; + case DEBUG_RATES: + return "rates"; + case DEBUG_SEARCH: + return "search"; + case DEBUG_STAT: + return "stat"; + case DEBUG_TREE: + return "tree"; + + case DEBUG_ALL: + break; + } + + bfs_bug("Unrecognized debug flag"); + return "???"; +} + void bfs_perror(const struct bfs_ctx *ctx, const char *str) { bfs_error(ctx, "%s: %m.\n", str); } diff --git a/src/diag.h b/src/diag.h index fea8847..838a794 100644 --- a/src/diag.h +++ b/src/diag.h @@ -9,7 +9,6 @@ #define BFS_DIAG_H #include "config.h" -#include "ctx.h" #include /** @@ -84,8 +83,36 @@ noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); # define bfs_assert bfs_verify #endif +struct bfs_ctx; struct bfs_expr; +/** + * Various debugging flags. + */ +enum debug_flags { + /** Print cost estimates. */ + DEBUG_COST = 1 << 0, + /** Print executed command details. */ + DEBUG_EXEC = 1 << 1, + /** Print optimization details. */ + DEBUG_OPT = 1 << 2, + /** Print rate information. */ + DEBUG_RATES = 1 << 3, + /** Trace the filesystem traversal. */ + DEBUG_SEARCH = 1 << 4, + /** Trace all stat() calls. */ + DEBUG_STAT = 1 << 5, + /** Print the parse tree. */ + DEBUG_TREE = 1 << 6, + /** All debug flags. */ + DEBUG_ALL = (1 << 7) - 1, +}; + +/** + * Convert a debug flag to a string. + */ +const char *debug_flag_name(enum debug_flags flag); + /** * Like perror(), but decorated like bfs_error(). */ diff --git a/tests/alloc.c b/tests/alloc.c index 382131f..2334241 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -5,6 +5,7 @@ #include "../src/diag.h" #include #include +#include int main(void) { // Check sizeof_flex() -- cgit v1.2.3 From 1addab1e5f12cb0fddfa92872bf45653352cc212 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 13:18:00 -0400 Subject: list: Assert that we're not inserting already-attached nodes --- src/list.h | 66 ++++++++++++++++++++++++++++++++++++++++++-------------------- src/trie.c | 1 + 2 files changed, 46 insertions(+), 21 deletions(-) diff --git a/src/list.h b/src/list.h index 5587543..61c22e3 100644 --- a/src/list.h +++ b/src/list.h @@ -82,6 +82,7 @@ #ifndef BFS_LIST_H #define BFS_LIST_H +#include "diag.h" #include #include @@ -205,6 +206,27 @@ #define SLIST_EMPTY_(list) \ (SLIST_CHECK_(list), !list->head) +/** + * Check if an item is attached to a singly-linked list. + * + * @param list + * The list to check. + * @param item + * The item to check. + * @param node (optional) + * If specified, use item->node.next rather than item->next. + * @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, next) \ + (item->next || list->tail == &item->next) + /** * Insert an item into a singly-linked list. * @@ -224,6 +246,7 @@ SLIST_INSERT__((list), (cursor), (item), LIST_NEXT_(__VA_ARGS__)) #define SLIST_INSERT__(list, cursor, item, next) LIST_VOID_( \ + bfs_assert(!SLIST_ATTACHED__(list, item, next)), \ item->next = *cursor, \ *cursor = item, \ list->tail = item->next ? list->tail : &item->next) @@ -425,6 +448,27 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void #define LIST_PREPEND(list, ...) \ LIST_INSERT(list, NULL, __VA_ARGS__) +/** + * Check if an item is attached to a doubly-linked list. + * + * @param list + * The list to check. + * @param item + * The item to check. + * @param node (optional) + * If specified, use item->node.{prev,next} rather than item->{prev,next}. + * @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, prev, next) \ + (item->prev || item->next || list->head == item || list->tail == item) + /** * Insert into a doubly-linked list after the given cursor. * @@ -444,6 +488,7 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void 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)), \ item->prev = cursor, \ item->next = cursor ? cursor->next : list->head, \ *(item->prev ? &item->prev->next : &list->head) = item, \ @@ -470,27 +515,6 @@ static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void *(item->next ? &item->next->prev : &list->tail) = item->prev, \ item->prev = item->next = NULL) -/** - * Check if an item is attached to a doubly-linked list. - * - * @param list - * The list to check. - * @param item - * The item to check. - * @param node (optional) - * If specified, use item->node.{prev,next} rather than item->{prev,next}. - * @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, prev, next) \ - (item->prev || item->next || list->head == item || list->tail == item) - /** * Loop over the items in a doubly-linked list. * diff --git a/src/trie.c b/src/trie.c index 55544e6..23b70ff 100644 --- a/src/trie.c +++ b/src/trie.c @@ -325,6 +325,7 @@ static struct trie_leaf *trie_leaf_alloc(struct trie *trie, const void *key, siz return NULL; } + LIST_ITEM_INIT(leaf); LIST_APPEND(trie, leaf); leaf->value = NULL; -- cgit v1.2.3 From 773f4a446f03da62d88e6d17be49fdc0a3e38465 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 15:13:02 -0400 Subject: bftw: Fix to_close list corruption with !BFS_USE_UNWRAPDIR It's possible for pincount to drop to zero, then get incremented and drop back to zero again. If this happens, we shouldn't add it to the to_close list twice. This should fix the intermittent hang on the macOS CI. Fixes: 815798e1eea7fc8dacd5acab40202ec4d251d517 --- src/bftw.c | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index c0bff75..1995e12 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -546,6 +546,17 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg return 0; } +/** Unpin a directory, and possibly queue it for unwrapping. */ +static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, bool force) { + bftw_cache_unpin(&state->cache, file); + + if (file->dir && (force || file->pincount == 0)) { + if (!SLIST_ATTACHED(&state->to_close, file)) { + SLIST_APPEND(&state->to_close, file); + } + } +} + /** Pop a response from the I/O queue. */ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct ioq *ioq = state->ioq; @@ -582,10 +593,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { ++cache->capacity; parent = file->parent; if (parent) { - bftw_cache_unpin(cache, parent); - if (parent->pincount == 0 && parent->dir) { - SLIST_APPEND(&state->to_close, parent); - } + bftw_unpin_dir(state, parent, false); } dir = ent->opendir.dir; @@ -1285,8 +1293,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { struct bftw_file *file = state->file; if (file && file->dir) { - bftw_cache_unpin(&state->cache, file); - SLIST_APPEND(&state->to_close, file); + bftw_unpin_dir(state, file, true); } state->dir = NULL; state->de = NULL; -- cgit v1.2.3 From d64a32e03b4d9a1336ebfa66d7d08d96e9847a6a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 15:50:59 -0400 Subject: bftw: Make sure we don't close a directory while we unwrap it bftw_cache_reserve() can lead to bftw_cache_pop(), which could close the directory we're trying to unwrap! If that happened, we would try dup_cloexec(-1), which would fail with EBADF, so there was no observable bug. But it's better to avoid the whole situation. --- src/bftw.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 1995e12..06a0085 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -811,8 +811,12 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { return bftw_close(state, file); } - if (bftw_cache_reserve(state) != 0) { - return -1; + // Make room for dup() + bftw_cache_pin(cache, file); + int ret = bftw_cache_reserve(state); + bftw_cache_unpin(cache, file); + if (ret != 0) { + return ret; } int fd = dup_cloexec(file->fd); -- cgit v1.2.3 From 11feab9496ef610fa01f088155549b4d2d5e5c08 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 18:21:55 -0400 Subject: bench: Check for bfs -j support before using it --- bench/bench.sh | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/bench/bench.sh b/bench/bench.sh index be5a15b..ab56b79 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -467,7 +467,11 @@ bench-jobs-corpus() { if ((${#BFS[@]} + ${#FD[@]} == 1)); then cmds=() for bfs in "${BFS[@]}"; do - cmds+=("$bfs -j"{1,2,3,4,6,8,12,16}" $2 -false") + if "$bfs" -j1 -quit &>/dev/null; then + cmds+=("$bfs -j"{1,2,3,4,6,8,12,16}" $2 -false") + else + cmds+=("$bfs $2 -false") + fi done for fd in "${FD[@]}"; do @@ -481,14 +485,20 @@ bench-jobs-corpus() { cmds=() for bfs in "${BFS[@]}"; do - cmds+=("$bfs -j$j $2 -false") + if "$bfs" -j1 -quit &>/dev/null; then + cmds+=("$bfs -j$j $2 -false") + elif ((j == 1)); then + cmds+=("$bfs $2 -false") + fi done for fd in "${FD[@]}"; do cmds+=("$fd -j$j -u '^$' $2") done - do-hyperfine "${cmds[@]}" + if ((${#cmds[@]})); then + do-hyperfine "${cmds[@]}" + fi done fi } -- cgit v1.2.3 From b475307de3b168cf9a63fa4e73a84a46900c8df6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 18:27:31 -0400 Subject: bench: Look for ./bfs if ./bin/bfs doesn't exist --- bench/bench.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bench/bench.sh b/bench/bench.sh index ab56b79..0dfd7c4 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -228,7 +228,11 @@ setup() { cd "$worktree" as-user git checkout -qd "$commit" -- as-user make -s -j"$nproc" release - as-user cp ./bin/bfs "$bin/bfs-$commit" + if [ -e ./bin/bfs ]; then + as-user cp ./bin/bfs "$bin/bfs-$commit" + else + as-user cp ./bfs "$bin/bfs-$commit" + fi as-user make -s clean ) done -- cgit v1.2.3 From 8b70270715eb9d58417cc551af413187b7260055 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 18:28:34 -0400 Subject: docs: Start writing the 3.1 changelog --- docs/CHANGELOG.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3dca97c..8e10caa 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,39 @@ 3.* === +3.1 +--- + +**Coming soon** + +### New features + +- `bfs` now uses `io_uring` on Linux ([#106]) + +- `bfs` now comes with an official benchmark suite ([#126]) + +### Changes + +- Iterative deepening modes (`-S {ids,eds}`) were optimized by delaying teardown until the very end ([`5f16169`]) + +- Parallel depth-first search (`-S dfs`) was optimized to avoid enqueueing every file separately ([`2572273`]) + +### Bug Fixes + +- Iterative deepening modes (`-S {ids,eds}`) were performing iterative *breadth*-first searches since `bfs` 3.0, negating any advantages they may have had over normal breadth-first search. + They now do iterative *depth*-first searches as expected. + ([`a029d95`]) + +- Fixed a linked-list corruption that could lead to an infinite loop on macOS and other non-Linux, non-FreeBSD platforms ([`773f4a4`]) + +[#106]: https://github.com/tavianator/bfs/pull/106 +[#126]: https://github.com/tavianator/bfs/pull/126 +[`5f16169`]: https://github.com/tavianator/bfs/commit/5f1616912ba3a7a23ce6bce02df3791b73da38ab +[`2572273`]: https://github.com/tavianator/bfs/commit/257227326fe60fe70e80433fd34d1ebcb2f9f623 +[`a029d95`]: https://github.com/tavianator/bfs/commit/a029d95b5736a74879f32089514a5a6b63d6efbc +[`773f4a4`]: https://github.com/tavianator/bfs/commit/773f4a446f03da62d88e6d17be49fdc0a3e38465 + + 3.0.2 ----- -- cgit v1.2.3 From 735a2979a37251dc447274aa888ad77ded126457 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 22:12:35 -0400 Subject: build: Disable liburing for now liburing will be enabled for the next feature release. --- GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index c1296f9..69cfe3d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -126,7 +126,7 @@ ifndef NOLIBS USE_ACL := y USE_ATTR := y USE_LIBCAP := y -USE_LIBURING := y +# USE_LIBURING := y endif ifdef USE_ACL -- cgit v1.2.3 From 2acd897857fe301dfc6f5a8648e0124cd8515b4e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 22:13:49 -0400 Subject: Release 3.0.3 --- GNUmakefile | 2 +- docs/CHANGELOG.md | 14 +++----------- src/config.h | 2 +- 3 files changed, 5 insertions(+), 13 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 69cfe3d..27d6674 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 3.0.2 +VERSION := 3.0.3 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 8e10caa..253dd46 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,16 +1,10 @@ 3.* === -3.1 ---- - -**Coming soon** - -### New features - -- `bfs` now uses `io_uring` on Linux ([#106]) +3.0.3 +----- -- `bfs` now comes with an official benchmark suite ([#126]) +**October 12, 2023** ### Changes @@ -26,8 +20,6 @@ - Fixed a linked-list corruption that could lead to an infinite loop on macOS and other non-Linux, non-FreeBSD platforms ([`773f4a4`]) -[#106]: https://github.com/tavianator/bfs/pull/106 -[#126]: https://github.com/tavianator/bfs/pull/126 [`5f16169`]: https://github.com/tavianator/bfs/commit/5f1616912ba3a7a23ce6bce02df3791b73da38ab [`2572273`]: https://github.com/tavianator/bfs/commit/257227326fe60fe70e80433fd34d1ebcb2f9f623 [`a029d95`]: https://github.com/tavianator/bfs/commit/a029d95b5736a74879f32089514a5a6b63d6efbc diff --git a/src/config.h b/src/config.h index 862a839..980980a 100644 --- a/src/config.h +++ b/src/config.h @@ -22,7 +22,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "3.0.2" +# define BFS_VERSION "3.0.3" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From da21718ab320f144e22b6b1147fc8b5d6401870a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 22:14:34 -0400 Subject: build: Re-enable liburing This reverts commit 735a2979a37251dc447274aa888ad77ded126457. --- GNUmakefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/GNUmakefile b/GNUmakefile index 27d6674..a5a2a00 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -126,7 +126,7 @@ ifndef NOLIBS USE_ACL := y USE_ATTR := y USE_LIBCAP := y -# USE_LIBURING := y +USE_LIBURING := y endif ifdef USE_ACL -- cgit v1.2.3 From d40eb87cc00f50a5debb8899eacb7fcf1065badf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 23:45:40 -0400 Subject: bfstd: Actually use the copied locale This fixes a segfault in xstrerror() when using musl. --- src/bfstd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bfstd.c b/src/bfstd.c index e9214d4..a4ae439 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -301,7 +301,7 @@ const char *xstrerror(int errnum) { } if (copy != (locale_t)0) { - ret = strerror_l(errnum, loc); + ret = strerror_l(errnum, copy); } if (loc == LC_GLOBAL_LOCALE) { -- cgit v1.2.3 From 5dbab7e7815800ea6a110519154f05485457b194 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 23:47:04 -0400 Subject: tests: Don't crash if unbuffer isn't installed --- tests/tests.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/tests.sh b/tests/tests.sh index 662efd9..f233f74 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -495,7 +495,7 @@ elif command -v expect_unbuffer &>/dev/null; then fi function bfs_pty() { - test -n "$UNBUFFER" || skip + test -n "${UNBUFFER:-}" || skip bfs_verbose "$@" "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" -- cgit v1.2.3 From c25fa4fb5e58e20ce97c3747c05d26307a70baaa Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 12 Oct 2023 23:50:16 -0400 Subject: Release 3.0.4 --- GNUmakefile | 2 +- docs/CHANGELOG.md | 12 ++++++++++++ src/config.h | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 27d6674..011e397 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 3.0.3 +VERSION := 3.0.4 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 253dd46..3b07f01 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,18 @@ 3.* === +3.0.4 +----- + +**October 12, 2023** + +### Bug Fixes + +- Fixed a segfault when reporting errors under musl ([`d40eb87`]) + +[`d40eb87`]: https://github.com/tavianator/bfs/commit/d40eb87cc00f50a5debb8899eacb7fcf1065badf + + 3.0.3 ----- diff --git a/src/config.h b/src/config.h index 980980a..38ca69b 100644 --- a/src/config.h +++ b/src/config.h @@ -22,7 +22,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "3.0.3" +# define BFS_VERSION "3.0.4" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From 3780856f7503096e01a64f19eb6051b765db252e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 13 Oct 2023 13:52:01 -0400 Subject: tests: Always create the scratch directory Otherwise, some tests that try to check for flag support like invoke_bfs scratch -quit -xattr || skip can be accidentally skipped if ./scratch doesn't exist yet: $ ./tests/tests.sh bsd/xattr tests skipped: 1 After this patch, we get the expected $ ./tests/tests.sh bsd/xattr tests passed: 1 --- tests/tests.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/tests.sh b/tests/tests.sh index f233f74..61e4aba 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -437,6 +437,8 @@ function make_rainbow() { } make_rainbow "$TMP/rainbow" +mkdir "$TMP/scratch" + # Close stdin so bfs doesn't think we're interactive exec Date: Fri, 13 Oct 2023 17:09:11 -0400 Subject: docs: Add a list of related utilities --- docs/RELATED.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 docs/RELATED.md diff --git a/docs/RELATED.md b/docs/RELATED.md new file mode 100644 index 0000000..cf52b70 --- /dev/null +++ b/docs/RELATED.md @@ -0,0 +1,42 @@ +# Related utilities + +There are many tools that can be used to find files. +This is a catalogue of some of the most important/interesting ones. + +## `find`-compatible + +### System `find` implementations + +These `find` implementations are commonly installed as the system `find` utility in UNIX-like operating systems: + +- [GNU findutils](https://www.gnu.org/software/findutils/) ([manual](https://www.gnu.org/software/findutils/manual/html_node/find_html/index.html), [source](https://git.savannah.gnu.org/cgit/findutils.git)) +- BSD `find` + - FreeBSD `find` ([manual](https://www.freebsd.org/cgi/man.cgi?find(1)), [source](https://cgit.freebsd.org/src/tree/usr.bin/find)) + - OpenBSD `find` ([manual](https://man.openbsd.org/find.1), [source](https://cvsweb.openbsd.org/src/usr.bin/find/)) + - NetBSD `find` ([manual](https://man.netbsd.org/find.1), [source](http://cvsweb.netbsd.org/bsdweb.cgi/src/usr.bin/find/)) +- macOS `find` ([manual](https://ss64.com/osx/find.html), [source](https://github.com/apple-oss-distributions/shell_cmds/tree/main/find)) +- Solaris `find` + - [Illumos](https://illumos.org/) `find` ([manual](https://illumos.org/man/1/find), [source](https://github.com/illumos/illumos-gate/blob/master/usr/src/cmd/find/find.c)) + +### Alternative `find` implementations + +These are not usually installed as the system `find`, but are designed to be `find`-compatible + +- [`bfs`](https://tavianator.com/projects/bfs.html) ([manual](https://man.archlinux.org/man/bfs.1), [source](https://github.com/tavianator/bfs)) +- [schilytools](https://codeberg.org/schilytools/schilytools) `sfind` ([source](https://codeberg.org/schilytools/schilytools/src/branch/master/sfind)) +- [BusyBox](https://busybox.net/) `find` ([manual](https://busybox.net/downloads/BusyBox.html#find), [source](https://git.busybox.net/busybox/tree/findutils/find.c)) +- [ToyBox](http://landley.net/toybox/) `find` ([manual](http://landley.net/toybox/help.html#find), [source](https://github.com/landley/toybox/blob/master/toys/posix/find.c)) +- uutils `find` ([source](https://github.com/uutils/findutils)) + +## `find` alternatives + +These utilities are not `find`-compatible, but serve a similar purpose: + +- [`fd`](https://github.com/sharkdp/fd): A simple, fast and user-friendly alternative to 'find' +- `locate` + - [GNU `locate`](https://www.gnu.org/software/findutils/locate) + - [`mlocate`](https://pagure.io/mlocate) ([manual](), [source](https://pagure.io/mlocate/tree/master)) + - [`plocate`](https://plocate.sesse.net/) ([manual](https://plocate.sesse.net/plocate.1.html), [source](https://git.sesse.net/?p=plocate)) +- [`walk`](https://github.com/google/walk): Plan 9 style utilities to replace find(1) +- [fselect](https://github.com/jhspetersson/fselect): Find files with SQL-like queries +- [rawhide](https://github.com/raforg/rawhide): find files using pretty C expressions -- cgit v1.2.3 From 287c2c72afeb8fbf017cd62cc100b2d05dca0fae Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 14 Oct 2023 08:48:15 -0400 Subject: dir: Fix Android build Android doesn't provide a getdents() wrapper, so use the syscall. --- src/dir.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dir.c b/src/dir.c index 0304674..a7423e9 100644 --- a/src/dir.c +++ b/src/dir.c @@ -24,7 +24,7 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { sanitize_uninit(buf, size); -#if __linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30) +#if (__linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30)) || __ANDROID__ ssize_t ret = syscall(SYS_getdents64, fd, buf, size); #elif __linux__ ssize_t ret = getdents64(fd, buf, size); -- cgit v1.2.3 From ddb476bcd224f8c262d24981af48da2fc649f43d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 14 Oct 2023 08:55:57 -0400 Subject: tests: Move closed_std* tests out of the POSIX group POSIX actually says > If the utility would be executed with file descriptor 0, 1, or 2 > closed, implementations may execute the utility with the file > descriptor open to an unspecified file. So we're not guaranteed to be able to detect the situation in the first place. Add a best-effort check for these platforms and skip the test. Link: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_09_01_01 --- tests/bfs/closed_stderr.sh | 4 ++++ tests/bfs/closed_stdin.out | 19 +++++++++++++++++++ tests/bfs/closed_stdin.sh | 1 + tests/bfs/closed_stdout.sh | 4 ++++ tests/posix/closed_stderr.sh | 1 - tests/posix/closed_stdin.out | 19 ------------------- tests/posix/closed_stdin.sh | 1 - tests/posix/closed_stdout.sh | 1 - 8 files changed, 28 insertions(+), 22 deletions(-) create mode 100644 tests/bfs/closed_stderr.sh create mode 100644 tests/bfs/closed_stdin.out create mode 100644 tests/bfs/closed_stdin.sh create mode 100644 tests/bfs/closed_stdout.sh delete mode 100644 tests/posix/closed_stderr.sh delete mode 100644 tests/posix/closed_stdin.out delete mode 100644 tests/posix/closed_stdin.sh delete mode 100644 tests/posix/closed_stdout.sh diff --git a/tests/bfs/closed_stderr.sh b/tests/bfs/closed_stderr.sh new file mode 100644 index 0000000..26abd85 --- /dev/null +++ b/tests/bfs/closed_stderr.sh @@ -0,0 +1,4 @@ +# Check if the platform automatically re-opens stderr before we can +(bash -c 'echo >&2' 2>&-) && skip + +! invoke_bfs basic >&- 2>&- diff --git a/tests/bfs/closed_stdin.out b/tests/bfs/closed_stdin.out new file mode 100644 index 0000000..a7ccfe4 --- /dev/null +++ b/tests/bfs/closed_stdin.out @@ -0,0 +1,19 @@ +basic +basic/a +basic/b +basic/c +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/bfs/closed_stdin.sh b/tests/bfs/closed_stdin.sh new file mode 100644 index 0000000..6932be8 --- /dev/null +++ b/tests/bfs/closed_stdin.sh @@ -0,0 +1 @@ +bfs_diff basic <&- diff --git a/tests/bfs/closed_stdout.sh b/tests/bfs/closed_stdout.sh new file mode 100644 index 0000000..5b6f7c3 --- /dev/null +++ b/tests/bfs/closed_stdout.sh @@ -0,0 +1,4 @@ +# Check if the platform automatically re-opens stdout before we can +(bash -c echo >&-) && skip + +! invoke_bfs basic >&- diff --git a/tests/posix/closed_stderr.sh b/tests/posix/closed_stderr.sh deleted file mode 100644 index 570d5bb..0000000 --- a/tests/posix/closed_stderr.sh +++ /dev/null @@ -1 +0,0 @@ -! invoke_bfs basic >&- 2>&- diff --git a/tests/posix/closed_stdin.out b/tests/posix/closed_stdin.out deleted file mode 100644 index a7ccfe4..0000000 --- a/tests/posix/closed_stdin.out +++ /dev/null @@ -1,19 +0,0 @@ -basic -basic/a -basic/b -basic/c -basic/c/d -basic/e -basic/e/f -basic/g -basic/g/h -basic/i -basic/j -basic/j/foo -basic/k -basic/k/foo -basic/k/foo/bar -basic/l -basic/l/foo -basic/l/foo/bar -basic/l/foo/bar/baz diff --git a/tests/posix/closed_stdin.sh b/tests/posix/closed_stdin.sh deleted file mode 100644 index 6932be8..0000000 --- a/tests/posix/closed_stdin.sh +++ /dev/null @@ -1 +0,0 @@ -bfs_diff basic <&- diff --git a/tests/posix/closed_stdout.sh b/tests/posix/closed_stdout.sh deleted file mode 100644 index 25c060d..0000000 --- a/tests/posix/closed_stdout.sh +++ /dev/null @@ -1 +0,0 @@ -! invoke_bfs basic >&- -- cgit v1.2.3 From 214a1f9215d33d4b9f34a3d258da1e1f4e3eb01f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 16 Oct 2023 16:44:46 -0400 Subject: dir: Add a flags parameter to bfs_opendir() --- src/bftw.c | 4 ++-- src/dir.c | 31 ++++++++++++++++++++----------- src/dir.h | 12 +++++++++++- src/eval.c | 6 +++--- src/ioq.c | 7 ++++--- src/ioq.h | 6 +++++- 6 files changed, 45 insertions(+), 21 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 06a0085..e2f1a66 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -856,7 +856,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } - if (ioq_opendir(state->ioq, dir, dfd, file->name, file) != 0) { + if (ioq_opendir(state->ioq, dir, dfd, file->name, 0, file) != 0) { goto free; } @@ -1018,7 +1018,7 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f return NULL; } - if (bfs_opendir(dir, fd, NULL) != 0) { + if (bfs_opendir(dir, fd, NULL, 0) != 0) { bftw_freedir(cache, dir); return NULL; } diff --git a/src/dir.c b/src/dir.c index a7423e9..dee02e5 100644 --- a/src/dir.c +++ b/src/dir.c @@ -96,18 +96,26 @@ enum bfs_type bfs_mode_to_type(mode_t mode) { } } +/** + * Private directory flags. + */ +enum { + /** We've reached the end of the directory. */ + BFS_DIR_EOF = BFS_DIR_PRIVATE << 0, +}; + struct bfs_dir { + unsigned int flags; + #if BFS_USE_GETDENTS - alignas(sys_dirent) int fd; + int fd; unsigned short pos; unsigned short size; - // sys_dirent buf[]; + alignas(sys_dirent) char buf[]; #else DIR *dir; struct dirent *de; #endif - - bool eof; }; #if BFS_USE_GETDENTS @@ -125,7 +133,7 @@ void bfs_dir_arena(struct arena *arena) { arena_init(arena, alignof(struct bfs_dir), DIR_SIZE); } -int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { +int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_dir_flags flags) { int fd; if (at_path) { fd = openat(at_fd, at_path, O_RDONLY | O_CLOEXEC | O_DIRECTORY); @@ -139,6 +147,8 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { return -1; } + dir->flags = flags; + #if BFS_USE_GETDENTS dir->fd = fd; dir->pos = 0; @@ -154,7 +164,6 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path) { dir->de = NULL; #endif - dir->eof = false; return 0; } @@ -170,14 +179,14 @@ int bfs_polldir(struct bfs_dir *dir) { #if BFS_USE_GETDENTS if (dir->pos < dir->size) { return 1; - } else if (dir->eof) { + } else if (dir->flags & BFS_DIR_EOF) { return 0; } char *buf = (char *)(dir + 1); ssize_t size = bfs_getdents(dir->fd, buf, BUF_SIZE); if (size == 0) { - dir->eof = true; + dir->flags |= BFS_DIR_EOF; return 0; } else if (size < 0) { return -1; @@ -194,7 +203,7 @@ int bfs_polldir(struct bfs_dir *dir) { if (size > 0) { dir->size += size; } else if (size == 0) { - dir->eof = true; + dir->flags |= BFS_DIR_EOF; } } @@ -202,7 +211,7 @@ int bfs_polldir(struct bfs_dir *dir) { #else // !BFS_USE_GETDENTS if (dir->de) { return 1; - } else if (dir->eof) { + } else if (dir->flags & BFS_DIR_EOF) { return 0; } @@ -211,7 +220,7 @@ int bfs_polldir(struct bfs_dir *dir) { if (dir->de) { return 1; } else if (errno == 0) { - dir->eof = true; + dir->flags |= BFS_DIR_EOF; return 0; } else { return -1; diff --git a/src/dir.h b/src/dir.h index 1137ff5..7e0cbba 100644 --- a/src/dir.h +++ b/src/dir.h @@ -86,6 +86,14 @@ struct bfs_dir *bfs_allocdir(void); */ void bfs_dir_arena(struct arena *arena); +/** + * bfs_opendir() flags. + */ +enum bfs_dir_flags { + /** @internal Start of private flags. */ + BFS_DIR_PRIVATE = 1 << 0, +}; + /** * Open a directory. * @@ -96,10 +104,12 @@ void bfs_dir_arena(struct arena *arena); * @param at_path * The path of the directory to open, relative to at_fd. Pass NULL to * open at_fd itself. + * @param flags + * Flags that control which directory entries are listed. * @return * 0 on success, or -1 on failure. */ -int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path); +int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_dir_flags flags); /** * Get the file descriptor for a directory. diff --git a/src/eval.c b/src/eval.c index e0dd97b..adf7a0b 100644 --- a/src/eval.c +++ b/src/eval.c @@ -427,7 +427,7 @@ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { return ret; } - if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path) != 0) { + if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path, 0) != 0) { eval_report_error(state); return ret; } @@ -1517,8 +1517,8 @@ static int infer_fdlimit(const struct bfs_ctx *ctx, int limit) { goto done; } - if (bfs_opendir(dir, AT_FDCWD, "/proc/self/fd") != 0 - && bfs_opendir(dir, AT_FDCWD, "/dev/fd") != 0) { + if (bfs_opendir(dir, AT_FDCWD, "/proc/self/fd", 0) != 0 + && bfs_opendir(dir, AT_FDCWD, "/dev/fd", 0) != 0) { goto done; } diff --git a/src/ioq.c b/src/ioq.c index 04b9c0d..8c1bdbe 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -348,7 +348,7 @@ static void ioq_handle(struct ioq *ioq, struct ioq_ent *ent) { break; case IOQ_OPENDIR: - ret = bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path); + ret = bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path, ent->opendir.flags); if (ret == 0) { bfs_polldir(ent->opendir.dir); } @@ -505,7 +505,7 @@ static void ioq_ring_reap(struct ioq_ring_state *state) { continue; } - ent->ret = bfs_opendir(ent->opendir.dir, fd, NULL); + ent->ret = bfs_opendir(ent->opendir.dir, fd, NULL, ent->opendir.flags); if (ent->ret == 0) { // TODO: io_uring_prep_getdents() bfs_polldir(ent->opendir.dir); @@ -659,7 +659,7 @@ int ioq_close(struct ioq *ioq, int fd, void *ptr) { return 0; } -int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr) { +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, enum bfs_dir_flags flags, void *ptr) { struct ioq_ent *ent = ioq_request(ioq, IOQ_OPENDIR, ptr); if (!ent) { return -1; @@ -669,6 +669,7 @@ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, args->dir = dir; args->dfd = dfd; args->path = path; + args->flags = flags; ioqq_push(ioq->pending, ent); return 0; diff --git a/src/ioq.h b/src/ioq.h index 99c18c2..eab89ec 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -8,6 +8,7 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H +#include "dir.h" #include /** @@ -53,6 +54,7 @@ struct ioq_ent { struct bfs_dir *dir; int dfd; const char *path; + enum bfs_dir_flags flags; } opendir; /** ioq_closedir() args. */ struct ioq_closedir { @@ -103,12 +105,14 @@ int ioq_close(struct ioq *ioq, int fd, void *ptr); * The base file descriptor. * @param path * The path to open, relative to dfd. + * @param flags + * Flags that control which directory entries are listed. * @param ptr * An arbitrary pointer to associate with the request. * @return * 0 on success, or -1 on failure. */ -int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, void *ptr); +int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, enum bfs_dir_flags flags, void *ptr); /** * Asynchronous bfs_closedir(). -- cgit v1.2.3 From 3ac3bee7b0d9c9be693415206efa664bf4a7d4a7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 17 Oct 2023 11:35:41 -0400 Subject: dir: Fix FreeBSD union mounts --- src/dir.c | 54 ++++++++++++++++++++++++++++++++++++++++------- tests/posix/overlayfs.out | 5 +++++ tests/posix/overlayfs.sh | 7 ++++++ tests/posix/unionfs.out | 10 +++++++++ tests/posix/unionfs.sh | 6 ++++++ 5 files changed, 74 insertions(+), 8 deletions(-) create mode 100644 tests/posix/overlayfs.out create mode 100644 tests/posix/overlayfs.sh create mode 100644 tests/posix/unionfs.out create mode 100644 tests/posix/unionfs.sh diff --git a/src/dir.c b/src/dir.c index dee02e5..371696f 100644 --- a/src/dir.c +++ b/src/dir.c @@ -7,6 +7,7 @@ #include "config.h" #include "diag.h" #include "sanity.h" +#include "trie.h" #include #include #include @@ -101,7 +102,9 @@ enum bfs_type bfs_mode_to_type(mode_t mode) { */ enum { /** We've reached the end of the directory. */ - BFS_DIR_EOF = BFS_DIR_PRIVATE << 0, + BFS_DIR_EOF = BFS_DIR_PRIVATE << 0, + /** This directory is a union mount we need to dedup manually. */ + BFS_DIR_UNION = BFS_DIR_PRIVATE << 1, }; struct bfs_dir { @@ -111,6 +114,9 @@ struct bfs_dir { int fd; unsigned short pos; unsigned short size; +# if __FreeBSD__ + struct trie trie; +# endif alignas(sys_dirent) char buf[]; #else DIR *dir; @@ -153,7 +159,14 @@ int bfs_opendir(struct bfs_dir *dir, int at_fd, const char *at_path, enum bfs_di dir->fd = fd; dir->pos = 0; dir->size = 0; -#else + +# if __FreeBSD__ && defined(F_ISUNIONSTACK) + if (fcntl(fd, F_ISUNIONSTACK) > 0) { + dir->flags |= BFS_DIR_UNION; + trie_init(&dir->trie); + } +# endif +#else // !BFS_USE_GETDENTS dir->dir = fdopendir(fd); if (!dir->dir) { if (at_path) { @@ -245,11 +258,23 @@ static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { } /** Skip ".", "..", and deleted/empty dirents. */ -static bool skip_dirent(const sys_dirent *de) { -#if __FreeBSD__ +static int bfs_skipdent(struct bfs_dir *dir, const sys_dirent *de) { +#if BFS_USE_GETDENTS && __FreeBSD__ + // Union mounts on FreeBSD have to be de-duplicated in userspace + if (dir->flags & BFS_DIR_UNION) { + struct trie_leaf *leaf = trie_insert_str(&dir->trie, de->d_name); + if (!leaf) { + return -1; + } else if (leaf->value) { + return 1; + } else { + leaf->value = leaf; + } + } + // NFS mounts on FreeBSD can return empty dirents with inode number 0 if (de->d_ino == 0) { - return true; + return 1; } #endif @@ -274,7 +299,10 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) { return ret; } - if (skip_dirent(sysde)) { + int skip = bfs_skipdent(dir, sysde); + if (skip < 0) { + return skip; + } else if (skip) { continue; } @@ -287,6 +315,16 @@ int bfs_readdir(struct bfs_dir *dir, struct bfs_dirent *de) { } } +static void bfs_destroydir(struct bfs_dir *dir) { +#if BFS_USE_GETDENTS && __FreeBSD__ + if (dir->flags & BFS_DIR_UNION) { + trie_destroy(&dir->trie); + } +#endif + + sanitize_uninit(dir, DIR_SIZE); +} + int bfs_closedir(struct bfs_dir *dir) { #if BFS_USE_GETDENTS int ret = xclose(dir->fd); @@ -297,7 +335,7 @@ int bfs_closedir(struct bfs_dir *dir) { } #endif - sanitize_uninit(dir, DIR_SIZE); + bfs_destroydir(dir); return ret; } @@ -309,7 +347,7 @@ int bfs_unwrapdir(struct bfs_dir *dir) { int ret = fdclosedir(dir->dir); #endif - sanitize_uninit(dir, DIR_SIZE); + bfs_destroydir(dir); return ret; } #endif diff --git a/tests/posix/overlayfs.out b/tests/posix/overlayfs.out new file mode 100644 index 0000000..754d01d --- /dev/null +++ b/tests/posix/overlayfs.out @@ -0,0 +1,5 @@ +scratch/merged +scratch/merged/bar +scratch/merged/baz +scratch/merged/baz/qux +scratch/merged/foo diff --git a/tests/posix/overlayfs.sh b/tests/posix/overlayfs.sh new file mode 100644 index 0000000..a56b5b3 --- /dev/null +++ b/tests/posix/overlayfs.sh @@ -0,0 +1,7 @@ +test "$UNAME" = "Linux" || skip +clean_scratch +"$XTOUCH" -p scratch/{lower/{foo,bar,baz},upper/{bar,baz/qux}} +mkdir -p scratch/{work,merged} +bfs_sudo mount -t overlay overlay -olowerdir=scratch/lower,upperdir=scratch/upper,workdir=scratch/work scratch/merged || skip +trap "bfs_sudo umount scratch/merged; bfs_sudo rm -rf scratch/work" EXIT +bfs_diff scratch/merged diff --git a/tests/posix/unionfs.out b/tests/posix/unionfs.out new file mode 100644 index 0000000..6d0fa3c --- /dev/null +++ b/tests/posix/unionfs.out @@ -0,0 +1,10 @@ +scratch +scratch/lower +scratch/lower/bar +scratch/lower/baz +scratch/lower/foo +scratch/upper +scratch/upper/bar +scratch/upper/baz +scratch/upper/baz/qux +scratch/upper/foo diff --git a/tests/posix/unionfs.sh b/tests/posix/unionfs.sh new file mode 100644 index 0000000..88a549f --- /dev/null +++ b/tests/posix/unionfs.sh @@ -0,0 +1,6 @@ +[[ "$UNAME" == *BSD* ]] || skip +clean_scratch +"$XTOUCH" -p scratch/{lower/{foo,bar,baz},upper/{bar,baz/qux}} +bfs_sudo mount -t unionfs -o below scratch/{lower,upper} || skip +trap "bfs_sudo umount scratch/upper" EXIT +bfs_diff scratch -- cgit v1.2.3 From 332f38aff0e6b7bc1a3a648eb66437d2d043ad21 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 17 Oct 2023 11:43:43 -0400 Subject: dir: New flag to control whiteout visibility --- src/dir.c | 12 ++++++++++-- src/dir.h | 4 +++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/src/dir.c b/src/dir.c index 371696f..98518f2 100644 --- a/src/dir.c +++ b/src/dir.c @@ -259,7 +259,8 @@ static int bfs_getdent(struct bfs_dir *dir, const sys_dirent **de) { /** Skip ".", "..", and deleted/empty dirents. */ static int bfs_skipdent(struct bfs_dir *dir, const sys_dirent *de) { -#if BFS_USE_GETDENTS && __FreeBSD__ +#if BFS_USE_GETDENTS +# if __FreeBSD__ // Union mounts on FreeBSD have to be de-duplicated in userspace if (dir->flags & BFS_DIR_UNION) { struct trie_leaf *leaf = trie_insert_str(&dir->trie, de->d_name); @@ -276,7 +277,14 @@ static int bfs_skipdent(struct bfs_dir *dir, const sys_dirent *de) { if (de->d_ino == 0) { return 1; } -#endif +# endif + +# ifdef DT_WHT + if (de->d_type == DT_WHT && !(dir->flags & BFS_DIR_WHITEOUTS)) { + return 1; + } +# endif +#endif // BFS_USE_GETDENTS const char *name = de->d_name; return name[0] == '.' && (name[1] == '\0' || (name[1] == '.' && name[2] == '\0')); diff --git a/src/dir.h b/src/dir.h index 7e0cbba..b11d454 100644 --- a/src/dir.h +++ b/src/dir.h @@ -90,8 +90,10 @@ void bfs_dir_arena(struct arena *arena); * bfs_opendir() flags. */ enum bfs_dir_flags { + /** Include whiteouts in the results. */ + BFS_DIR_WHITEOUTS = 1 << 0, /** @internal Start of private flags. */ - BFS_DIR_PRIVATE = 1 << 0, + BFS_DIR_PRIVATE = 1 << 1, }; /** -- cgit v1.2.3 From 68949cf1b9cb5336ea06ad7f87db8e28b620f2ac Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 17 Oct 2023 11:55:19 -0400 Subject: bftw: New flag to control whiteout visibility --- src/bftw.c | 18 ++++++++++++++++-- src/bftw.h | 2 ++ src/eval.c | 1 + 3 files changed, 19 insertions(+), 2 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index e2f1a66..dff949f 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -41,6 +41,13 @@ static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_st errno = cache->error; } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { cache->buf = &cache->storage; +#ifdef S_IFWHT + } else if (errno == ENOENT && ftwbuf->type == BFS_WHT) { + // This matches the behavior of FTS_WHITEOUT on BSD + memset(&cache->storage, 0, sizeof(cache->storage)); + cache->storage.mode = S_IFWHT; + cache->buf = &cache->storage; +#endif } else { cache->error = errno; } @@ -410,6 +417,8 @@ struct bftw_state { enum bftw_strategy strategy; /** The mount table. */ const struct bfs_mtab *mtab; + /** bfs_opendir() flags. */ + enum bfs_dir_flags dir_flags; /** The appropriate errno value, if any. */ int error; @@ -492,6 +501,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->flags = args->flags; state->strategy = args->strategy; state->mtab = args->mtab; + state->dir_flags = 0; state->error = 0; if (args->nopenfd < 2) { @@ -527,6 +537,10 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->flags |= BFTW_BUFFER; } + if (state->flags & BFTW_WHITEOUTS) { + state->dir_flags |= BFS_DIR_WHITEOUTS; + } + SLIST_INIT(&state->dir_batch); SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); @@ -856,7 +870,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } - if (ioq_opendir(state->ioq, dir, dfd, file->name, 0, file) != 0) { + if (ioq_opendir(state->ioq, dir, dfd, file->name, state->dir_flags, file) != 0) { goto free; } @@ -1018,7 +1032,7 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f return NULL; } - if (bfs_opendir(dir, fd, NULL, 0) != 0) { + if (bfs_opendir(dir, fd, NULL, state->dir_flags) != 0) { bftw_freedir(cache, dir); return NULL; } diff --git a/src/bftw.h b/src/bftw.h index 940532c..2b36b8b 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -156,6 +156,8 @@ enum bftw_flags { BFTW_SORT = 1 << 8, /** Read each directory into memory before processing its children. */ BFTW_BUFFER = 1 << 9, + /** Include whiteouts in the search results. */ + BFTW_WHITEOUTS = 1 << 10, }; /** diff --git a/src/eval.c b/src/eval.c index adf7a0b..3d396fa 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1572,6 +1572,7 @@ static void dump_bftw_flags(enum bftw_flags flags) { DEBUG_FLAG(flags, BFTW_PRUNE_MOUNTS); DEBUG_FLAG(flags, BFTW_SORT); DEBUG_FLAG(flags, BFTW_BUFFER); + DEBUG_FLAG(flags, BFTW_WHITEOUTS); bfs_assert(flags == 0, "Missing bftw flag 0x%X", flags); } -- cgit v1.2.3 From dd1c8c2f08d232d5745dc1f5fe483ec4072fe454 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 17 Oct 2023 11:55:31 -0400 Subject: parse: Set BFTW_WHITEOUTS when parsing -type w --- src/parse.c | 3 +++ tests/bsd/type_w.out | 38 +++++++++++++++++++++++++++++++ tests/bsd/type_w.sh | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+) create mode 100644 tests/bsd/type_w.out create mode 100644 tests/bsd/type_w.sh diff --git a/src/parse.c b/src/parse.c index 976f7cb..fafd787 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2514,6 +2514,8 @@ static struct bfs_expr *parse_status(struct parser_state *state, int arg1, int a * Parse -x?type [bcdpflsD]. */ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) { + struct bfs_ctx *ctx = state->ctx; + bfs_eval_fn *eval = x ? eval_xtype : eval_type; struct bfs_expr *expr = parse_unary_test(state, eval); if (!expr) { @@ -2551,6 +2553,7 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) break; case 'w': expr->num |= 1 << BFS_WHT; + ctx->flags |= BFTW_WHITEOUTS; break; case '\0': diff --git a/tests/bsd/type_w.out b/tests/bsd/type_w.out new file mode 100644 index 0000000..d383f69 --- /dev/null +++ b/tests/bsd/type_w.out @@ -0,0 +1,38 @@ +1: -rw-r--r-- scratch/mnt/lower/bar +1: -rw-r--r-- scratch/mnt/lower/baz +1: -rw-r--r-- scratch/mnt/lower/foo +1: -rw-r--r-- scratch/mnt/upper/baz/qux +1: -rw-r--r-- scratch/mnt/upper/foo +1: drwxr-xr-x scratch/mnt +1: drwxr-xr-x scratch/mnt/lower +1: drwxr-xr-x scratch/mnt/upper +1: drwxr-xr-x scratch/mnt/upper/baz +2: w--------- scratch/mnt/upper/bar +3: -rw-r--r-- scratch/mnt/lower/bar +3: -rw-r--r-- scratch/mnt/lower/baz +3: -rw-r--r-- scratch/mnt/lower/foo +3: -rw-r--r-- scratch/mnt/upper/baz/qux +3: -rw-r--r-- scratch/mnt/upper/foo +3: drwxr-xr-x scratch/mnt +3: drwxr-xr-x scratch/mnt/lower +3: drwxr-xr-x scratch/mnt/upper +3: drwxr-xr-x scratch/mnt/upper/baz +3: w--------- scratch/mnt/upper/bar +4: -rw-r--r-- scratch/mnt/lower/bar +4: -rw-r--r-- scratch/mnt/lower/baz +4: -rw-r--r-- scratch/mnt/lower/foo +4: -rw-r--r-- scratch/mnt/upper/baz/qux +4: drwxr-xr-x scratch/mnt +4: drwxr-xr-x scratch/mnt/lower +4: drwxr-xr-x scratch/mnt/upper +4: drwxr-xr-x scratch/mnt/upper/baz +5: w--------- scratch/mnt/upper/bar +6: -rw-r--r-- scratch/mnt/lower/bar +6: -rw-r--r-- scratch/mnt/lower/baz +6: -rw-r--r-- scratch/mnt/lower/foo +6: -rw-r--r-- scratch/mnt/upper/baz/qux +6: drwxr-xr-x scratch/mnt +6: drwxr-xr-x scratch/mnt/lower +6: drwxr-xr-x scratch/mnt/upper +6: drwxr-xr-x scratch/mnt/upper/baz +6: w--------- scratch/mnt/upper/bar diff --git a/tests/bsd/type_w.sh b/tests/bsd/type_w.sh new file mode 100644 index 0000000..d0cb58c --- /dev/null +++ b/tests/bsd/type_w.sh @@ -0,0 +1,64 @@ +# Only ffs supports whiteouts on FreeBSD +command -v mdconfig &>/dev/null || skip +command -v newfs &>/dev/null || skip + +cleanup=() +do_cleanup() { + # Run cleanup hooks in reverse order + while ((${#cleanup[@]} > 0)); do + cmd="${cleanup[-1]}" + unset 'cleanup[-1]' + eval "bfs_sudo $cmd" + done +} +trap do_cleanup EXIT + +clean_scratch + +# Create a ramdisk +truncate -s1M scratch/img +md=$(bfs_sudo mdconfig scratch/img) || skip +cleanup+=("mdconfig -du $md") + +# Make an ffs filesystem +bfs_sudo newfs -n "/dev/$md" >&2 || skip +mkdir scratch/mnt + +# Mount it +bfs_sudo mount "/dev/$md" scratch/mnt || skip +cleanup+=("umount scratch/mnt") + +# Make it owned by us +bfs_sudo chown "$(id -u):$(id -g)" scratch/mnt +"$XTOUCH" -p scratch/mnt/{lower/{foo,bar,baz},upper/{bar,baz/qux}} + +# Mount a union filesystem within it +bfs_sudo mount -t unionfs -o below scratch/mnt/{lower,upper} +cleanup+=("umount scratch/mnt/upper") + +# Create a whiteout +rm scratch/mnt/upper/bar + +# FreeBSD find doesn't have -printf, so munge -ls output +munge_ls() { + sed -En 's|.*([-drwx]{10}).*(scratch/.*)|'"$1"': \1 \2|p' +} + +# Do a few tests in one +{ + # Normally, we shouldn't see the whiteouts + invoke_bfs scratch/mnt -ls | munge_ls 1 + # -type w adds whiteouts to the output + invoke_bfs scratch/mnt -type w -ls | munge_ls 2 + # So this is not the same as test 1 + invoke_bfs scratch/mnt \( -type w -or -not -type w \) -ls | munge_ls 3 + # Unmount the unionfs + bfs_sudo umount scratch/mnt/upper + unset 'cleanup[-1]' + # Now repeat the same tests + invoke_bfs scratch/mnt -ls | munge_ls 4 + invoke_bfs scratch/mnt -type w -ls | munge_ls 5 + invoke_bfs scratch/mnt \( -type w -or -not -type w \) -ls | munge_ls 6 +} >"$OUT" +sort_output +diff_output -- cgit v1.2.3 From 8560c29d22462764dbf88f52451f8dfe84ad9d4c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 18 Oct 2023 11:34:21 -0400 Subject: wordesc: Don't allow braces in bare words Things like {a,b} should be quoted to avoid brace expansion. --- src/bfstd.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bfstd.c b/src/bfstd.c index a4ae439..ace9ed2 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -816,7 +816,7 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, en /** How much of this string is safe as a bare word? */ static size_t bare_len(const char *str, size_t len) { // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 - size_t ret = strcspn(str, "|&;<>()$`\\\"' *?[#˜=%!"); + size_t ret = strcspn(str, "|&;<>()$`\\\"' *?[#˜=%!{}"); return ret < len ? ret : len; } -- cgit v1.2.3 From 55904f21f8565986fbd04cf0cfba486f936c08a6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 08:03:04 -0400 Subject: bench: Account for tailfin's at-exit -> defer rename --- bench/bench.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bench/bench.sh b/bench/bench.sh index 0dfd7c4..b1d52bb 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -217,7 +217,7 @@ setup() { worktree="bench/worktree" as-user git worktree add -qd "$worktree" - at-exit as-user git worktree remove "$worktree" + defer as-user git worktree remove "$worktree" bin="$(realpath -- "$SETUP_DIR")/bin" as-user mkdir "$bin" @@ -241,7 +241,7 @@ setup() { # Work around this with a symlink tmp=$(as-user mktemp) as-user ln -sf "$bin" "$tmp" - at-exit rm "$tmp" + defer rm "$tmp" export PATH="$tmp:$PATH" fi -- cgit v1.2.3 From b1c6199ad82de2081f18eba931a787a25eb27ea7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 08:04:30 -0400 Subject: tests: Rename --noclean -> --no-clean --- tests/tests.sh | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 61e4aba..282a428 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -94,7 +94,7 @@ function usage() { local pad=$(printf "%*s" ${#0} "") cat < Date: Thu, 19 Oct 2023 09:31:33 -0400 Subject: tests: New defer function --- tests/bsd/type_w.sh | 20 ++++---------------- tests/common/L_mount.sh | 2 +- tests/common/inum_bind_mount.sh | 2 +- tests/common/inum_mount.sh | 2 +- tests/common/mount.sh | 2 +- tests/gnu/fstype_stacked.sh | 4 ++-- tests/gnu/fstype_umount.sh | 2 +- tests/gnu/inum_automount.sh | 2 +- tests/gnu/printf_Y_error.sh | 2 +- tests/gnu/xtype_bind_mount.sh | 2 +- tests/posix/L_xdev.sh | 2 +- tests/posix/depth_error.sh | 2 +- tests/posix/overlayfs.sh | 6 +++++- tests/posix/readdir_error.sh | 2 +- tests/posix/type_bind_mount.sh | 2 +- tests/posix/unionfs.sh | 2 +- tests/posix/xdev.sh | 2 +- tests/tests.sh | 30 +++++++++++++++++++++++++++++- 18 files changed, 54 insertions(+), 34 deletions(-) diff --git a/tests/bsd/type_w.sh b/tests/bsd/type_w.sh index d0cb58c..15b4e68 100644 --- a/tests/bsd/type_w.sh +++ b/tests/bsd/type_w.sh @@ -2,23 +2,12 @@ command -v mdconfig &>/dev/null || skip command -v newfs &>/dev/null || skip -cleanup=() -do_cleanup() { - # Run cleanup hooks in reverse order - while ((${#cleanup[@]} > 0)); do - cmd="${cleanup[-1]}" - unset 'cleanup[-1]' - eval "bfs_sudo $cmd" - done -} -trap do_cleanup EXIT - clean_scratch # Create a ramdisk truncate -s1M scratch/img md=$(bfs_sudo mdconfig scratch/img) || skip -cleanup+=("mdconfig -du $md") +defer bfs_sudo mdconfig -du "$md" # Make an ffs filesystem bfs_sudo newfs -n "/dev/$md" >&2 || skip @@ -26,7 +15,7 @@ mkdir scratch/mnt # Mount it bfs_sudo mount "/dev/$md" scratch/mnt || skip -cleanup+=("umount scratch/mnt") +defer bfs_sudo umount scratch/mnt # Make it owned by us bfs_sudo chown "$(id -u):$(id -g)" scratch/mnt @@ -34,7 +23,7 @@ bfs_sudo chown "$(id -u):$(id -g)" scratch/mnt # Mount a union filesystem within it bfs_sudo mount -t unionfs -o below scratch/mnt/{lower,upper} -cleanup+=("umount scratch/mnt/upper") +defer bfs_sudo umount scratch/mnt/upper # Create a whiteout rm scratch/mnt/upper/bar @@ -53,8 +42,7 @@ munge_ls() { # So this is not the same as test 1 invoke_bfs scratch/mnt \( -type w -or -not -type w \) -ls | munge_ls 3 # Unmount the unionfs - bfs_sudo umount scratch/mnt/upper - unset 'cleanup[-1]' + pop_defer # Now repeat the same tests invoke_bfs scratch/mnt -ls | munge_ls 4 invoke_bfs scratch/mnt -type w -ls | munge_ls 5 diff --git a/tests/common/L_mount.sh b/tests/common/L_mount.sh index b04acd0..aaf9069 100644 --- a/tests/common/L_mount.sh +++ b/tests/common/L_mount.sh @@ -4,7 +4,7 @@ clean_scratch mkdir scratch/{foo,mnt} bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -trap "bfs_sudo umount scratch/mnt" EXIT +defer bfs_sudo umount scratch/mnt ln -s ../mnt scratch/foo/bar "$XTOUCH" scratch/mnt/baz diff --git a/tests/common/inum_bind_mount.sh b/tests/common/inum_bind_mount.sh index ecb4ec3..47f7c36 100644 --- a/tests/common/inum_bind_mount.sh +++ b/tests/common/inum_bind_mount.sh @@ -4,6 +4,6 @@ clean_scratch "$XTOUCH" scratch/{foo,bar} bfs_sudo mount --bind scratch/{foo,bar} || skip -trap "bfs_sudo umount scratch/bar" EXIT +defer bfs_sudo umount scratch/bar bfs_diff scratch -inum "$(inum scratch/bar)" diff --git a/tests/common/inum_mount.sh b/tests/common/inum_mount.sh index a4832e4..1bf2d86 100644 --- a/tests/common/inum_mount.sh +++ b/tests/common/inum_mount.sh @@ -4,6 +4,6 @@ clean_scratch mkdir scratch/{foo,mnt} bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -trap "bfs_sudo umount scratch/mnt" EXIT +defer bfs_sudo umount scratch/mnt bfs_diff scratch -inum "$(inum scratch/mnt)" diff --git a/tests/common/mount.sh b/tests/common/mount.sh index b13b43c..db8b801 100644 --- a/tests/common/mount.sh +++ b/tests/common/mount.sh @@ -4,7 +4,7 @@ clean_scratch mkdir scratch/{foo,mnt} bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -trap "bfs_sudo umount scratch/mnt" EXIT +defer bfs_sudo umount scratch/mnt "$XTOUCH" scratch/foo/bar scratch/mnt/baz diff --git a/tests/gnu/fstype_stacked.sh b/tests/gnu/fstype_stacked.sh index 16f428f..a4b067a 100644 --- a/tests/gnu/fstype_stacked.sh +++ b/tests/gnu/fstype_stacked.sh @@ -4,9 +4,9 @@ clean_scratch mkdir scratch/mnt bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -trap "bfs_sudo umount scratch/mnt" EXIT +defer bfs_sudo umount scratch/mnt bfs_sudo mount -t ramfs ramfs scratch/mnt || skip -trap "bfs_sudo umount scratch/mnt; bfs_sudo umount scratch/mnt" EXIT +defer bfs_sudo umount scratch/mnt bfs_diff scratch/mnt -fstype ramfs -print -o -printf '%p: %F\n' diff --git a/tests/gnu/fstype_umount.sh b/tests/gnu/fstype_umount.sh index e817831..b6da7a3 100644 --- a/tests/gnu/fstype_umount.sh +++ b/tests/gnu/fstype_umount.sh @@ -4,7 +4,7 @@ clean_scratch mkdir scratch/tmp bfs_sudo mount -t tmpfs tmpfs scratch/tmp || skip -trap "bfs_sudo umount -R scratch/tmp" EXIT +defer bfs_sudo umount -R scratch/tmp mkdir scratch/tmp/ram bfs_sudo mount -t ramfs ramfs scratch/tmp/ram || skip diff --git a/tests/gnu/inum_automount.sh b/tests/gnu/inum_automount.sh index c4450ef..261a4be 100644 --- a/tests/gnu/inum_automount.sh +++ b/tests/gnu/inum_automount.sh @@ -6,7 +6,7 @@ clean_scratch mkdir scratch/{foo,automnt} bfs_sudo systemd-mount -A -o bind basic scratch/automnt || skip -trap "bfs_sudo systemd-umount scratch/automnt" EXIT +defer bfs_sudo systemd-umount scratch/automnt before=$(inum scratch/automnt) bfs_diff scratch -inum "$before" -prune diff --git a/tests/gnu/printf_Y_error.sh b/tests/gnu/printf_Y_error.sh index 3aa816e..13d52e3 100644 --- a/tests/gnu/printf_Y_error.sh +++ b/tests/gnu/printf_Y_error.sh @@ -3,6 +3,6 @@ mkdir scratch/foo ln -s foo/bar scratch/bar chmod -x scratch/foo -trap "chmod +x scratch/foo" EXIT +defer chmod +x scratch/foo ! bfs_diff scratch -printf '(%p) (%l) %y %Y\n' diff --git a/tests/gnu/xtype_bind_mount.sh b/tests/gnu/xtype_bind_mount.sh index 99a11ab..1cc20ec 100644 --- a/tests/gnu/xtype_bind_mount.sh +++ b/tests/gnu/xtype_bind_mount.sh @@ -5,6 +5,6 @@ clean_scratch ln -s /dev/null scratch/link bfs_sudo mount --bind /dev/null scratch/null || skip -trap "bfs_sudo umount scratch/null" EXIT +defer bfs_sudo umount scratch/null bfs_diff -L scratch -type c diff --git a/tests/posix/L_xdev.sh b/tests/posix/L_xdev.sh index 172ea23..d16c211 100644 --- a/tests/posix/L_xdev.sh +++ b/tests/posix/L_xdev.sh @@ -4,7 +4,7 @@ clean_scratch mkdir scratch/{foo,mnt} bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -trap "bfs_sudo umount scratch/mnt" EXIT +defer bfs_sudo umount scratch/mnt ln -s ../mnt scratch/foo/bar "$XTOUCH" scratch/mnt/baz diff --git a/tests/posix/depth_error.sh b/tests/posix/depth_error.sh index e91fbf6..a6429d1 100644 --- a/tests/posix/depth_error.sh +++ b/tests/posix/depth_error.sh @@ -2,6 +2,6 @@ clean_scratch "$XTOUCH" -p scratch/foo/bar chmod a-r scratch/foo -trap "chmod +r scratch/foo" EXIT +defer chmod +r scratch/foo ! bfs_diff scratch -depth diff --git a/tests/posix/overlayfs.sh b/tests/posix/overlayfs.sh index a56b5b3..4cccebf 100644 --- a/tests/posix/overlayfs.sh +++ b/tests/posix/overlayfs.sh @@ -1,7 +1,11 @@ test "$UNAME" = "Linux" || skip + clean_scratch "$XTOUCH" -p scratch/{lower/{foo,bar,baz},upper/{bar,baz/qux}} + mkdir -p scratch/{work,merged} bfs_sudo mount -t overlay overlay -olowerdir=scratch/lower,upperdir=scratch/upper,workdir=scratch/work scratch/merged || skip -trap "bfs_sudo umount scratch/merged; bfs_sudo rm -rf scratch/work" EXIT +defer bfs_sudo rm -rf scratch/work +defer bfs_sudo umount scratch/merged + bfs_diff scratch/merged diff --git a/tests/posix/readdir_error.sh b/tests/posix/readdir_error.sh index 9a002a1..fc48eb1 100644 --- a/tests/posix/readdir_error.sh +++ b/tests/posix/readdir_error.sh @@ -13,7 +13,7 @@ mkfifo scratch/{fever,pid,wait,running} ) & # Kill the parent cat on exit -trap "kill -9 %1" EXIT +defer kill -9 %1 # Read the child PID read -r pid 0)); do + pop_defer || ret=$? + done + + return $ret +} + function bfs_sudo() { if ((${#SUDO[@]})); then "${SUDO[@]}" "$@" @@ -304,7 +332,7 @@ function cleanup() { } if [ "$CLEAN" ]; then - trap cleanup EXIT + defer cleanup else echo "Test files saved to $TMP" fi -- cgit v1.2.3 From e527c1b288e15eadead25e01c29b5e8b074601a7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 09:34:58 -0400 Subject: tests: Fix uses of $? with set -e --- tests/tests.sh | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index b159ea2..83f5b68 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -507,14 +507,15 @@ function bfs_verbose() { function invoke_bfs() { bfs_verbose "$@" - "${BFS[@]}" "$@" - local status="$?" + + local ret=0 + "${BFS[@]}" "$@" || ret=$? # Allow bfs to fail, but not crash - if ((status > 125)); then - exit "$status" + if ((ret > 125)); then + exit "$ret" else - return "$status" + return "$ret" fi } @@ -528,13 +529,14 @@ function bfs_pty() { test -n "${UNBUFFER:-}" || skip bfs_verbose "$@" - "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" - local status="$?" - if ((status > 125)); then - exit "$status" + local ret=0 + "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" || ret=$? + + if ((ret > 125)); then + exit "$ret" else - return "$status" + return "$ret" fi } -- cgit v1.2.3 From ee11047140578029ff6d6bd27ac733b03c3e737c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 10:19:45 -0400 Subject: tests: Use the ERR trap to print the failing command --- tests/tests.sh | 50 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 83f5b68..c601f8a 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -260,6 +260,14 @@ if (( ${#TEST_CASES[@]} == 0 )); then exit 1 fi +function quote() { + printf '%q' "$1" + shift + if (($# > 0)); then + printf ' %q' "$@" + fi +} + DEFER=() # Run a command when this (sub)shell exits @@ -269,13 +277,21 @@ function defer() { DEFER=() trap pop_defers EXIT fi - DEFER+=("$(printf '%q ' "$@")") + DEFER+=("$(quote "$@")") } function pop_defer() { local cmd="${DEFER[-1]}" unset "DEFER[-1]" - eval "$cmd" + + local ret=0 + eval "$cmd" || ret=$? + + if ((ret != 0)); then + printf 'defer %s: error %d\n' "$cmd" $ret >&2 + fi + + return $ret } function pop_defers() { @@ -703,6 +719,32 @@ elif color_fd 1; then trap update_eol WINCH fi +function callers() { + local frame=0 + while caller $frame; do + ((++frame)) + done +} + +function debug_err() { + local ret=$? + local line func file + callers | while read -r line func file; do + if [ "$func" = source ]; then + local cmd="$(awk "NR == $line" "$file" 2>/dev/null)" || : + file="${file/#*\/tests\//tests\/}" + printf '%s:%d: %s: error %d\n' "$file" "$line" "$cmd" "$ret" >&2 + break + fi + done +} + +function run_test() ( + set -eE + trap debug_err ERR + source "$@" +) + passed=0 failed=0 skipped=0 @@ -720,9 +762,9 @@ for TEST in "${TEST_CASES[@]}"; do mkdir -p "${OUT%/*}" if [ "$VERBOSE_ERRORS" ]; then - (set -e; . "$TESTS/$TEST.sh") + run_test "$TESTS/$TEST.sh" else - (set -e; . "$TESTS/$TEST.sh") 2>"$TMP/$TEST.err" + run_test "$TESTS/$TEST.sh" 2>"$TMP/$TEST.err" fi status=$? -- cgit v1.2.3 From 31442d6bdaf6a7490e1e31fd9a676525f8a05a6d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 11:31:31 -0400 Subject: tests: Clean up colorization --- tests/tests.sh | 178 +++++++++++++++++++++++++++++++++------------------------ 1 file changed, 102 insertions(+), 76 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index c601f8a..5c8fca8 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -19,29 +19,35 @@ export UBSAN_OPTIONS="$SAN_OPTIONS" export LS_COLORS="" unset BFS_COLORS +BLD=$'\e[01m' +RED=$'\e[01;31m' +GRN=$'\e[01;32m' +YLW=$'\e[01;33m' +BLU=$'\e[01;34m' +MAG=$'\e[01;35m' +CYN=$'\e[01;36m' +RST=$'\e[0m' + function color_fd() { [ -z "${NO_COLOR:-}" ] && [ -t "$1" ] } -if color_fd 1; then - BLD=$'\033[01m' - RED=$'\033[01;31m' - GRN=$'\033[01;32m' - YLW=$'\033[01;33m' - BLU=$'\033[01;34m' - MAG=$'\033[01;35m' - CYN=$'\033[01;36m' - RST=$'\033[0m' -else - BLD= - RED= - GRN= - YLW= - BLU= - MAG= - CYN= - RST= -fi +color_fd 1 && COLOR_STDOUT=1 || COLOR_STDOUT=0 +color_fd 2 && COLOR_STDERR=1 || COLOR_STDERR=0 + +# Filter out escape sequences if necessary +function color() { + if color_fd 1; then + cat + else + sed $'s/\e\\[[^m]*m//g' + fi +} + +# printf with auto-detected color support +function cprintf() { + printf "$@" | color +} UNAME=$(uname) @@ -59,14 +65,14 @@ fi if command -v capsh &>/dev/null; then if capsh --has-p=cap_dac_override &>/dev/null || capsh --has-p=cap_dac_read_search &>/dev/null; then if [ -n "${BFS_TRIED_DROP:-}" ]; then - cat >&2 <&2 <&2 <&2 <&2 <&2 <&2 + cprintf "${RED}error:${RST} Unrecognized option '%s'.\n\n" "$arg" >&2 usage >&2 exit 1 ;; @@ -237,7 +243,7 @@ chown "$(id -u):$(id -g)" "$TMP" cd "$TESTS" -if (( ${#PATTERNS[@]} == 0 )); then +if ((${#PATTERNS[@]} == 0)); then PATTERNS=("*") fi @@ -252,10 +258,10 @@ for TEST in {posix,common,bsd,gnu,bfs}/*.sh; do done done -if (( ${#TEST_CASES[@]} == 0 )); then - printf "${RED}error:${RST} No tests matched" >&2 - printf " ${BLD}%s${RST}" "${PATTERNS[@]}" >&2 - printf ".\n\n" >&2 +if ((${#TEST_CASES[@]} == 0)); then + cprintf "${RED}error:${RST} No tests matched" >&2 + cprintf " ${BLD}%s${RST}" "${PATTERNS[@]}" >&2 + cprintf ".\n\n" >&2 usage >&2 exit 1 fi @@ -268,27 +274,48 @@ function quote() { fi } -DEFER=() - # Run a command when this (sub)shell exits function defer() { trap -- KILL if ! trap -p EXIT | grep -q pop_defers; then - DEFER=() + DEFER_CMDS=() + DEFER_LINES=() + DEFER_FILES=() trap pop_defers EXIT fi - DEFER+=("$(quote "$@")") + + DEFER_CMDS+=("$(quote "$@")") + + local line file + read -r line file < <(caller) + DEFER_LINES+=("$line") + DEFER_FILES+=("$file") +} + +function report_err() { + local file="${1/#*\/tests\//tests\/}" + set -- "$file" "${@:2}" + + if ((COLOR_STDERR)); then + printf "${BLD}%s:%d:${RST} ${RED}error %d:${RST}\n %s\n" "$@" >&2 + else + printf "%s:%d: error %d:\n %s\n" "$@" >&2 + fi } function pop_defer() { - local cmd="${DEFER[-1]}" - unset "DEFER[-1]" + local cmd="${DEFER_CMDS[-1]}" + local file="${DEFER_FILES[-1]}" + local line="${DEFER_LINES[-1]}" + unset "DEFER_CMDS[-1]" + unset "DEFER_FILES[-1]" + unset "DEFER_LINES[-1]" local ret=0 eval "$cmd" || ret=$? if ((ret != 0)); then - printf 'defer %s: error %d\n' "$cmd" $ret >&2 + report_err "$file" $line $ret "defer $cmd" fi return $ret @@ -297,7 +324,7 @@ function pop_defer() { function pop_defers() { local ret=0 - while ((${#DEFER[@]} > 0)); do + while ((${#DEFER_CMDS[@]} > 0)); do pop_defer || ret=$? done @@ -492,33 +519,34 @@ if [ "$VERBOSE_COMMANDS" ]; then fi function bfs_verbose() { - if [ "$VERBOSE_COMMANDS" ]; then - if color_fd 3; then - printf "${GRN}%q${RST} " "${BFS[@]}" >&3 - - local expr_started= - for arg; do - if [[ $arg == -[A-Z]* ]]; then - printf "${CYN}%q${RST} " "$arg" >&3 - elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then - expr_started=yes - printf "${RED}%q${RST} " "$arg" >&3 - elif [[ $expr_started && $arg == [\),] ]]; then - printf "${RED}%q${RST} " "$arg" >&3 - elif [[ $arg == -?* ]]; then - expr_started=yes - printf "${BLU}%q${RST} " "$arg" >&3 - elif [ "$expr_started" ]; then - printf "${BLD}%q${RST} " "$arg" >&3 - else - printf "${MAG}%q${RST} " "$arg" >&3 - fi - done - else - printf '%q ' "${BFS[@]}" "$@" >&3 - fi - printf '\n' >&3 + if ! [ "$VERBOSE_COMMANDS" ]; then + return fi + + { + printf "${GRN}%q${RST} " "${BFS[@]}" + + local expr_started= + for arg; do + if [[ $arg == -[A-Z]* ]]; then + printf "${CYN}%q${RST} " "$arg" + elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then + expr_started=yes + printf "${RED}%q${RST} " "$arg" + elif [[ $expr_started && $arg == [\),] ]]; then + printf "${RED}%q${RST} " "$arg" + elif [[ $arg == -?* ]]; then + expr_started=yes + printf "${BLU}%q${RST} " "$arg" + elif [ "$expr_started" ]; then + printf "${BLD}%q${RST} " "$arg" + else + printf "${MAG}%q${RST} " "$arg" + fi + done + + printf '\n' + } | color >&3 } function invoke_bfs() { @@ -565,7 +593,7 @@ function check_exit() { } # Detect colored diff support -if color_fd 2 && diff --color=always /dev/null /dev/null 2>/dev/null; then +if ((COLOR_STDERR)) && diff --color=always /dev/null /dev/null 2>/dev/null; then DIFF="diff --color=always" else DIFF="diff" @@ -608,10 +636,10 @@ function skip() { if [ "$VERBOSE_SKIPPED" ]; then caller | { read -r line file - printf "${BOL}${CYN}%s skipped!${RST} (%s)\n" "$TEST" "$(awk "NR == $line" "$file")" + cprintf "${BOL}${CYN}%s skipped!${RST} (%s)\n" "$TEST" "$(awk "NR == $line" "$file")" } elif [ "$VERBOSE_TESTS" ]; then - printf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" + cprintf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" fi exit $EX_SKIP @@ -706,7 +734,7 @@ function update_eol() { if [ "$VERBOSE_TESTS" ]; then BOL='' -elif color_fd 1; then +elif ((COLOR_STDOUT)); then BOL='\r\033[K' # Workaround for bash 4: checkwinsize is off by default. We can turn it on, @@ -727,13 +755,11 @@ function callers() { } function debug_err() { - local ret=$? - local line func file + local ret=$? line func file callers | while read -r line func file; do if [ "$func" = source ]; then local cmd="$(awk "NR == $line" "$file" 2>/dev/null)" || : - file="${file/#*\/tests\//tests\/}" - printf '%s:%d: %s: error %d\n' "$file" "$line" "$cmd" "$ret" >&2 + report_err "$file" $line $ret "$cmd" break fi done @@ -749,7 +775,7 @@ passed=0 failed=0 skipped=0 -if color_fd 1 || [ "$VERBOSE_TESTS" ]; then +if ((COLOR_STDOUT)) || [ "$VERBOSE_TESTS" ]; then TEST_FMT="${BOL}${YLW}%s${RST}${EOL}" else TEST_FMT="." @@ -775,7 +801,7 @@ for TEST in "${TEST_CASES[@]}"; do else ((++failed)) [ "$VERBOSE_ERRORS" ] || cat "$TMP/$TEST.err" >&2 - printf "${BOL}${RED}%s failed!${RST}\n" "$TEST" + cprintf "${BOL}${RED}%s failed!${RST}\n" "$TEST" [ "$STOP" ] && break fi done @@ -783,12 +809,12 @@ done printf "${BOL}" if ((passed > 0)); then - printf "${GRN}tests passed: %d${RST}\n" "$passed" + cprintf "${GRN}tests passed: %d${RST}\n" "$passed" fi if ((skipped > 0)); then - printf "${CYN}tests skipped: %s${RST}\n" "$skipped" + cprintf "${CYN}tests skipped: %s${RST}\n" "$skipped" fi if ((failed > 0)); then - printf "${RED}tests failed: %s${RST}\n" "$failed" + cprintf "${RED}tests failed: %s${RST}\n" "$failed" exit 1 fi -- cgit v1.2.3 From d484cba3424dcfca4851ba867d8877e3a9381a0e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 11:39:04 -0400 Subject: tests: Update bash coding style --- tests/tests.sh | 64 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 32 insertions(+), 32 deletions(-) diff --git a/tests/tests.sh b/tests/tests.sh index 5c8fca8..dcef28e 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -83,7 +83,7 @@ EOF --caps=cap_dac_override,cap_dac_read_search-eip \ -- "$0" "$@" fi -elif [ "$EUID" -eq 0 ]; then +elif ((EUID == 0)); then UNLESS= if [ "$UNAME" = "Linux" ]; then UNLESS=" unless ${GRN}capsh${RST} is installed" @@ -142,13 +142,13 @@ EOF PATTERNS=() SUDO=() -STOP= -CLEAN=yes -UPDATE= -VERBOSE_COMMANDS= -VERBOSE_ERRORS= -VERBOSE_SKIPPED= -VERBOSE_TESTS= +STOP=0 +CLEAN=1 +UPDATE=0 +VERBOSE_COMMANDS=0 +VERBOSE_ERRORS=0 +VERBOSE_SKIPPED=0 +VERBOSE_TESTS=0 for arg; do case "$arg" in @@ -174,31 +174,31 @@ for arg; do read -a SUDO <<<"${arg#*=}" ;; --stop) - STOP=yes + STOP=1 ;; --no-clean|--noclean) - CLEAN= + CLEAN=0 ;; --update) - UPDATE=yes + UPDATE=1 ;; --verbose=commands) - VERBOSE_COMMANDS=yes + VERBOSE_COMMANDS=1 ;; --verbose=errors) - VERBOSE_ERRORS=yes + VERBOSE_ERRORS=1 ;; --verbose=skipped) - VERBOSE_SKIPPED=yes + VERBOSE_SKIPPED=1 ;; --verbose=tests) - VERBOSE_TESTS=yes + VERBOSE_TESTS=1 ;; --verbose) - VERBOSE_COMMANDS=yes - VERBOSE_ERRORS=yes - VERBOSE_SKIPPED=yes - VERBOSE_TESTS=yes + VERBOSE_COMMANDS=1 + VERBOSE_ERRORS=1 + VERBOSE_SKIPPED=1 + VERBOSE_TESTS=1 ;; --help) usage @@ -374,7 +374,7 @@ function cleanup() { rm -rf "$TMP" } -if [ "$CLEAN" ]; then +if ((CLEAN)); then defer cleanup else echo "Test files saved to $TMP" @@ -513,13 +513,13 @@ mkdir "$TMP/scratch" # Close stdin so bfs doesn't think we're interactive exec &1 fi function bfs_verbose() { - if ! [ "$VERBOSE_COMMANDS" ]; then + if ((!VERBOSE_COMMANDS)); then return fi @@ -611,7 +611,7 @@ function sort_output() { function diff_output() { local GOLD="$TESTS/$TEST.out" - if [ "$UPDATE" ]; then + if ((UPDATE)); then cp "$OUT" "$GOLD" else $DIFF -u "$GOLD" "$OUT" >&2 @@ -633,12 +633,12 @@ function bfs_diff() ( ) function skip() { - if [ "$VERBOSE_SKIPPED" ]; then + if ((VERBOSE_SKIPPED)); then caller | { read -r line file cprintf "${BOL}${CYN}%s skipped!${RST} (%s)\n" "$TEST" "$(awk "NR == $line" "$file")" } - elif [ "$VERBOSE_TESTS" ]; then + elif ((VERBOSE_TESTS)); then cprintf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" fi @@ -658,7 +658,7 @@ function closefrom() { fi local fd="${fd##*/}" - if [ "$fd" -ge "$1" ]; then + if ((fd >= $1)); then eval "exec ${fd}<&-" fi done @@ -674,7 +674,7 @@ function set_acl() { chmod +a "$(id -un) allow read,write" "$1" ;; FreeBSD) - if [ "$(getconf ACL_NFS4 "$1")" -gt 0 ]; then + if (($(getconf ACL_NFS4 "$1") > 0)); then setfacl -m "u:$(id -un):rw::allow" "$1" else setfacl -m "u:$(id -un):rw" "$1" @@ -732,7 +732,7 @@ function update_eol() { EOL="\\033[${cols}G " } -if [ "$VERBOSE_TESTS" ]; then +if ((VERBOSE_TESTS)); then BOL='' elif ((COLOR_STDOUT)); then BOL='\r\033[K' @@ -775,7 +775,7 @@ passed=0 failed=0 skipped=0 -if ((COLOR_STDOUT)) || [ "$VERBOSE_TESTS" ]; then +if ((COLOR_STDOUT || VERBOSE_TESTS)); then TEST_FMT="${BOL}${YLW}%s${RST}${EOL}" else TEST_FMT="." @@ -787,7 +787,7 @@ for TEST in "${TEST_CASES[@]}"; do OUT="$TMP/$TEST.out" mkdir -p "${OUT%/*}" - if [ "$VERBOSE_ERRORS" ]; then + if ((VERBOSE_ERRORS)); then run_test "$TESTS/$TEST.sh" else run_test "$TESTS/$TEST.sh" 2>"$TMP/$TEST.err" @@ -800,9 +800,9 @@ for TEST in "${TEST_CASES[@]}"; do ((++skipped)) else ((++failed)) - [ "$VERBOSE_ERRORS" ] || cat "$TMP/$TEST.err" >&2 + ((VERBOSE_ERRORS)) || cat "$TMP/$TEST.err" >&2 cprintf "${BOL}${RED}%s failed!${RST}\n" "$TEST" - [ "$STOP" ] && break + ((STOP)) && break fi done -- cgit v1.2.3 From 785a3f2d777627f39bed44f4ae7a0180d5184109 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 16:37:47 -0400 Subject: tests: Refactor implementation into separate files --- tests/bfs/deep_strict.sh | 2 - tests/color.sh | 43 +++ tests/common/execdir_ulimit.sh | 1 - tests/getopts.sh | 158 ++++++++ tests/gnu/printf_u_g_ulimit.sh | 1 - tests/ls-color.sh | 6 +- tests/posix/deep.sh | 2 - tests/posix/nogroup_ulimit.sh | 1 - tests/posix/nouser_ulimit.sh | 1 - tests/run.sh | 316 ++++++++++++++++ tests/stddirs.sh | 185 +++++++++ tests/tests.sh | 824 +---------------------------------------- tests/util.sh | 189 ++++++++++ 13 files changed, 906 insertions(+), 823 deletions(-) create mode 100644 tests/color.sh create mode 100644 tests/getopts.sh create mode 100644 tests/run.sh create mode 100644 tests/stddirs.sh create mode 100644 tests/util.sh diff --git a/tests/bfs/deep_strict.sh b/tests/bfs/deep_strict.sh index e057310..50c8f05 100644 --- a/tests/bfs/deep_strict.sh +++ b/tests/bfs/deep_strict.sh @@ -1,5 +1,3 @@ -closefrom 4 - # Not even enough fds to keep the root open ulimit -n 7 bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash {} \; diff --git a/tests/color.sh b/tests/color.sh new file mode 100644 index 0000000..0d6ef68 --- /dev/null +++ b/tests/color.sh @@ -0,0 +1,43 @@ +#!/hint/bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +## Colored output + +# Common escape sequences +BLD=$'\e[01m' +RED=$'\e[01;31m' +GRN=$'\e[01;32m' +YLW=$'\e[01;33m' +BLU=$'\e[01;34m' +MAG=$'\e[01;35m' +CYN=$'\e[01;36m' +RST=$'\e[0m' + +# Check if we should color output to the given fd +color_fd() { + [ -z "${NO_COLOR:-}" ] && [ -t "$1" ] +} + +# Cache the color status for std{out,err} +color_fd 1 && COLOR_STDOUT=1 || COLOR_STDOUT=0 +color_fd 2 && COLOR_STDERR=1 || COLOR_STDERR=0 + +# Save these in case the tests unset PATH +CAT=$(command -v cat) +SED=$(command -v sed) + +# Filter out escape sequences if necessary +color() { + if color_fd 1; then + "$CAT" + else + "$SED" $'s/\e\\[[^m]*m//g' + fi +} + +# printf with auto-detected color support +cprintf() { + printf "$@" | color +} diff --git a/tests/common/execdir_ulimit.sh b/tests/common/execdir_ulimit.sh index 8bd9edd..f7fc467 100644 --- a/tests/common/execdir_ulimit.sh +++ b/tests/common/execdir_ulimit.sh @@ -2,6 +2,5 @@ clean_scratch mkdir -p scratch/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z mkdir -p scratch/a/b/c/d/e/f/g/h/i/j/k/l/m/0/1/2/3/4/5/6/7/8/9/A/B/C -closefrom 4 ulimit -n 13 bfs_diff scratch -execdir echo {} \; diff --git a/tests/getopts.sh b/tests/getopts.sh new file mode 100644 index 0000000..6616a4a --- /dev/null +++ b/tests/getopts.sh @@ -0,0 +1,158 @@ +#!/hint/bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +## Argument parsing + +# Print usage information +usage() { + local pad=$(printf "%*s" ${#0} "") + color <&2 + usage >&2 + exit 1 + ;; + *) + PATTERNS+=("$arg") + ;; + esac + done + + # Try to resolve the path to $BFS before we cd, while also supporting + # --bfs="./bin/bfs -S ids" + read -a BFS <<<"${BFS:-$BIN/bfs}" + BFS[0]=$(_realpath "$(command -v "${BFS[0]}")") + + if ((${#PATTERNS[@]} == 0)); then + PATTERNS=("*") + fi + + TEST_CASES=() + ALL_TESTS=($(cd "$TESTS" && quote {posix,common,bsd,gnu,bfs}/*.sh)) + for TEST in "${ALL_TESTS[@]}"; do + TEST="${TEST%.sh}" + for PATTERN in "${PATTERNS[@]}"; do + if [[ $TEST == $PATTERN ]]; then + TEST_CASES+=("$TEST") + break + fi + done + done + + if ((${#TEST_CASES[@]} == 0)); then + cprintf "${RED}error:${RST} No tests matched" >&2 + cprintf " ${BLD}%s${RST}" "${PATTERNS[@]}" >&2 + cprintf ".\n\n" >&2 + usage >&2 + exit 1 + fi +} diff --git a/tests/gnu/printf_u_g_ulimit.sh b/tests/gnu/printf_u_g_ulimit.sh index a84ee29..390ad48 100644 --- a/tests/gnu/printf_u_g_ulimit.sh +++ b/tests/gnu/printf_u_g_ulimit.sh @@ -1,3 +1,2 @@ -closefrom 4 ulimit -n 16 [ "$(invoke_bfs deep -printf '%u %g\n' | uniq)" = "$(id -un) $(id -gn)" ] diff --git a/tests/ls-color.sh b/tests/ls-color.sh index 9fdd59c..b9a0402 100755 --- a/tests/ls-color.sh +++ b/tests/ls-color.sh @@ -7,7 +7,7 @@ set -e -function parse_ls_colors() { +parse_ls_colors() { for key; do local -n var="$key" if [[ "$LS_COLORS" =~ (^|:)$key=(([^:]|\\:)*) ]]; then @@ -18,7 +18,7 @@ function parse_ls_colors() { done } -function re_escape() { +re_escape() { # https://stackoverflow.com/a/29613573/502399 sed 's/[^^]/[&]/g; s/\^/\\^/g' <<<"$1" } @@ -34,7 +34,7 @@ parse_ls_colors rs lc rc ec no strip="(($(re_escape "$lc$no$rc"))?($(re_escape "$ec")|$(re_escape "$lc$rc")))+" -function ls_color() { +ls_color() { # Strip the leading reset sequence from the ls output ls -1d --color "$@" | sed -E "s/^$strip([a-z].*)$strip/\4/; s/^$strip//" } diff --git a/tests/posix/deep.sh b/tests/posix/deep.sh index 3d1cd60..431705e 100644 --- a/tests/posix/deep.sh +++ b/tests/posix/deep.sh @@ -1,4 +1,2 @@ -closefrom 4 - ulimit -n 16 bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash {} \; diff --git a/tests/posix/nogroup_ulimit.sh b/tests/posix/nogroup_ulimit.sh index 8f758c4..2186321 100644 --- a/tests/posix/nogroup_ulimit.sh +++ b/tests/posix/nogroup_ulimit.sh @@ -1,4 +1,3 @@ -closefrom 4 ulimit -n 16 # -mindepth 18, but POSIX diff --git a/tests/posix/nouser_ulimit.sh b/tests/posix/nouser_ulimit.sh index 2777589..be0a65f 100644 --- a/tests/posix/nouser_ulimit.sh +++ b/tests/posix/nouser_ulimit.sh @@ -1,4 +1,3 @@ -closefrom 4 ulimit -n 16 # -mindepth 18, but POSIX diff --git a/tests/run.sh b/tests/run.sh new file mode 100644 index 0000000..70c9cc2 --- /dev/null +++ b/tests/run.sh @@ -0,0 +1,316 @@ +#!/hint/bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +## Running test cases + +# Beginning/end of line escape sequences +BOL=$'\n' +EOL=$'\n' + +# Update $EOL for the terminal size +update_eol() { + # Bash gets $COLUMNS from stderr, so if it's redirected use tput instead + local cols="${COLUMNS-}" + if [ -z "$cols" ]; then + cols=$(tput cols) + fi + + # Put the cursor at the last column, then write a space so the next + # character will wrap + EOL=$'\e['"${cols}G " +} + +# ERR trap for tests +debug_err() { + local ret=$? line func file + callers | while read -r line func file; do + if [ "$func" = source ]; then + local cmd="$(awk "NR == $line" "$file" 2>/dev/null)" || : + debug "$file" $line "${RED}error $ret${RST}" "$cmd" >&4 + break + fi + done +} + +# Run a single test +run_test() ( + set -eE + trap debug_err ERR + cd "$TMP" + source "$@" +) + +# Run all the tests +run_tests() { + if ((VERBOSE_TESTS)); then + BOL='' + elif ((COLOR_STDOUT)); then + # Carriage return + clear line + BOL=$'\r\e[K' + + # Workaround for bash 4: checkwinsize is off by default. We can turn it + # on, but we also have to explicitly trigger a foreground job to finish + # so that it will update the window size before we use $COLUMNS + shopt -s checkwinsize + (:) + + update_eol + trap update_eol WINCH + fi + + passed=0 + failed=0 + skipped=0 + + if ((COLOR_STDOUT || VERBOSE_TESTS)); then + TEST_FMT="${BOL}${YLW}%s${RST}${EOL}" + else + TEST_FMT="." + fi + + # Turn off set -e (but turn it back on in run_test) + set +e + + for TEST in "${TEST_CASES[@]}"; do + printf "$TEST_FMT" "$TEST" + + OUT="$TMP/$TEST.out" + mkdir -p "${OUT%/*}" + + if ((VERBOSE_ERRORS)); then + run_test "$TESTS/$TEST.sh" + else + run_test "$TESTS/$TEST.sh" 2>"$TMP/$TEST.err" + fi + status=$? + + if ((status == 0)); then + ((++passed)) + elif ((status == EX_SKIP)); then + ((++skipped)) + else + ((++failed)) + ((VERBOSE_ERRORS)) || cat "$TMP/$TEST.err" >&2 + cprintf "${BOL}${RED}%s failed!${RST}\n" "$TEST" + ((STOP)) && break + fi + done + + printf "${BOL}" + + if ((passed > 0)); then + cprintf "${GRN}tests passed: %d${RST}\n" "$passed" + fi + if ((skipped > 0)); then + cprintf "${CYN}tests skipped: %s${RST}\n" "$skipped" + fi + if ((failed > 0)); then + cprintf "${RED}tests failed: %s${RST}\n" "$failed" + exit 1 + fi +} + +## Utilities for the tests themselves + +# Return value when a test is skipped +EX_SKIP=77 + +# Skip the current test +skip() { + if ((VERBOSE_SKIPPED)); then + caller | { + read -r line file + printf "${BOL}" + debug "$file" $line "${CYN}$TEST skipped!${RST}" "$(awk "NR == $line" "$file")" >&3 + } + elif ((VERBOSE_TESTS)); then + cprintf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" + fi + + exit $EX_SKIP +} + +# Run a command and check its exit status +check_exit() { + local expected="$1" + local actual="0" + shift + "$@" || actual="$?" + ((actual == expected)) +} + +# Run a command with sudo +bfs_sudo() { + if ((${#SUDO[@]})); then + "${SUDO[@]}" "$@" + else + return 1 + fi +} + +# Get the inode number of a file +inum() { + ls -id "$@" | awk '{ print $1 }' +} + +# Set an ACL on a file +set_acl() { + case "$UNAME" in + Darwin) + chmod +a "$(id -un) allow read,write" "$1" + ;; + FreeBSD) + if (($(getconf ACL_NFS4 "$1") > 0)); then + setfacl -m "u:$(id -un):rw::allow" "$1" + else + setfacl -m "u:$(id -un):rw" "$1" + fi + ;; + *) + setfacl -m "u:$(id -un):rw" "$1" + ;; + esac +} + +# Print a bfs invocation for --verbose=commands +bfs_verbose() ( + if ((!VERBOSE_COMMANDS)); then + return + fi + + # Free up an fd for the pipe + exec 4>&- + + { + printf "${GRN}%q${RST} " "${BFS[@]}" + + local expr_started= + for arg; do + if [[ $arg == -[A-Z]* ]]; then + printf "${CYN}%q${RST} " "$arg" + elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then + expr_started=yes + printf "${RED}%q${RST} " "$arg" + elif [[ $expr_started && $arg == [\),] ]]; then + printf "${RED}%q${RST} " "$arg" + elif [[ $arg == -?* ]]; then + expr_started=yes + printf "${BLU}%q${RST} " "$arg" + elif [ "$expr_started" ]; then + printf "${BLD}%q${RST} " "$arg" + else + printf "${MAG}%q${RST} " "$arg" + fi + done + + printf '\n' + } | color >&3 +) + +# Run the bfs we're testing +invoke_bfs() { + bfs_verbose "$@" + + local ret=0 + # Close the logging fds + "${BFS[@]}" "$@" 3>&- 4>&- || ret=$? + + # Allow bfs to fail, but not crash + if ((ret > 125)); then + exit "$ret" + else + return "$ret" + fi +} + +if command -v unbuffer &>/dev/null; then + UNBUFFER=unbuffer +elif command -v expect_unbuffer &>/dev/null; then + UNBUFFER=expect_unbuffer +fi + +# Run bfs with a pseudo-terminal attached +bfs_pty() { + test -n "${UNBUFFER:-}" || skip + + bfs_verbose "$@" + + local ret=0 + "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" || ret=$? + + if ((ret > 125)); then + exit "$ret" + else + return "$ret" + fi +} + +# Create a directory tree with xattrs in scratch +make_xattrs() { + clean_scratch + + "$XTOUCH" scratch/{normal,xattr,xattr_2} + ln -s xattr scratch/link + ln -s normal scratch/xattr_link + + case "$UNAME" in + Darwin) + xattr -w bfs_test true scratch/xattr \ + && xattr -w bfs_test_2 true scratch/xattr_2 \ + && xattr -s -w bfs_test true scratch/xattr_link + ;; + FreeBSD) + setextattr user bfs_test true scratch/xattr \ + && setextattr user bfs_test_2 true scratch/xattr_2 \ + && setextattr -h user bfs_test true scratch/xattr_link + ;; + *) + # Linux tmpfs doesn't support the user.* namespace, so we use the security.* + # namespace, which is writable by root and readable by others + bfs_sudo setfattr -n security.bfs_test scratch/xattr \ + && bfs_sudo setfattr -n security.bfs_test_2 scratch/xattr_2 \ + && bfs_sudo setfattr -h -n security.bfs_test scratch/xattr_link + ;; + esac +} + +## Snapshot testing + +# Return value when a difference is detected +EX_DIFF=20 + +# Detect colored diff support +if ((COLOR_STDERR)) && diff --color=always /dev/null /dev/null 2>/dev/null; then + DIFF="diff --color=always" +else + DIFF="diff" +fi + +# Sort the output file +sort_output() { + sort -o "$OUT" "$OUT" +} + +# Diff against the expected output +diff_output() { + local GOLD="$TESTS/$TEST.out" + + if ((UPDATE)); then + cp "$OUT" "$GOLD" + else + $DIFF -u "$GOLD" "$OUT" >&2 + fi +} + +# Run bfs, and diff it against the expected output +bfs_diff() { + local ret=0 + invoke_bfs "$@" >"$OUT" || ret=$? + + sort_output + diff_output || exit $EX_DIFF + + return $ret +} diff --git a/tests/stddirs.sh b/tests/stddirs.sh new file mode 100644 index 0000000..e7f7246 --- /dev/null +++ b/tests/stddirs.sh @@ -0,0 +1,185 @@ +#!/hint/bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +## Standard directory trees for tests + +# Creates a simple file+directory structure for tests +make_basic() { + "$XTOUCH" -p "$1"/{a,b,c/d,e/f,g/h/,i/} + "$XTOUCH" -p "$1"/{j/foo,k/foo/bar,l/foo/bar/baz} + echo baz >"$1/l/foo/bar/baz" +} + +# Creates a file+directory structure with various permissions for tests +make_perms() { + "$XTOUCH" -p -M000 "$1/0" + "$XTOUCH" -p -M444 "$1/r" + "$XTOUCH" -p -M222 "$1/w" + "$XTOUCH" -p -M644 "$1/rw" + "$XTOUCH" -p -M555 "$1/rx" + "$XTOUCH" -p -M311 "$1/wx" + "$XTOUCH" -p -M755 "$1/rwx" +} + +# Creates a file+directory structure with various symbolic and hard links +make_links() { + "$XTOUCH" -p "$1/file" + ln -s file "$1/symlink" + ln "$1/file" "$1/hardlink" + ln -s nowhere "$1/broken" + ln -s symlink/file "$1/notdir" + "$XTOUCH" -p "$1/deeply/nested"/{dir/,file} + ln -s file "$1/deeply/nested/link" + ln -s nowhere "$1/deeply/nested/broken" + ln -s deeply/nested "$1/skip" +} + +# Creates a file+directory structure with symbolic link loops +make_loops() { + "$XTOUCH" -p "$1/file" + ln -s file "$1/symlink" + ln -s nowhere "$1/broken" + ln -s symlink/file "$1/notdir" + ln -s loop "$1/loop" + mkdir -p "$1/deeply/nested/dir" + ln -s ../../deeply "$1/deeply/nested/loop" + ln -s deeply/nested/loop/nested "$1/skip" +} + +# Creates a file+directory structure with varying timestamps +make_times() { + "$XTOUCH" -p -t "1991-12-14 00:00" "$1/a" + "$XTOUCH" -p -t "1991-12-14 00:01" "$1/b" + "$XTOUCH" -p -t "1991-12-14 00:02" "$1/c" + ln -s a "$1/l" + "$XTOUCH" -p -h -t "1991-12-14 00:03" "$1/l" + "$XTOUCH" -p -t "1991-12-14 00:04" "$1" +} + +# Creates a file+directory structure with various weird file/directory names +make_weirdnames() { + "$XTOUCH" -p "$1/-/a" + "$XTOUCH" -p "$1/(/b" + "$XTOUCH" -p "$1/(-/c" + "$XTOUCH" -p "$1/!/d" + "$XTOUCH" -p "$1/!-/e" + "$XTOUCH" -p "$1/,/f" + "$XTOUCH" -p "$1/)/g" + "$XTOUCH" -p "$1/.../h" + "$XTOUCH" -p "$1/\\/i" + "$XTOUCH" -p "$1/ /j" + "$XTOUCH" -p "$1/[/k" +} + +# Creates a very deep directory structure for testing PATH_MAX handling +make_deep() { + mkdir -p "$1" + + # $name will be 255 characters, aka _XOPEN_NAME_MAX + local name="0123456789ABCDEF" + name="${name}${name}${name}${name}" + name="${name}${name}${name}${name}" + name="${name:0:255}" + + for i in {0..9} A B C D E F; do + "$XTOUCH" -p "$1/$i/$name" + + ( + cd "$1/$i" + + # 8 * 512 == 4096 >= PATH_MAX + for _ in {1..8}; do + mv "$name" .. + mkdir -p "$name/$name" + mv "../$name" "$name/$name/" + done + ) + done +} + +# Creates a directory structure with many different types, and therefore colors +make_rainbow() { + "$XTOUCH" -p "$1/file.txt" + "$XTOUCH" -p "$1/file.dat" + "$XTOUCH" -p "$1/lower".{gz,tar,tar.gz} + "$XTOUCH" -p "$1/upper".{GZ,TAR,TAR.GZ} + "$XTOUCH" -p "$1/lu.tar.GZ" "$1/ul.TAR.gz" + ln -s file.txt "$1/link.txt" + "$XTOUCH" -p "$1/mh1" + ln "$1/mh1" "$1/mh2" + mkfifo "$1/pipe" + # TODO: block + ln -s /dev/null "$1/chardev_link" + ln -s nowhere "$1/broken" + "$MKSOCK" "$1/socket" + "$XTOUCH" -p "$1"/s{u,g,ug}id + chmod u+s "$1"/su{,g}id + chmod g+s "$1"/s{u,}gid + mkdir "$1/ow" "$1"/sticky{,_ow} + chmod o+w "$1"/*ow + chmod +t "$1"/sticky* + "$XTOUCH" -p "$1"/exec.sh + chmod +x "$1"/exec.sh + "$XTOUCH" -p "$1/"$'\e[1m/\e[0m' +} + +# Create all standard directory trees +make_stddirs() { + TMP=$(mktemp -d "${TMPDIR:-/tmp}"/bfs.XXXXXXXXXX) + + if ((CLEAN)); then + defer clean_stddirs + else + printf "Test files saved to ${BLD}%s${RST}\n" "$TMP" + fi + + chown "$(id -u):$(id -g)" "$TMP" + + make_basic "$TMP/basic" + make_perms "$TMP/perms" + make_links "$TMP/links" + make_loops "$TMP/loops" + make_times "$TMP/times" + make_weirdnames "$TMP/weirdnames" + make_deep "$TMP/deep" + make_rainbow "$TMP/rainbow" + mkdir "$TMP/scratch" +} + +# Clean whatever was left in the scratch directory +clean_scratch() { + if [ -e "$TMP/scratch" ]; then + # Try to unmount anything left behind + if ((${#SUDO[@]})) && command -v mountpoint &>/dev/null; then + for path in "$TMP/scratch"/*; do + if mountpoint -q "$path"; then + sudo umount "$path" + fi + done + fi + + # Reset any modified permissions + chmod -R +rX "$TMP/scratch" + + rm -rf "$TMP/scratch" + fi + + mkdir "$TMP/scratch" +} + +# Clean up temporary directories on exit +clean_stddirs() { + # Don't force rm to deal with long paths + for dir in "$TMP"/deep/*/*; do + if [ -d "$dir" ]; then + (cd "$dir" && rm -rf *) + fi + done + + # In case a test left anything weird in scratch/ + clean_scratch + + rm -rf "$TMP" +} diff --git a/tests/tests.sh b/tests/tests.sh index dcef28e..3890243 100755 --- a/tests/tests.sh +++ b/tests/tests.sh @@ -6,815 +6,15 @@ set -euP umask 022 -export LC_ALL=C -export TZ=UTC0 - -SAN_OPTIONS="halt_on_error=1:log_to_syslog=0" -export ASAN_OPTIONS="$SAN_OPTIONS" -export LSAN_OPTIONS="$SAN_OPTIONS" -export MSAN_OPTIONS="$SAN_OPTIONS" -export TSAN_OPTIONS="$SAN_OPTIONS" -export UBSAN_OPTIONS="$SAN_OPTIONS" - -export LS_COLORS="" -unset BFS_COLORS - -BLD=$'\e[01m' -RED=$'\e[01;31m' -GRN=$'\e[01;32m' -YLW=$'\e[01;33m' -BLU=$'\e[01;34m' -MAG=$'\e[01;35m' -CYN=$'\e[01;36m' -RST=$'\e[0m' - -function color_fd() { - [ -z "${NO_COLOR:-}" ] && [ -t "$1" ] -} - -color_fd 1 && COLOR_STDOUT=1 || COLOR_STDOUT=0 -color_fd 2 && COLOR_STDERR=1 || COLOR_STDERR=0 - -# Filter out escape sequences if necessary -function color() { - if color_fd 1; then - cat - else - sed $'s/\e\\[[^m]*m//g' - fi -} - -# printf with auto-detected color support -function cprintf() { - printf "$@" | color -} - -UNAME=$(uname) - -if [ "$UNAME" = Darwin ]; then - # ASan on macOS likes to report - # - # malloc: nano zone abandoned due to inability to preallocate reserved vm space. - # - # to syslog, which as a side effect opens a socket which might take the - # place of one of the standard streams if the process is launched with it - # closed. This environment variable avoids the message. - export MallocNanoZone=0 -fi - -if command -v capsh &>/dev/null; then - if capsh --has-p=cap_dac_override &>/dev/null || capsh --has-p=cap_dac_read_search &>/dev/null; then - if [ -n "${BFS_TRIED_DROP:-}" ]; then - color >&2 <&2 <&2 <&2 - usage >&2 - exit 1 - ;; - *) - PATTERNS+=("$arg") - ;; - esac -done - -function _realpath() { - ( - cd "$(dirname -- "$1")" - echo "$PWD/$(basename -- "$1")" - ) -} - -TESTS=$(_realpath "$(dirname -- "${BASH_SOURCE[0]}")") - -if [ "${BUILDDIR-}" ]; then - BIN=$(_realpath "$BUILDDIR/bin") -else - BIN=$(_realpath "$TESTS/../bin") -fi -MKSOCK="$BIN/tests/mksock" -XTOUCH="$BIN/tests/xtouch" - -# Try to resolve the path to $BFS before we cd, while also supporting -# --bfs="./bin/bfs -S ids" -read -a BFS <<<"${BFS:-$BIN/bfs}" -BFS[0]=$(_realpath "$(command -v "${BFS[0]}")") - -# The temporary directory that will hold our test data -TMP=$(mktemp -d "${TMPDIR:-/tmp}"/bfs.XXXXXXXXXX) -chown "$(id -u):$(id -g)" "$TMP" - -cd "$TESTS" - -if ((${#PATTERNS[@]} == 0)); then - PATTERNS=("*") -fi - -TEST_CASES=() -for TEST in {posix,common,bsd,gnu,bfs}/*.sh; do - TEST="${TEST%.sh}" - for PATTERN in "${PATTERNS[@]}"; do - if [[ $TEST == $PATTERN ]]; then - TEST_CASES+=("$TEST") - break - fi - done -done - -if ((${#TEST_CASES[@]} == 0)); then - cprintf "${RED}error:${RST} No tests matched" >&2 - cprintf " ${BLD}%s${RST}" "${PATTERNS[@]}" >&2 - cprintf ".\n\n" >&2 - usage >&2 - exit 1 -fi - -function quote() { - printf '%q' "$1" - shift - if (($# > 0)); then - printf ' %q' "$@" - fi -} - -# Run a command when this (sub)shell exits -function defer() { - trap -- KILL - if ! trap -p EXIT | grep -q pop_defers; then - DEFER_CMDS=() - DEFER_LINES=() - DEFER_FILES=() - trap pop_defers EXIT - fi - - DEFER_CMDS+=("$(quote "$@")") - - local line file - read -r line file < <(caller) - DEFER_LINES+=("$line") - DEFER_FILES+=("$file") -} - -function report_err() { - local file="${1/#*\/tests\//tests\/}" - set -- "$file" "${@:2}" - - if ((COLOR_STDERR)); then - printf "${BLD}%s:%d:${RST} ${RED}error %d:${RST}\n %s\n" "$@" >&2 - else - printf "%s:%d: error %d:\n %s\n" "$@" >&2 - fi -} - -function pop_defer() { - local cmd="${DEFER_CMDS[-1]}" - local file="${DEFER_FILES[-1]}" - local line="${DEFER_LINES[-1]}" - unset "DEFER_CMDS[-1]" - unset "DEFER_FILES[-1]" - unset "DEFER_LINES[-1]" - - local ret=0 - eval "$cmd" || ret=$? - - if ((ret != 0)); then - report_err "$file" $line $ret "defer $cmd" - fi - - return $ret -} - -function pop_defers() { - local ret=0 - - while ((${#DEFER_CMDS[@]} > 0)); do - pop_defer || ret=$? - done - - return $ret -} - -function bfs_sudo() { - if ((${#SUDO[@]})); then - "${SUDO[@]}" "$@" - else - return 1 - fi -} - -function clean_scratch() { - if [ -e "$TMP/scratch" ]; then - # Try to unmount anything left behind - if ((${#SUDO[@]})) && command -v mountpoint &>/dev/null; then - for path in "$TMP"/scratch/*; do - if mountpoint -q "$path"; then - sudo umount "$path" - fi - done - fi - - # Reset any modified permissions - chmod -R +rX "$TMP/scratch" - - rm -rf "$TMP/scratch" - fi - - mkdir "$TMP/scratch" -} - -# Clean up temporary directories on exit -function cleanup() { - # Don't force rm to deal with long paths - for dir in "$TMP"/deep/*/*; do - if [ -d "$dir" ]; then - (cd "$dir" && rm -rf *) - fi - done - - # In case a test left anything weird in scratch/ - clean_scratch - - rm -rf "$TMP" -} - -if ((CLEAN)); then - defer cleanup -else - echo "Test files saved to $TMP" -fi - -# Creates a simple file+directory structure for tests -function make_basic() { - "$XTOUCH" -p "$1"/{a,b,c/d,e/f,g/h/,i/} - "$XTOUCH" -p "$1"/{j/foo,k/foo/bar,l/foo/bar/baz} - echo baz >"$1/l/foo/bar/baz" -} -make_basic "$TMP/basic" - -# Creates a file+directory structure with various permissions for tests -function make_perms() { - "$XTOUCH" -p -M000 "$1/0" - "$XTOUCH" -p -M444 "$1/r" - "$XTOUCH" -p -M222 "$1/w" - "$XTOUCH" -p -M644 "$1/rw" - "$XTOUCH" -p -M555 "$1/rx" - "$XTOUCH" -p -M311 "$1/wx" - "$XTOUCH" -p -M755 "$1/rwx" -} -make_perms "$TMP/perms" - -# Creates a file+directory structure with various symbolic and hard links -function make_links() { - "$XTOUCH" -p "$1/file" - ln -s file "$1/symlink" - ln "$1/file" "$1/hardlink" - ln -s nowhere "$1/broken" - ln -s symlink/file "$1/notdir" - "$XTOUCH" -p "$1/deeply/nested"/{dir/,file} - ln -s file "$1/deeply/nested/link" - ln -s nowhere "$1/deeply/nested/broken" - ln -s deeply/nested "$1/skip" -} -make_links "$TMP/links" - -# Creates a file+directory structure with symbolic link loops -function make_loops() { - "$XTOUCH" -p "$1/file" - ln -s file "$1/symlink" - ln -s nowhere "$1/broken" - ln -s symlink/file "$1/notdir" - ln -s loop "$1/loop" - mkdir -p "$1/deeply/nested/dir" - ln -s ../../deeply "$1/deeply/nested/loop" - ln -s deeply/nested/loop/nested "$1/skip" -} -make_loops "$TMP/loops" - -# Creates a file+directory structure with varying timestamps -function make_times() { - "$XTOUCH" -p -t "1991-12-14 00:00" "$1/a" - "$XTOUCH" -p -t "1991-12-14 00:01" "$1/b" - "$XTOUCH" -p -t "1991-12-14 00:02" "$1/c" - ln -s a "$1/l" - "$XTOUCH" -p -h -t "1991-12-14 00:03" "$1/l" - "$XTOUCH" -p -t "1991-12-14 00:04" "$1" -} -make_times "$TMP/times" - -# Creates a file+directory structure with various weird file/directory names -function make_weirdnames() { - "$XTOUCH" -p "$1/-/a" - "$XTOUCH" -p "$1/(/b" - "$XTOUCH" -p "$1/(-/c" - "$XTOUCH" -p "$1/!/d" - "$XTOUCH" -p "$1/!-/e" - "$XTOUCH" -p "$1/,/f" - "$XTOUCH" -p "$1/)/g" - "$XTOUCH" -p "$1/.../h" - "$XTOUCH" -p "$1/\\/i" - "$XTOUCH" -p "$1/ /j" - "$XTOUCH" -p "$1/[/k" -} -make_weirdnames "$TMP/weirdnames" - -# Creates a very deep directory structure for testing PATH_MAX handling -function make_deep() { - mkdir -p "$1" - - # $name will be 255 characters, aka _XOPEN_NAME_MAX - local name="0123456789ABCDEF" - name="${name}${name}${name}${name}" - name="${name}${name}${name}${name}" - name="${name:0:255}" - - for i in {0..9} A B C D E F; do - "$XTOUCH" -p "$1/$i/$name" - - ( - cd "$1/$i" - - # 8 * 512 == 4096 >= PATH_MAX - for _ in {1..8}; do - mv "$name" .. - mkdir -p "$name/$name" - mv "../$name" "$name/$name/" - done - ) - done -} -make_deep "$TMP/deep" - -# Creates a directory structure with many different types, and therefore colors -function make_rainbow() { - "$XTOUCH" -p "$1/file.txt" - "$XTOUCH" -p "$1/file.dat" - "$XTOUCH" -p "$1/lower".{gz,tar,tar.gz} - "$XTOUCH" -p "$1/upper".{GZ,TAR,TAR.GZ} - "$XTOUCH" -p "$1/lu.tar.GZ" "$1/ul.TAR.gz" - ln -s file.txt "$1/link.txt" - "$XTOUCH" -p "$1/mh1" - ln "$1/mh1" "$1/mh2" - mkfifo "$1/pipe" - # TODO: block - ln -s /dev/null "$1/chardev_link" - ln -s nowhere "$1/broken" - "$MKSOCK" "$1/socket" - "$XTOUCH" -p "$1"/s{u,g,ug}id - chmod u+s "$1"/su{,g}id - chmod g+s "$1"/s{u,}gid - mkdir "$1/ow" "$1"/sticky{,_ow} - chmod o+w "$1"/*ow - chmod +t "$1"/sticky* - "$XTOUCH" -p "$1"/exec.sh - chmod +x "$1"/exec.sh - "$XTOUCH" -p "$1/"$'\e[1m/\e[0m' -} -make_rainbow "$TMP/rainbow" - -mkdir "$TMP/scratch" - -# Close stdin so bfs doesn't think we're interactive -exec &1 -fi - -function bfs_verbose() { - if ((!VERBOSE_COMMANDS)); then - return - fi - - { - printf "${GRN}%q${RST} " "${BFS[@]}" - - local expr_started= - for arg; do - if [[ $arg == -[A-Z]* ]]; then - printf "${CYN}%q${RST} " "$arg" - elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then - expr_started=yes - printf "${RED}%q${RST} " "$arg" - elif [[ $expr_started && $arg == [\),] ]]; then - printf "${RED}%q${RST} " "$arg" - elif [[ $arg == -?* ]]; then - expr_started=yes - printf "${BLU}%q${RST} " "$arg" - elif [ "$expr_started" ]; then - printf "${BLD}%q${RST} " "$arg" - else - printf "${MAG}%q${RST} " "$arg" - fi - done - - printf '\n' - } | color >&3 -} - -function invoke_bfs() { - bfs_verbose "$@" - - local ret=0 - "${BFS[@]}" "$@" || ret=$? - - # Allow bfs to fail, but not crash - if ((ret > 125)); then - exit "$ret" - else - return "$ret" - fi -} - -if command -v unbuffer &>/dev/null; then - UNBUFFER=unbuffer -elif command -v expect_unbuffer &>/dev/null; then - UNBUFFER=expect_unbuffer -fi - -function bfs_pty() { - test -n "${UNBUFFER:-}" || skip - - bfs_verbose "$@" - - local ret=0 - "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" || ret=$? - - if ((ret > 125)); then - exit "$ret" - else - return "$ret" - fi -} - -function check_exit() { - local expected="$1" - local actual="0" - shift - "$@" || actual="$?" - ((actual == expected)) -} - -# Detect colored diff support -if ((COLOR_STDERR)) && diff --color=always /dev/null /dev/null 2>/dev/null; then - DIFF="diff --color=always" -else - DIFF="diff" -fi - -# Return value when a difference is detected -EX_DIFF=20 -# Return value when a test is skipped -EX_SKIP=77 - -function sort_output() { - sort -o "$OUT" "$OUT" -} - -function diff_output() { - local GOLD="$TESTS/$TEST.out" - - if ((UPDATE)); then - cp "$OUT" "$GOLD" - else - $DIFF -u "$GOLD" "$OUT" >&2 - fi -} - -function bfs_diff() ( - bfs_verbose "$@" - - # Close the dup()'d stdout to make sure we have enough fd's for the process - # substitution, even with low ulimit -n - exec 3>&- - - "${BFS[@]}" "$@" | sort >"$OUT" - local status="${PIPESTATUS[0]}" - - diff_output || exit $EX_DIFF - return "$status" -) - -function skip() { - if ((VERBOSE_SKIPPED)); then - caller | { - read -r line file - cprintf "${BOL}${CYN}%s skipped!${RST} (%s)\n" "$TEST" "$(awk "NR == $line" "$file")" - } - elif ((VERBOSE_TESTS)); then - cprintf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" - fi - - exit $EX_SKIP -} - -function closefrom() { - if [ -d /proc/self/fd ]; then - local fds=/proc/self/fd - else - local fds=/dev/fd - fi - - for fd in "$fds"/*; do - if [ ! -e "$fd" ]; then - continue - fi - - local fd="${fd##*/}" - if ((fd >= $1)); then - eval "exec ${fd}<&-" - fi - done -} - -function inum() { - ls -id "$@" | awk '{ print $1 }' -} - -function set_acl() { - case "$UNAME" in - Darwin) - chmod +a "$(id -un) allow read,write" "$1" - ;; - FreeBSD) - if (($(getconf ACL_NFS4 "$1") > 0)); then - setfacl -m "u:$(id -un):rw::allow" "$1" - else - setfacl -m "u:$(id -un):rw" "$1" - fi - ;; - *) - setfacl -m "u:$(id -un):rw" "$1" - ;; - esac -} - -function make_xattrs() { - clean_scratch - - "$XTOUCH" scratch/{normal,xattr,xattr_2} - ln -s xattr scratch/link - ln -s normal scratch/xattr_link - - case "$UNAME" in - Darwin) - xattr -w bfs_test true scratch/xattr \ - && xattr -w bfs_test_2 true scratch/xattr_2 \ - && xattr -s -w bfs_test true scratch/xattr_link - ;; - FreeBSD) - setextattr user bfs_test true scratch/xattr \ - && setextattr user bfs_test_2 true scratch/xattr_2 \ - && setextattr -h user bfs_test true scratch/xattr_link - ;; - *) - # Linux tmpfs doesn't support the user.* namespace, so we use the security.* - # namespace, which is writable by root and readable by others - bfs_sudo setfattr -n security.bfs_test scratch/xattr \ - && bfs_sudo setfattr -n security.bfs_test_2 scratch/xattr_2 \ - && bfs_sudo setfattr -h -n security.bfs_test scratch/xattr_link - ;; - esac -} - -cd "$TMP" -set +e - -BOL='\n' -EOL='\n' - -function update_eol() { - # Bash gets $COLUMNS from stderr, so if it's redirected use tput instead - local cols="${COLUMNS-}" - if [ -z "$cols" ]; then - cols=$(tput cols) - fi - - # Put the cursor at the last column, then write a space so the next - # character will wrap - EOL="\\033[${cols}G " -} - -if ((VERBOSE_TESTS)); then - BOL='' -elif ((COLOR_STDOUT)); then - BOL='\r\033[K' - - # Workaround for bash 4: checkwinsize is off by default. We can turn it on, - # but we also have to explicitly trigger a foreground job to finish so that - # it will update the window size before we use $COLUMNS - shopt -s checkwinsize - (:) - - update_eol - trap update_eol WINCH -fi - -function callers() { - local frame=0 - while caller $frame; do - ((++frame)) - done -} - -function debug_err() { - local ret=$? line func file - callers | while read -r line func file; do - if [ "$func" = source ]; then - local cmd="$(awk "NR == $line" "$file" 2>/dev/null)" || : - report_err "$file" $line $ret "$cmd" - break - fi - done -} - -function run_test() ( - set -eE - trap debug_err ERR - source "$@" -) - -passed=0 -failed=0 -skipped=0 - -if ((COLOR_STDOUT || VERBOSE_TESTS)); then - TEST_FMT="${BOL}${YLW}%s${RST}${EOL}" -else - TEST_FMT="." -fi - -for TEST in "${TEST_CASES[@]}"; do - printf "$TEST_FMT" "$TEST" - - OUT="$TMP/$TEST.out" - mkdir -p "${OUT%/*}" - - if ((VERBOSE_ERRORS)); then - run_test "$TESTS/$TEST.sh" - else - run_test "$TESTS/$TEST.sh" 2>"$TMP/$TEST.err" - fi - status=$? - - if ((status == 0)); then - ((++passed)) - elif ((status == EX_SKIP)); then - ((++skipped)) - else - ((++failed)) - ((VERBOSE_ERRORS)) || cat "$TMP/$TEST.err" >&2 - cprintf "${BOL}${RED}%s failed!${RST}\n" "$TEST" - ((STOP)) && break - fi -done - -printf "${BOL}" - -if ((passed > 0)); then - cprintf "${GRN}tests passed: %d${RST}\n" "$passed" -fi -if ((skipped > 0)); then - cprintf "${CYN}tests skipped: %s${RST}\n" "$skipped" -fi -if ((failed > 0)); then - cprintf "${RED}tests failed: %s${RST}\n" "$failed" - exit 1 -fi +TESTS="$(dirname -- "${BASH_SOURCE[0]}")" +. "$TESTS/util.sh" +. "$TESTS/color.sh" +. "$TESTS/stddirs.sh" +. "$TESTS/getopts.sh" +. "$TESTS/run.sh" + +stdenv +drop_root "$@" +parse_args "$@" +make_stddirs +run_tests diff --git a/tests/util.sh b/tests/util.sh new file mode 100644 index 0000000..5131522 --- /dev/null +++ b/tests/util.sh @@ -0,0 +1,189 @@ +#!/hint/bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +## Utility functions + +# Portable realpath(1) +_realpath() ( + cd "$(dirname -- "$1")" + echo "$PWD/$(basename -- "$1")" +) + +# Globals +TESTS=$(_realpath "$TESTS") +if [ "${BUILDDIR-}" ]; then + BIN=$(_realpath "$BUILDDIR/bin") +else + BIN=$(_realpath "$TESTS/../bin") +fi +MKSOCK="$BIN/tests/mksock" +XTOUCH="$BIN/tests/xtouch" +UNAME=$(uname) + +# Standardize the environment +stdenv() { + export LC_ALL=C + export TZ=UTC0 + + local SAN_OPTIONS="halt_on_error=1:log_to_syslog=0" + export ASAN_OPTIONS="$SAN_OPTIONS" + export LSAN_OPTIONS="$SAN_OPTIONS" + export MSAN_OPTIONS="$SAN_OPTIONS" + export TSAN_OPTIONS="$SAN_OPTIONS" + export UBSAN_OPTIONS="$SAN_OPTIONS" + + export LS_COLORS="" + unset BFS_COLORS + + if [ "$UNAME" = Darwin ]; then + # ASan on macOS likes to report + # + # malloc: nano zone abandoned due to inability to preallocate reserved vm space. + # + # to syslog, which as a side effect opens a socket which might take the + # place of one of the standard streams if the process is launched with + # it closed. This environment variable avoids the message. + export MallocNanoZone=0 + fi + + # Close non-standard inherited fds + if [ -d /proc/self/fd ]; then + local fds=/proc/self/fd + else + local fds=/dev/fd + fi + + for fd in "$fds"/*; do + if [ ! -e "$fd" ]; then + continue + fi + + local fd="${fd##*/}" + if ((fd > 2)); then + eval "exec ${fd}<&-" + fi + done + + # Close stdin so bfs doesn't think we're interactive + # dup() the standard fds for logging even when redirected + exec &1 4>&2 +} + +# Drop root priviliges or bail +drop_root() { + if command -v capsh &>/dev/null; then + if capsh --has-p=cap_dac_override &>/dev/null || capsh --has-p=cap_dac_read_search &>/dev/null; then + if [ -n "${BFS_TRIED_DROP:-}" ]; then + color >&2 <&2 <&2 < 0)); then + printf ' %q' "$@" + fi +} + +# Run a command when this (sub)shell exits +defer() { + # Refresh trap state before trap -p + # See https://unix.stackexchange.com/a/556888/56202 + trap -- KILL + + # Check if the EXIT trap is already set + if ! trap -p EXIT | grep -q pop_defers; then + DEFER_CMDS=() + DEFER_LINES=() + DEFER_FILES=() + trap pop_defers EXIT + fi + + DEFER_CMDS+=("$(quote "$@")") + + local line file + read -r line file < <(caller) + DEFER_LINES+=("$line") + DEFER_FILES+=("$file") +} + +# Pop a single command from the defer stack and run it +pop_defer() { + local cmd="${DEFER_CMDS[-1]}" + local file="${DEFER_FILES[-1]}" + local line="${DEFER_LINES[-1]}" + unset "DEFER_CMDS[-1]" + unset "DEFER_FILES[-1]" + unset "DEFER_LINES[-1]" + + local ret=0 + eval "$cmd" || ret=$? + + if ((ret != 0)); then + debug "$file" $line "${RED}error $ret${RST}" "defer $cmd" >&4 + fi + + return $ret +} + +# Run all deferred commands +pop_defers() { + local ret=0 + + while ((${#DEFER_CMDS[@]} > 0)); do + pop_defer || ret=$? + done + + return $ret +} -- cgit v1.2.3 From 13053c43ee8f5a2a5f7a66258d047e1b68203da3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 19 Oct 2023 17:05:36 -0400 Subject: tests: Don't unset array[-1] This was only added in Bash 4.3 which is too new for macOS. --- tests/util.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/tests/util.sh b/tests/util.sh index 5131522..efc24b0 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -160,12 +160,13 @@ defer() { # Pop a single command from the defer stack and run it pop_defer() { - local cmd="${DEFER_CMDS[-1]}" - local file="${DEFER_FILES[-1]}" - local line="${DEFER_LINES[-1]}" - unset "DEFER_CMDS[-1]" - unset "DEFER_FILES[-1]" - unset "DEFER_LINES[-1]" + local i=$((${#DEFER_CMDS[@]} - 1)) + local cmd="${DEFER_CMDS[$i]}" + local file="${DEFER_FILES[$i]}" + local line="${DEFER_LINES[$i]}" + unset "DEFER_CMDS[$i]" + unset "DEFER_FILES[$i]" + unset "DEFER_LINES[$i]" local ret=0 eval "$cmd" || ret=$? -- cgit v1.2.3 From 156602895250813a8a5a0850f3fec5ed0c774796 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 20 Oct 2023 11:18:15 -0400 Subject: tests/xtouch: Recover from ENAMETOOLONG --- tests/xtouch.c | 129 +++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 84 insertions(+), 45 deletions(-) diff --git a/tests/xtouch.c b/tests/xtouch.c index a4c4d40..260a3a3 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -37,89 +37,128 @@ struct args { mode_t pmode; }; -/** Compute flags for fstatat()/utimensat(). */ -static int at_flags(const struct args *args) { - if (args->flags & NO_FOLLOW) { - return AT_SYMLINK_NOFOLLOW; - } else { - return 0; +/** Open (and maybe create) a single directory. */ +static int open_dir(const struct args *args, int dfd, const char *path) { + int ret = openat(dfd, path, O_SEARCH | O_DIRECTORY); + + if (ret < 0 && errno == ENOENT && (args->flags & CREATE_PARENTS)) { + if (mkdirat(dfd, path, args->pmode) == 0 || errno == EEXIST) { + ret = openat(dfd, path, O_SEARCH | O_DIRECTORY); + } } + + return ret; } -/** Create any parent directories of the given path. */ -static int mkdirs(const char *path, mode_t mode) { - int ret = -1; - char *dir = xdirname(path); +/** Open (and maybe create) the parent directory of the path. */ +static int open_parent(const struct args *args, const char **path) { + size_t max = xbaseoff(*path); + if (max == 0) { + return AT_FDCWD; + } + + char *dir = strndup(*path, max); if (!dir) { - goto err; + return -1; } - if (strcmp(dir, ".") == 0) { + // Optimistically try the whole path first + int dfd = open_dir(args, AT_FDCWD, dir); + if (dfd >= 0) { goto done; } - // Optimistically try the immediate parent first - if (mkdir(dir, mode) == 0 || errno == EEXIST) { - goto done; + switch (errno) { + case ENAMETOOLONG: + break; + case ENOENT: + if (args->flags & CREATE_PARENTS) { + break; + } else { + goto err; + } + default: + goto err; } - // Create the parents one-at-a-time - char *cur = dir + strspn(dir, "/"); + // Open the parents one-at-a-time + dfd = AT_FDCWD; + char *cur = dir; while (*cur) { - cur += strcspn(cur, "/"); - char *next = cur + strspn(cur, "/"); + char *next = cur; + next += strcspn(next, "/"); + next += strspn(next, "/"); + + char c = *next; + *next = '\0'; - char c = *cur; - *cur = '\0'; - if (mkdir(dir, mode) != 0 && errno != EEXIST) { + int parent = dfd; + dfd = open_dir(args, parent, cur); + if (parent >= 0) { + close_quietly(parent); + } + if (dfd < 0) { goto err; } - *cur = c; + + *next = c; cur = next; } done: - ret = 0; + *path += max; err: free(dir); - return ret; + return dfd; +} + +/** Compute flags for fstatat()/utimensat(). */ +static int at_flags(const struct args *args) { + if (args->flags & NO_FOLLOW) { + return AT_SYMLINK_NOFOLLOW; + } else { + return 0; + } } /** Touch one path. */ static int xtouch(const struct args *args, const char *path) { - int ret = utimensat(AT_FDCWD, path, args->times, at_flags(args)); + int dfd = open_parent(args, &path); + if (dfd < 0 && dfd != AT_FDCWD) { + return -1; + } + + int ret = utimensat(dfd, path, args->times, at_flags(args)); if (ret == 0 || errno != ENOENT) { - return ret; + goto done; } if (args->flags & NO_CREATE) { - return 0; - } else if (args->flags & CREATE_PARENTS) { - if (mkdirs(path, args->pmode) != 0) { - return -1; - } + ret = 0; + goto done; } size_t len = strlen(path); if (len > 0 && path[len - 1] == '/') { - if (mkdir(path, args->dmode) != 0) { - return -1; + if (mkdirat(dfd, path, args->dmode) == 0) { + ret = utimensat(dfd, path, args->times, at_flags(args)); } - - return utimensat(AT_FDCWD, path, args->times, at_flags(args)); } else { - int fd = open(path, O_WRONLY | O_CREAT, args->fmode); - if (fd < 0) { - return -1; - } - - if (futimens(fd, args->times) != 0) { - close_quietly(fd); - return -1; + int fd = openat(dfd, path, O_WRONLY | O_CREAT, args->fmode); + if (fd >= 0) { + if (futimens(fd, args->times) == 0) { + ret = xclose(fd); + } else { + close_quietly(fd); + } } + } - return xclose(fd); +done: + if (dfd >= 0) { + close_quietly(dfd); } + return ret; } int main(int argc, char *argv[]) { -- cgit v1.2.3 From 962d715ce2dee7a017dacca3b787ade6d33cd1c6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 20 Oct 2023 11:18:25 -0400 Subject: tests: Use xtouch to create the whole deep tree at once --- tests/stddirs.sh | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/tests/stddirs.sh b/tests/stddirs.sh index e7f7246..acc23dc 100644 --- a/tests/stddirs.sh +++ b/tests/stddirs.sh @@ -79,24 +79,15 @@ make_deep() { # $name will be 255 characters, aka _XOPEN_NAME_MAX local name="0123456789ABCDEF" - name="${name}${name}${name}${name}" - name="${name}${name}${name}${name}" + name="$name$name$name$name" + name="$name$name$name$name" name="${name:0:255}" - for i in {0..9} A B C D E F; do - "$XTOUCH" -p "$1/$i/$name" + # 4 * 4 * 256 == 4096 >= PATH_MAX + local path="$name/$name/$name/$name" + path="$path/$path/$path/$path" - ( - cd "$1/$i" - - # 8 * 512 == 4096 >= PATH_MAX - for _ in {1..8}; do - mv "$name" .. - mkdir -p "$name/$name" - mv "../$name" "$name/$name/" - done - ) - done + "$XTOUCH" -p "$1"/{{0..9},A,B,C,D,E,F}/"$path/$name" } # Creates a directory structure with many different types, and therefore colors -- cgit v1.2.3 From 60366e4583a1d148dd5f8171c9148ebb98890478 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 20 Oct 2023 15:52:45 -0400 Subject: tests/color: Remove some useless cats --- tests/color.sh | 12 +++------- tests/getopts.sh | 10 ++++---- tests/run.sh | 73 +++++++++++++++++++++++++++++--------------------------- tests/util.sh | 8 +++---- 4 files changed, 50 insertions(+), 53 deletions(-) diff --git a/tests/color.sh b/tests/color.sh index 0d6ef68..805d2b8 100644 --- a/tests/color.sh +++ b/tests/color.sh @@ -24,20 +24,14 @@ color_fd() { color_fd 1 && COLOR_STDOUT=1 || COLOR_STDOUT=0 color_fd 2 && COLOR_STDERR=1 || COLOR_STDERR=0 -# Save these in case the tests unset PATH -CAT=$(command -v cat) +# Save this in case the tests unset PATH SED=$(command -v sed) # Filter out escape sequences if necessary color() { if color_fd 1; then - "$CAT" + "$@" else - "$SED" $'s/\e\\[[^m]*m//g' + "$@" | "$SED" $'s/\e\\[[^m]*m//g' fi } - -# printf with auto-detected color support -cprintf() { - printf "$@" | color -} diff --git a/tests/getopts.sh b/tests/getopts.sh index 6616a4a..7d3ef4b 100644 --- a/tests/getopts.sh +++ b/tests/getopts.sh @@ -8,7 +8,7 @@ # Print usage information usage() { local pad=$(printf "%*s" ${#0} "") - color <&2 + color printf "${RED}error:${RST} Unrecognized option '%s'.\n\n" "$arg" >&2 usage >&2 exit 1 ;; @@ -149,9 +149,9 @@ parse_args() { done if ((${#TEST_CASES[@]} == 0)); then - cprintf "${RED}error:${RST} No tests matched" >&2 - cprintf " ${BLD}%s${RST}" "${PATTERNS[@]}" >&2 - cprintf ".\n\n" >&2 + color printf "${RED}error:${RST} No tests matched" >&2 + color printf " ${BLD}%s${RST}" "${PATTERNS[@]}" >&2 + color printf ".\n\n" >&2 usage >&2 exit 1 fi diff --git a/tests/run.sh b/tests/run.sh index 70c9cc2..5fcccad 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -93,7 +93,7 @@ run_tests() { else ((++failed)) ((VERBOSE_ERRORS)) || cat "$TMP/$TEST.err" >&2 - cprintf "${BOL}${RED}%s failed!${RST}\n" "$TEST" + color printf "${BOL}${RED}%s failed!${RST}\n" "$TEST" ((STOP)) && break fi done @@ -101,13 +101,13 @@ run_tests() { printf "${BOL}" if ((passed > 0)); then - cprintf "${GRN}tests passed: %d${RST}\n" "$passed" + color printf "${GRN}tests passed: %d${RST}\n" "$passed" fi if ((skipped > 0)); then - cprintf "${CYN}tests skipped: %s${RST}\n" "$skipped" + color printf "${CYN}tests skipped: %s${RST}\n" "$skipped" fi if ((failed > 0)); then - cprintf "${RED}tests failed: %s${RST}\n" "$failed" + color printf "${RED}tests failed: %s${RST}\n" "$failed" exit 1 fi } @@ -126,7 +126,7 @@ skip() { debug "$file" $line "${CYN}$TEST skipped!${RST}" "$(awk "NR == $line" "$file")" >&3 } elif ((VERBOSE_TESTS)); then - cprintf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" + color printf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" fi exit $EX_SKIP @@ -175,39 +175,42 @@ set_acl() { } # Print a bfs invocation for --verbose=commands -bfs_verbose() ( - if ((!VERBOSE_COMMANDS)); then - return +bfs_verbose() { + if ((VERBOSE_COMMANDS)); then + ( + # Close some fds to make room for the pipe, + # even with extremely low ulimit -n + exec >&- 4>&- + exec >&3 3>&- + color bfs_verbose_impl "$@" + ) fi +} - # Free up an fd for the pipe - exec 4>&- - - { - printf "${GRN}%q${RST} " "${BFS[@]}" - - local expr_started= - for arg; do - if [[ $arg == -[A-Z]* ]]; then - printf "${CYN}%q${RST} " "$arg" - elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then - expr_started=yes - printf "${RED}%q${RST} " "$arg" - elif [[ $expr_started && $arg == [\),] ]]; then - printf "${RED}%q${RST} " "$arg" - elif [[ $arg == -?* ]]; then - expr_started=yes - printf "${BLU}%q${RST} " "$arg" - elif [ "$expr_started" ]; then - printf "${BLD}%q${RST} " "$arg" - else - printf "${MAG}%q${RST} " "$arg" - fi - done +bfs_verbose_impl() { + printf "${GRN}%q${RST} " "${BFS[@]}" + + local expr_started= + for arg; do + if [[ $arg == -[A-Z]* ]]; then + printf "${CYN}%q${RST} " "$arg" + elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then + expr_started=yes + printf "${RED}%q${RST} " "$arg" + elif [[ $expr_started && $arg == [\),] ]]; then + printf "${RED}%q${RST} " "$arg" + elif [[ $arg == -?* ]]; then + expr_started=yes + printf "${BLU}%q${RST} " "$arg" + elif [ "$expr_started" ]; then + printf "${BLD}%q${RST} " "$arg" + else + printf "${MAG}%q${RST} " "$arg" + fi + done - printf '\n' - } | color >&3 -) + printf '\n' +} # Run the bfs we're testing invoke_bfs() { diff --git a/tests/util.sh b/tests/util.sh index efc24b0..31a7b6c 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -76,14 +76,14 @@ drop_root() { if command -v capsh &>/dev/null; then if capsh --has-p=cap_dac_override &>/dev/null || capsh --has-p=cap_dac_read_search &>/dev/null; then if [ -n "${BFS_TRIED_DROP:-}" ]; then - color >&2 <&2 <&2 <&2 <&2 <&2 < Date: Fri, 20 Oct 2023 15:56:55 -0400 Subject: tests: Fix column detection with redirected stderr --- tests/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run.sh b/tests/run.sh index 5fcccad..7b7fe24 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -14,7 +14,7 @@ update_eol() { # Bash gets $COLUMNS from stderr, so if it's redirected use tput instead local cols="${COLUMNS-}" if [ -z "$cols" ]; then - cols=$(tput cols) + cols=$(tput cols 2>/dev/tty) fi # Put the cursor at the last column, then write a space so the next -- cgit v1.2.3 From a2af2746c4686201e2e0796fbdfa115d08727b86 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 22 Oct 2023 16:41:41 -0400 Subject: tests: Use test-specific scratch directories --- tests/bfs/L_capable.out | 4 +- tests/bfs/L_capable.sh | 13 +++--- tests/bfs/capable.out | 2 +- tests/bfs/capable.sh | 13 +++--- tests/bfs/color_ls.sh | 2 +- tests/bfs/exec_flush_fprint.sh | 3 +- tests/bsd/L_acl.out | 4 +- tests/bsd/L_acl.sh | 12 +++--- tests/bsd/L_xattr.out | 6 +-- tests/bsd/L_xattr.sh | 4 +- tests/bsd/L_xattrname.out | 4 +- tests/bsd/L_xattrname.sh | 6 +-- tests/bsd/acl.out | 2 +- tests/bsd/acl.sh | 12 +++--- tests/bsd/flags.out | 2 +- tests/bsd/flags.sh | 10 ++--- tests/bsd/rm.out | 2 +- tests/bsd/rm.sh | 10 ++--- tests/bsd/type_w.out | 72 ++++++++++++++++----------------- tests/bsd/type_w.sh | 36 ++++++++--------- tests/bsd/xattr.out | 6 +-- tests/bsd/xattr.sh | 4 +- tests/bsd/xattrname.out | 4 +- tests/bsd/xattrname.sh | 6 +-- tests/common/L_ls.sh | 3 +- tests/common/L_mount.out | 10 ++--- tests/common/L_mount.sh | 16 ++++---- tests/common/delete.out | 2 +- tests/common/delete.sh | 11 ++--- tests/common/delete_many.out | 2 +- tests/common/delete_many.sh | 10 ++--- tests/common/execdir_ulimit.out | 2 +- tests/common/execdir_ulimit.sh | 8 ++-- tests/common/inum_bind_mount.out | 4 +- tests/common/inum_bind_mount.sh | 10 ++--- tests/common/inum_mount.out | 2 +- tests/common/inum_mount.sh | 10 ++--- tests/common/ls.sh | 3 +- tests/common/mount.out | 8 ++-- tests/common/mount.sh | 12 +++--- tests/gnu/L_delete.out | 4 +- tests/gnu/L_delete.sh | 11 +++-- tests/gnu/files0_from_file.sh | 6 +-- tests/gnu/fls.sh | 3 +- tests/gnu/fls_nonexistent.sh | 2 +- tests/gnu/fprint0_nonexistent.sh | 2 +- tests/gnu/fprint_duplicate.sh | 10 ++--- tests/gnu/fprint_nonexistent.sh | 2 +- tests/gnu/fprintf_nonexistent.sh | 2 +- tests/gnu/fstype_stacked.out | 2 +- tests/gnu/fstype_stacked.sh | 14 +++---- tests/gnu/fstype_umount.sh | 14 +++---- tests/gnu/ignore_readdir_race.sh | 6 +-- tests/gnu/ignore_readdir_race_notdir.sh | 6 +-- tests/gnu/inum_automount.out | 2 +- tests/gnu/inum_automount.sh | 14 +++---- tests/gnu/printf_Y_error.out | 6 +-- tests/gnu/printf_Y_error.sh | 12 +++--- tests/gnu/regex_invalid_utf8.out | 2 +- tests/gnu/regex_invalid_utf8.sh | 10 ++--- tests/gnu/xtype_bind_mount.out | 4 +- tests/gnu/xtype_bind_mount.sh | 12 +++--- tests/posix/L_xdev.out | 10 ++--- tests/posix/L_xdev.sh | 16 ++++---- tests/posix/depth_error.out | 4 +- tests/posix/depth_error.sh | 10 ++--- tests/posix/overlayfs.out | 10 ++--- tests/posix/overlayfs.sh | 14 +++---- tests/posix/readdir_error.sh | 18 ++++----- tests/posix/type_bind_mount.out | 2 +- tests/posix/type_bind_mount.sh | 10 ++--- tests/posix/unionfs.out | 20 ++++----- tests/posix/unionfs.sh | 13 +++--- tests/posix/xdev.out | 8 ++-- tests/posix/xdev.sh | 12 +++--- tests/run.sh | 28 ++++++------- tests/stddirs.sh | 25 ------------ 77 files changed, 330 insertions(+), 368 deletions(-) diff --git a/tests/bfs/L_capable.out b/tests/bfs/L_capable.out index e5ba3c7..0810d4a 100644 --- a/tests/bfs/L_capable.out +++ b/tests/bfs/L_capable.out @@ -1,2 +1,2 @@ -scratch/capable -scratch/link +./capable +./link diff --git a/tests/bfs/L_capable.sh b/tests/bfs/L_capable.sh index 232d6ac..97c404f 100644 --- a/tests/bfs/L_capable.sh +++ b/tests/bfs/L_capable.sh @@ -1,11 +1,10 @@ test "$UNAME" = "Linux" || skip +invoke_bfs . -quit -capable || skip -clean_scratch +cd "$TEST" -invoke_bfs scratch -quit -capable || skip +"$XTOUCH" normal capable +bfs_sudo setcap all+ep capable || skip +ln -s capable link -"$XTOUCH" scratch/{normal,capable} -bfs_sudo setcap all+ep scratch/capable || skip -ln -s capable scratch/link - -bfs_diff -L scratch -capable +bfs_diff -L . -capable diff --git a/tests/bfs/capable.out b/tests/bfs/capable.out index 78b5bd9..ac7b5ce 100644 --- a/tests/bfs/capable.out +++ b/tests/bfs/capable.out @@ -1 +1 @@ -scratch/capable +./capable diff --git a/tests/bfs/capable.sh b/tests/bfs/capable.sh index e5cad63..35bb0b4 100644 --- a/tests/bfs/capable.sh +++ b/tests/bfs/capable.sh @@ -1,11 +1,10 @@ test "$UNAME" = "Linux" || skip +invoke_bfs . -quit -capable || skip -clean_scratch +cd "$TEST" -invoke_bfs scratch -quit -capable || skip +"$XTOUCH" normal capable +bfs_sudo setcap all+ep capable || skip +ln -s capable link -"$XTOUCH" scratch/{normal,capable} -bfs_sudo setcap all+ep scratch/capable || skip -ln -s capable scratch/link - -bfs_diff scratch -capable +bfs_diff . -capable diff --git a/tests/bfs/color_ls.sh b/tests/bfs/color_ls.sh index f2d3c72..f1cc216 100644 --- a/tests/bfs/color_ls.sh +++ b/tests/bfs/color_ls.sh @@ -1,4 +1,4 @@ -clean_scratch +cd "$TEST" "$XTOUCH" -p scratch/foo/bar/baz ln -s foo/bar/baz scratch/link ln -s foo/bar/nowhere scratch/broken diff --git a/tests/bfs/exec_flush_fprint.sh b/tests/bfs/exec_flush_fprint.sh index bf6b62f..a862773 100644 --- a/tests/bfs/exec_flush_fprint.sh +++ b/tests/bfs/exec_flush_fprint.sh @@ -1,3 +1,2 @@ # Even non-stdstreams should be flushed -clean_scratch -bfs_diff basic/a -fprint scratch/foo -exec cat scratch/foo \; +bfs_diff basic/a -fprint "$OUT.f" -exec cat "$OUT.f" \; diff --git a/tests/bsd/L_acl.out b/tests/bsd/L_acl.out index 1dae00a..dd89800 100644 --- a/tests/bsd/L_acl.out +++ b/tests/bsd/L_acl.out @@ -1,2 +1,2 @@ -scratch/acl -scratch/link +./acl +./link diff --git a/tests/bsd/L_acl.sh b/tests/bsd/L_acl.sh index db97013..a3fcbc8 100644 --- a/tests/bsd/L_acl.sh +++ b/tests/bsd/L_acl.sh @@ -1,9 +1,9 @@ -clean_scratch +cd "$TEST" -invoke_bfs scratch -quit -acl || skip +invoke_bfs . -quit -acl || skip -"$XTOUCH" scratch/{normal,acl} -set_acl scratch/acl || skip -ln -s acl scratch/link +"$XTOUCH" normal acl +set_acl acl || skip +ln -s acl link -bfs_diff -L scratch -acl +bfs_diff -L . -acl diff --git a/tests/bsd/L_xattr.out b/tests/bsd/L_xattr.out index 12fac95..21eb50f 100644 --- a/tests/bsd/L_xattr.out +++ b/tests/bsd/L_xattr.out @@ -1,3 +1,3 @@ -scratch/link -scratch/xattr -scratch/xattr_2 +./link +./xattr +./xattr_2 diff --git a/tests/bsd/L_xattr.sh b/tests/bsd/L_xattr.sh index 1f61c78..f8b56d8 100644 --- a/tests/bsd/L_xattr.sh +++ b/tests/bsd/L_xattr.sh @@ -1,3 +1,3 @@ -invoke_bfs scratch -quit -xattr || skip +invoke_bfs . -quit -xattr || skip make_xattrs || skip -bfs_diff -L scratch -xattr +bfs_diff -L . -xattr diff --git a/tests/bsd/L_xattrname.out b/tests/bsd/L_xattrname.out index 4dc4836..9e4c172 100644 --- a/tests/bsd/L_xattrname.out +++ b/tests/bsd/L_xattrname.out @@ -1,2 +1,2 @@ -scratch/link -scratch/xattr +./link +./xattr diff --git a/tests/bsd/L_xattrname.sh b/tests/bsd/L_xattrname.sh index 3b2006b..8108d57 100644 --- a/tests/bsd/L_xattrname.sh +++ b/tests/bsd/L_xattrname.sh @@ -1,11 +1,11 @@ -invoke_bfs scratch -quit -xattr || skip +invoke_bfs . -quit -xattr || skip make_xattrs || skip case "$UNAME" in Darwin|FreeBSD) - bfs_diff -L scratch -xattrname bfs_test + bfs_diff -L . -xattrname bfs_test ;; *) - bfs_diff -L scratch -xattrname security.bfs_test + bfs_diff -L . -xattrname security.bfs_test ;; esac diff --git a/tests/bsd/acl.out b/tests/bsd/acl.out index ddf8446..92e2f67 100644 --- a/tests/bsd/acl.out +++ b/tests/bsd/acl.out @@ -1 +1 @@ -scratch/acl +./acl diff --git a/tests/bsd/acl.sh b/tests/bsd/acl.sh index c044398..a13c75f 100644 --- a/tests/bsd/acl.sh +++ b/tests/bsd/acl.sh @@ -1,9 +1,9 @@ -clean_scratch +cd "$TEST" -invoke_bfs scratch -quit -acl || skip +invoke_bfs . -quit -acl || skip -"$XTOUCH" scratch/{normal,acl} -set_acl scratch/acl || skip -ln -s acl scratch/link +"$XTOUCH" normal acl +set_acl acl || skip +ln -s acl link -bfs_diff scratch -acl +bfs_diff . -acl diff --git a/tests/bsd/flags.out b/tests/bsd/flags.out index 11998ed..3216ff5 100644 --- a/tests/bsd/flags.out +++ b/tests/bsd/flags.out @@ -1 +1 @@ -scratch/bar +./bar diff --git a/tests/bsd/flags.sh b/tests/bsd/flags.sh index 949a7d3..eb9bc22 100644 --- a/tests/bsd/flags.sh +++ b/tests/bsd/flags.sh @@ -1,8 +1,8 @@ -invoke_bfs scratch -quit -flags offline || skip +invoke_bfs . -quit -flags offline || skip -clean_scratch +cd "$TEST" -"$XTOUCH" scratch/{foo,bar} -chflags offline scratch/bar || skip +"$XTOUCH" foo bar +chflags offline bar || skip -bfs_diff scratch -flags -offline,nohidden +bfs_diff . -flags -offline,nohidden diff --git a/tests/bsd/rm.out b/tests/bsd/rm.out index fb188b9..9c558e3 100644 --- a/tests/bsd/rm.out +++ b/tests/bsd/rm.out @@ -1 +1 @@ -scratch +. diff --git a/tests/bsd/rm.sh b/tests/bsd/rm.sh index 9ee2b0a..595d514 100644 --- a/tests/bsd/rm.sh +++ b/tests/bsd/rm.sh @@ -1,6 +1,4 @@ -clean_scratch -"$XTOUCH" -p scratch/foo/bar/baz - -(cd scratch && invoke_bfs . -rm) - -bfs_diff scratch +cd "$TEST" +"$XTOUCH" -p foo/bar/baz +invoke_bfs . -rm +bfs_diff . diff --git a/tests/bsd/type_w.out b/tests/bsd/type_w.out index d383f69..a20a4f3 100644 --- a/tests/bsd/type_w.out +++ b/tests/bsd/type_w.out @@ -1,38 +1,34 @@ -1: -rw-r--r-- scratch/mnt/lower/bar -1: -rw-r--r-- scratch/mnt/lower/baz -1: -rw-r--r-- scratch/mnt/lower/foo -1: -rw-r--r-- scratch/mnt/upper/baz/qux -1: -rw-r--r-- scratch/mnt/upper/foo -1: drwxr-xr-x scratch/mnt -1: drwxr-xr-x scratch/mnt/lower -1: drwxr-xr-x scratch/mnt/upper -1: drwxr-xr-x scratch/mnt/upper/baz -2: w--------- scratch/mnt/upper/bar -3: -rw-r--r-- scratch/mnt/lower/bar -3: -rw-r--r-- scratch/mnt/lower/baz -3: -rw-r--r-- scratch/mnt/lower/foo -3: -rw-r--r-- scratch/mnt/upper/baz/qux -3: -rw-r--r-- scratch/mnt/upper/foo -3: drwxr-xr-x scratch/mnt -3: drwxr-xr-x scratch/mnt/lower -3: drwxr-xr-x scratch/mnt/upper -3: drwxr-xr-x scratch/mnt/upper/baz -3: w--------- scratch/mnt/upper/bar -4: -rw-r--r-- scratch/mnt/lower/bar -4: -rw-r--r-- scratch/mnt/lower/baz -4: -rw-r--r-- scratch/mnt/lower/foo -4: -rw-r--r-- scratch/mnt/upper/baz/qux -4: drwxr-xr-x scratch/mnt -4: drwxr-xr-x scratch/mnt/lower -4: drwxr-xr-x scratch/mnt/upper -4: drwxr-xr-x scratch/mnt/upper/baz -5: w--------- scratch/mnt/upper/bar -6: -rw-r--r-- scratch/mnt/lower/bar -6: -rw-r--r-- scratch/mnt/lower/baz -6: -rw-r--r-- scratch/mnt/lower/foo -6: -rw-r--r-- scratch/mnt/upper/baz/qux -6: drwxr-xr-x scratch/mnt -6: drwxr-xr-x scratch/mnt/lower -6: drwxr-xr-x scratch/mnt/upper -6: drwxr-xr-x scratch/mnt/upper/baz -6: w--------- scratch/mnt/upper/bar +1: -rw-r--r-- mnt/lower/bar +1: -rw-r--r-- mnt/lower/baz +1: -rw-r--r-- mnt/lower/foo +1: -rw-r--r-- mnt/upper/baz/qux +1: -rw-r--r-- mnt/upper/foo +1: drwxr-xr-x mnt/lower +1: drwxr-xr-x mnt/upper +1: drwxr-xr-x mnt/upper/baz +2: w--------- mnt/upper/bar +3: -rw-r--r-- mnt/lower/bar +3: -rw-r--r-- mnt/lower/baz +3: -rw-r--r-- mnt/lower/foo +3: -rw-r--r-- mnt/upper/baz/qux +3: -rw-r--r-- mnt/upper/foo +3: drwxr-xr-x mnt/lower +3: drwxr-xr-x mnt/upper +3: drwxr-xr-x mnt/upper/baz +3: w--------- mnt/upper/bar +4: -rw-r--r-- mnt/lower/bar +4: -rw-r--r-- mnt/lower/baz +4: -rw-r--r-- mnt/lower/foo +4: -rw-r--r-- mnt/upper/baz/qux +4: drwxr-xr-x mnt/lower +4: drwxr-xr-x mnt/upper +4: drwxr-xr-x mnt/upper/baz +5: w--------- mnt/upper/bar +6: -rw-r--r-- mnt/lower/bar +6: -rw-r--r-- mnt/lower/baz +6: -rw-r--r-- mnt/lower/foo +6: -rw-r--r-- mnt/upper/baz/qux +6: drwxr-xr-x mnt/lower +6: drwxr-xr-x mnt/upper +6: drwxr-xr-x mnt/upper/baz +6: w--------- mnt/upper/bar diff --git a/tests/bsd/type_w.sh b/tests/bsd/type_w.sh index 15b4e68..aec9cb1 100644 --- a/tests/bsd/type_w.sh +++ b/tests/bsd/type_w.sh @@ -2,51 +2,51 @@ command -v mdconfig &>/dev/null || skip command -v newfs &>/dev/null || skip -clean_scratch +cd "$TEST" # Create a ramdisk -truncate -s1M scratch/img -md=$(bfs_sudo mdconfig scratch/img) || skip +truncate -s1M img +md=$(bfs_sudo mdconfig img) || skip defer bfs_sudo mdconfig -du "$md" # Make an ffs filesystem bfs_sudo newfs -n "/dev/$md" >&2 || skip -mkdir scratch/mnt +mkdir mnt # Mount it -bfs_sudo mount "/dev/$md" scratch/mnt || skip -defer bfs_sudo umount scratch/mnt +bfs_sudo mount "/dev/$md" mnt || skip +defer bfs_sudo umount mnt # Make it owned by us -bfs_sudo chown "$(id -u):$(id -g)" scratch/mnt -"$XTOUCH" -p scratch/mnt/{lower/{foo,bar,baz},upper/{bar,baz/qux}} +bfs_sudo chown "$(id -u):$(id -g)" mnt +"$XTOUCH" -p mnt/{lower/{foo,bar,baz},upper/{bar,baz/qux}} # Mount a union filesystem within it -bfs_sudo mount -t unionfs -o below scratch/mnt/{lower,upper} -defer bfs_sudo umount scratch/mnt/upper +bfs_sudo mount -t unionfs -o below mnt/{lower,upper} +defer bfs_sudo umount mnt/upper # Create a whiteout -rm scratch/mnt/upper/bar +rm mnt/upper/bar # FreeBSD find doesn't have -printf, so munge -ls output munge_ls() { - sed -En 's|.*([-drwx]{10}).*(scratch/.*)|'"$1"': \1 \2|p' + sed -En 's|.*([-drwx]{10}).*(mnt/.*)|'"$1"': \1 \2|p' } # Do a few tests in one { # Normally, we shouldn't see the whiteouts - invoke_bfs scratch/mnt -ls | munge_ls 1 + invoke_bfs mnt -ls | munge_ls 1 # -type w adds whiteouts to the output - invoke_bfs scratch/mnt -type w -ls | munge_ls 2 + invoke_bfs mnt -type w -ls | munge_ls 2 # So this is not the same as test 1 - invoke_bfs scratch/mnt \( -type w -or -not -type w \) -ls | munge_ls 3 + invoke_bfs mnt \( -type w -or -not -type w \) -ls | munge_ls 3 # Unmount the unionfs pop_defer # Now repeat the same tests - invoke_bfs scratch/mnt -ls | munge_ls 4 - invoke_bfs scratch/mnt -type w -ls | munge_ls 5 - invoke_bfs scratch/mnt \( -type w -or -not -type w \) -ls | munge_ls 6 + invoke_bfs mnt -ls | munge_ls 4 + invoke_bfs mnt -type w -ls | munge_ls 5 + invoke_bfs mnt \( -type w -or -not -type w \) -ls | munge_ls 6 } >"$OUT" sort_output diff_output diff --git a/tests/bsd/xattr.out b/tests/bsd/xattr.out index 109e7c9..0afed35 100644 --- a/tests/bsd/xattr.out +++ b/tests/bsd/xattr.out @@ -1,3 +1,3 @@ -scratch/xattr -scratch/xattr_2 -scratch/xattr_link +./xattr +./xattr_2 +./xattr_link diff --git a/tests/bsd/xattr.sh b/tests/bsd/xattr.sh index 4a4658c..68f729a 100644 --- a/tests/bsd/xattr.sh +++ b/tests/bsd/xattr.sh @@ -1,3 +1,3 @@ -invoke_bfs scratch -quit -xattr || skip +invoke_bfs . -quit -xattr || skip make_xattrs || skip -bfs_diff scratch -xattr +bfs_diff . -xattr diff --git a/tests/bsd/xattrname.out b/tests/bsd/xattrname.out index 0285ac1..ef732bd 100644 --- a/tests/bsd/xattrname.out +++ b/tests/bsd/xattrname.out @@ -1,2 +1,2 @@ -scratch/xattr -scratch/xattr_link +./xattr +./xattr_link diff --git a/tests/bsd/xattrname.sh b/tests/bsd/xattrname.sh index 655bd74..38b111a 100644 --- a/tests/bsd/xattrname.sh +++ b/tests/bsd/xattrname.sh @@ -1,11 +1,11 @@ -invoke_bfs scratch -quit -xattr || skip +invoke_bfs . -quit -xattr || skip make_xattrs || skip case "$UNAME" in Darwin|FreeBSD) - bfs_diff scratch -xattrname bfs_test + bfs_diff . -xattrname bfs_test ;; *) - bfs_diff scratch -xattrname security.bfs_test + bfs_diff . -xattrname security.bfs_test ;; esac diff --git a/tests/common/L_ls.sh b/tests/common/L_ls.sh index ced16c6..7ee2b44 100644 --- a/tests/common/L_ls.sh +++ b/tests/common/L_ls.sh @@ -1,2 +1 @@ -clean_scratch -invoke_bfs -L rainbow -ls >scratch/L_ls.out +invoke_bfs -L rainbow -ls >"$OUT" diff --git a/tests/common/L_mount.out b/tests/common/L_mount.out index 2e80082..788579d 100644 --- a/tests/common/L_mount.out +++ b/tests/common/L_mount.out @@ -1,5 +1,5 @@ -scratch -scratch/foo -scratch/foo/bar -scratch/foo/qux -scratch/mnt +. +./foo +./foo/bar +./foo/qux +./mnt diff --git a/tests/common/L_mount.sh b/tests/common/L_mount.sh index aaf9069..fd8042a 100644 --- a/tests/common/L_mount.sh +++ b/tests/common/L_mount.sh @@ -1,13 +1,13 @@ test "$UNAME" = "Darwin" && skip -clean_scratch -mkdir scratch/{foo,mnt} +cd "$TEST" +mkdir foo mnt -bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -defer bfs_sudo umount scratch/mnt +bfs_sudo mount -t tmpfs tmpfs mnt || skip +defer bfs_sudo umount mnt -ln -s ../mnt scratch/foo/bar -"$XTOUCH" scratch/mnt/baz -ln -s ../mnt/baz scratch/foo/qux +ln -s ../mnt foo/bar +"$XTOUCH" mnt/baz +ln -s ../mnt/baz foo/qux -bfs_diff -L scratch -mount +bfs_diff -L . -mount diff --git a/tests/common/delete.out b/tests/common/delete.out index fb188b9..9c558e3 100644 --- a/tests/common/delete.out +++ b/tests/common/delete.out @@ -1 +1 @@ -scratch +. diff --git a/tests/common/delete.sh b/tests/common/delete.sh index 89cf2a2..638f307 100644 --- a/tests/common/delete.sh +++ b/tests/common/delete.sh @@ -1,7 +1,4 @@ -clean_scratch -"$XTOUCH" -p scratch/foo/bar/baz - -# Don't try to delete '.' -(cd scratch && invoke_bfs . -delete) - -bfs_diff scratch +cd "$TEST" +"$XTOUCH" -p foo/bar/baz +invoke_bfs . -delete +bfs_diff . diff --git a/tests/common/delete_many.out b/tests/common/delete_many.out index fb188b9..9c558e3 100644 --- a/tests/common/delete_many.out +++ b/tests/common/delete_many.out @@ -1 +1 @@ -scratch +. diff --git a/tests/common/delete_many.sh b/tests/common/delete_many.sh index 6274319..48fe4c2 100644 --- a/tests/common/delete_many.sh +++ b/tests/common/delete_many.sh @@ -1,8 +1,8 @@ # Test for https://github.com/tavianator/bfs/issues/67 -clean_scratch -mkdir scratch/foo -"$XTOUCH" scratch/foo/{1..256} +cd "$TEST" +mkdir foo +"$XTOUCH" foo/{1..256} -invoke_bfs scratch/foo -delete -bfs_diff scratch +invoke_bfs foo -delete +bfs_diff . diff --git a/tests/common/execdir_ulimit.out b/tests/common/execdir_ulimit.out index 7f53982..bf52c09 100644 --- a/tests/common/execdir_ulimit.out +++ b/tests/common/execdir_ulimit.out @@ -1,3 +1,4 @@ +./. ./0 ./1 ./2 @@ -30,7 +31,6 @@ ./q ./r ./s -./scratch ./t ./u ./v diff --git a/tests/common/execdir_ulimit.sh b/tests/common/execdir_ulimit.sh index f7fc467..90c93c1 100644 --- a/tests/common/execdir_ulimit.sh +++ b/tests/common/execdir_ulimit.sh @@ -1,6 +1,6 @@ -clean_scratch -mkdir -p scratch/a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z -mkdir -p scratch/a/b/c/d/e/f/g/h/i/j/k/l/m/0/1/2/3/4/5/6/7/8/9/A/B/C +cd "$TEST" +mkdir -p a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z +mkdir -p a/b/c/d/e/f/g/h/i/j/k/l/m/0/1/2/3/4/5/6/7/8/9/A/B/C ulimit -n 13 -bfs_diff scratch -execdir echo {} \; +bfs_diff . -execdir echo {} \; diff --git a/tests/common/inum_bind_mount.out b/tests/common/inum_bind_mount.out index a520de3..ede8749 100644 --- a/tests/common/inum_bind_mount.out +++ b/tests/common/inum_bind_mount.out @@ -1,2 +1,2 @@ -scratch/bar -scratch/foo +./bar +./foo diff --git a/tests/common/inum_bind_mount.sh b/tests/common/inum_bind_mount.sh index 47f7c36..892713e 100644 --- a/tests/common/inum_bind_mount.sh +++ b/tests/common/inum_bind_mount.sh @@ -1,9 +1,9 @@ test "$UNAME" = "Linux" || skip -clean_scratch -"$XTOUCH" scratch/{foo,bar} +cd "$TEST" +"$XTOUCH" foo bar baz -bfs_sudo mount --bind scratch/{foo,bar} || skip -defer bfs_sudo umount scratch/bar +bfs_sudo mount --bind foo bar || skip +defer bfs_sudo umount bar -bfs_diff scratch -inum "$(inum scratch/bar)" +bfs_diff . -inum "$(inum bar)" diff --git a/tests/common/inum_mount.out b/tests/common/inum_mount.out index 99c7511..99fa01e 100644 --- a/tests/common/inum_mount.out +++ b/tests/common/inum_mount.out @@ -1 +1 @@ -scratch/mnt +./mnt diff --git a/tests/common/inum_mount.sh b/tests/common/inum_mount.sh index 1bf2d86..7facf57 100644 --- a/tests/common/inum_mount.sh +++ b/tests/common/inum_mount.sh @@ -1,9 +1,9 @@ test "$UNAME" = "Darwin" && skip -clean_scratch -mkdir scratch/{foo,mnt} +cd "$TEST" +mkdir foo mnt -bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -defer bfs_sudo umount scratch/mnt +bfs_sudo mount -t tmpfs tmpfs mnt || skip +defer bfs_sudo umount mnt -bfs_diff scratch -inum "$(inum scratch/mnt)" +bfs_diff . -inum "$(inum mnt)" diff --git a/tests/common/ls.sh b/tests/common/ls.sh index 85ca39c..bc50d90 100644 --- a/tests/common/ls.sh +++ b/tests/common/ls.sh @@ -1,2 +1 @@ -clean_scratch -invoke_bfs rainbow -ls >scratch/ls.out +invoke_bfs rainbow -ls >"$OUT" diff --git a/tests/common/mount.out b/tests/common/mount.out index f7839fb..6253434 100644 --- a/tests/common/mount.out +++ b/tests/common/mount.out @@ -1,4 +1,4 @@ -scratch -scratch/foo -scratch/foo/bar -scratch/mnt +. +./foo +./foo/bar +./mnt diff --git a/tests/common/mount.sh b/tests/common/mount.sh index db8b801..c9abde5 100644 --- a/tests/common/mount.sh +++ b/tests/common/mount.sh @@ -1,11 +1,11 @@ test "$UNAME" = "Darwin" && skip -clean_scratch -mkdir scratch/{foo,mnt} +cd "$TEST" +mkdir foo mnt -bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -defer bfs_sudo umount scratch/mnt +bfs_sudo mount -t tmpfs tmpfs mnt || skip +defer bfs_sudo umount mnt -"$XTOUCH" scratch/foo/bar scratch/mnt/baz +"$XTOUCH" foo/bar mnt/baz -bfs_diff scratch -mount +bfs_diff . -mount diff --git a/tests/gnu/L_delete.out b/tests/gnu/L_delete.out index ed0e9a1..7ed5f0d 100644 --- a/tests/gnu/L_delete.out +++ b/tests/gnu/L_delete.out @@ -1,2 +1,2 @@ -scratch -scratch/foo +. +./foo diff --git a/tests/gnu/L_delete.sh b/tests/gnu/L_delete.sh index 8fdb12a..0559c49 100644 --- a/tests/gnu/L_delete.sh +++ b/tests/gnu/L_delete.sh @@ -1,9 +1,8 @@ -clean_scratch -mkdir scratch/foo -mkdir scratch/bar -ln -s ../foo scratch/bar/baz +cd "$TEST" +mkdir foo bar +ln -s ../foo bar/baz # Don't try to rmdir() a symlink -invoke_bfs -L scratch/bar -delete +invoke_bfs -L bar -delete -bfs_diff scratch +bfs_diff . diff --git a/tests/gnu/files0_from_file.sh b/tests/gnu/files0_from_file.sh index 089a20e..81435a0 100644 --- a/tests/gnu/files0_from_file.sh +++ b/tests/gnu/files0_from_file.sh @@ -1,4 +1,4 @@ -clean_scratch +FILE="$TMP/$TEST.in" cd weirdnames -invoke_bfs -mindepth 1 -fprintf ../scratch/files0.in "%P\0" -bfs_diff -files0-from ../scratch/files0.in +invoke_bfs -mindepth 1 -fprintf "$FILE" "%P\0" +bfs_diff -files0-from "$FILE" diff --git a/tests/gnu/fls.sh b/tests/gnu/fls.sh index a86fa20..d2ff794 100644 --- a/tests/gnu/fls.sh +++ b/tests/gnu/fls.sh @@ -1,2 +1 @@ -clean_scratch -invoke_bfs rainbow -fls scratch/fls.out +invoke_bfs rainbow -fls "$OUT" diff --git a/tests/gnu/fls_nonexistent.sh b/tests/gnu/fls_nonexistent.sh index ff86763..2854569 100644 --- a/tests/gnu/fls_nonexistent.sh +++ b/tests/gnu/fls_nonexistent.sh @@ -1 +1 @@ -! invoke_bfs rainbow -fls scratch/nonexistent/path +! invoke_bfs rainbow -fls nonexistent/path diff --git a/tests/gnu/fprint0_nonexistent.sh b/tests/gnu/fprint0_nonexistent.sh index ec14c2d..4906081 100644 --- a/tests/gnu/fprint0_nonexistent.sh +++ b/tests/gnu/fprint0_nonexistent.sh @@ -1 +1 @@ -! invoke_bfs basic -fprint0 scratch/nonexistent/path +! invoke_bfs basic -fprint0 nonexistent/path diff --git a/tests/gnu/fprint_duplicate.sh b/tests/gnu/fprint_duplicate.sh index 5275502..8533b05 100644 --- a/tests/gnu/fprint_duplicate.sh +++ b/tests/gnu/fprint_duplicate.sh @@ -1,7 +1,7 @@ -"$XTOUCH" -p scratch/foo.out -ln scratch/foo.out scratch/foo.hard -ln -s foo.out scratch/foo.soft +"$XTOUCH" -p "$TEST/foo.out" +ln "$TEST/foo.out" "$TEST/foo.hard" +ln -s foo.out "$TEST/foo.soft" -invoke_bfs basic -fprint scratch/foo.out -fprint scratch/foo.hard -fprint scratch/foo.soft -sort scratch/foo.out >"$OUT" +invoke_bfs basic -fprint "$TEST/foo.out" -fprint "$TEST/foo.hard" -fprint "$TEST/foo.soft" +sort "$TEST/foo.out" >"$OUT" diff_output diff --git a/tests/gnu/fprint_nonexistent.sh b/tests/gnu/fprint_nonexistent.sh index 4409162..2a403a2 100644 --- a/tests/gnu/fprint_nonexistent.sh +++ b/tests/gnu/fprint_nonexistent.sh @@ -1 +1 @@ -! invoke_bfs basic -fprint scratch/nonexistent/path +! invoke_bfs basic -fprint nonexistent/path diff --git a/tests/gnu/fprintf_nonexistent.sh b/tests/gnu/fprintf_nonexistent.sh index 160e739..b1eea10 100644 --- a/tests/gnu/fprintf_nonexistent.sh +++ b/tests/gnu/fprintf_nonexistent.sh @@ -1 +1 @@ -! invoke_bfs basic -fprintf scratch/nonexistent/path '%p\n' +! invoke_bfs basic -fprintf nonexistent/path '%p\n' diff --git a/tests/gnu/fstype_stacked.out b/tests/gnu/fstype_stacked.out index 99c7511..c1e0e6c 100644 --- a/tests/gnu/fstype_stacked.out +++ b/tests/gnu/fstype_stacked.out @@ -1 +1 @@ -scratch/mnt +mnt diff --git a/tests/gnu/fstype_stacked.sh b/tests/gnu/fstype_stacked.sh index a4b067a..a9739bb 100644 --- a/tests/gnu/fstype_stacked.sh +++ b/tests/gnu/fstype_stacked.sh @@ -1,12 +1,12 @@ test "$UNAME" = "Linux" || skip -clean_scratch -mkdir scratch/mnt +cd "$TEST" +mkdir mnt -bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -defer bfs_sudo umount scratch/mnt +bfs_sudo mount -t tmpfs tmpfs mnt || skip +defer bfs_sudo umount mnt -bfs_sudo mount -t ramfs ramfs scratch/mnt || skip -defer bfs_sudo umount scratch/mnt +bfs_sudo mount -t ramfs ramfs mnt || skip +defer bfs_sudo umount mnt -bfs_diff scratch/mnt -fstype ramfs -print -o -printf '%p: %F\n' +bfs_diff mnt -fstype ramfs -print -o -printf '%p: %F\n' diff --git a/tests/gnu/fstype_umount.sh b/tests/gnu/fstype_umount.sh index b6da7a3..81c195f 100644 --- a/tests/gnu/fstype_umount.sh +++ b/tests/gnu/fstype_umount.sh @@ -1,12 +1,12 @@ test "$UNAME" = "Linux" || skip -clean_scratch +cd "$TEST" -mkdir scratch/tmp -bfs_sudo mount -t tmpfs tmpfs scratch/tmp || skip -defer bfs_sudo umount -R scratch/tmp +mkdir tmp +bfs_sudo mount -t tmpfs tmpfs tmp || skip +defer bfs_sudo umount -R tmp -mkdir scratch/tmp/ram -bfs_sudo mount -t ramfs ramfs scratch/tmp/ram || skip +mkdir tmp/ram +bfs_sudo mount -t ramfs ramfs tmp/ram || skip -bfs_diff scratch/tmp -path scratch/tmp -exec "${SUDO[@]}" umount scratch/tmp/ram \; , -fstype ramfs -print +bfs_diff tmp -path tmp -exec "${SUDO[@]}" umount tmp/ram \; , -fstype ramfs -print diff --git a/tests/gnu/ignore_readdir_race.sh b/tests/gnu/ignore_readdir_race.sh index 6586bcc..75165f6 100644 --- a/tests/gnu/ignore_readdir_race.sh +++ b/tests/gnu/ignore_readdir_race.sh @@ -1,5 +1,5 @@ -clean_scratch -"$XTOUCH" scratch/{foo,bar} +cd "$TEST" +"$XTOUCH" foo bar # -links 1 forces a stat() call, which will fail for the second file -invoke_bfs scratch -mindepth 1 -ignore_readdir_race -links 1 -exec "$TESTS/remove-sibling.sh" {} \; +invoke_bfs . -mindepth 1 -ignore_readdir_race -links 1 -exec "$TESTS/remove-sibling.sh" {} \; diff --git a/tests/gnu/ignore_readdir_race_notdir.sh b/tests/gnu/ignore_readdir_race_notdir.sh index 5b8b56d..8b03164 100644 --- a/tests/gnu/ignore_readdir_race_notdir.sh +++ b/tests/gnu/ignore_readdir_race_notdir.sh @@ -1,5 +1,5 @@ # Check -ignore_readdir_race handling when a directory is replaced with a file -clean_scratch -"$XTOUCH" -p scratch/foo/bar +cd "$TEST" +"$XTOUCH" -p foo/bar -invoke_bfs scratch -mindepth 1 -ignore_readdir_race -execdir rm -r {} \; -execdir "$XTOUCH" {} \; +invoke_bfs . -mindepth 1 -ignore_readdir_race -execdir rm -r {} \; -execdir "$XTOUCH" {} \; diff --git a/tests/gnu/inum_automount.out b/tests/gnu/inum_automount.out index 7b53ae3..3378e2d 100644 --- a/tests/gnu/inum_automount.out +++ b/tests/gnu/inum_automount.out @@ -1 +1 @@ -scratch/automnt +./automnt diff --git a/tests/gnu/inum_automount.sh b/tests/gnu/inum_automount.sh index 261a4be..86b23e1 100644 --- a/tests/gnu/inum_automount.sh +++ b/tests/gnu/inum_automount.sh @@ -2,13 +2,13 @@ command -v systemd-mount &>/dev/null || skip -clean_scratch -mkdir scratch/{foo,automnt} +cd "$TEST" +mkdir foo automnt -bfs_sudo systemd-mount -A -o bind basic scratch/automnt || skip -defer bfs_sudo systemd-umount scratch/automnt +bfs_sudo systemd-mount -A -o bind "$TMP/basic" automnt || skip +defer bfs_sudo systemd-umount automnt -before=$(inum scratch/automnt) -bfs_diff scratch -inum "$before" -prune -after=$(inum scratch/automnt) +before=$(inum automnt) +bfs_diff . -inum "$before" -prune +after=$(inum automnt) ((before == after)) diff --git a/tests/gnu/printf_Y_error.out b/tests/gnu/printf_Y_error.out index 410a9b5..1dd554e 100644 --- a/tests/gnu/printf_Y_error.out +++ b/tests/gnu/printf_Y_error.out @@ -1,3 +1,3 @@ -(scratch) () d d -(scratch/bar) (foo/bar) l ? -(scratch/foo) () d d +(.) () d d +(./bar) (foo/bar) l ? +(./foo) () d d diff --git a/tests/gnu/printf_Y_error.sh b/tests/gnu/printf_Y_error.sh index 13d52e3..d3130ce 100644 --- a/tests/gnu/printf_Y_error.sh +++ b/tests/gnu/printf_Y_error.sh @@ -1,8 +1,8 @@ -clean_scratch -mkdir scratch/foo -ln -s foo/bar scratch/bar +cd "$TEST" +mkdir foo +ln -s foo/bar bar -chmod -x scratch/foo -defer chmod +x scratch/foo +chmod -x foo +defer chmod +x foo -! bfs_diff scratch -printf '(%p) (%l) %y %Y\n' +! bfs_diff . -printf '(%p) (%l) %y %Y\n' diff --git a/tests/gnu/regex_invalid_utf8.out b/tests/gnu/regex_invalid_utf8.out index 03f3f58..a133b1a 100644 --- a/tests/gnu/regex_invalid_utf8.out +++ b/tests/gnu/regex_invalid_utf8.out @@ -1 +1 @@ -scratch/ +./ diff --git a/tests/gnu/regex_invalid_utf8.sh b/tests/gnu/regex_invalid_utf8.sh index 603d688..7006dcd 100644 --- a/tests/gnu/regex_invalid_utf8.sh +++ b/tests/gnu/regex_invalid_utf8.sh @@ -1,8 +1,8 @@ -clean_scratch +cd "$TEST" # Incomplete UTF-8 sequences -touch scratch/$'\xC3' || skip -touch scratch/$'\xE2\x84' || skip -touch scratch/$'\xF0\x9F\x92' || skip +touch $'\xC3' || skip +touch $'\xE2\x84' || skip +touch $'\xF0\x9F\x92' || skip -bfs_diff scratch -regex 'scratch/..' +bfs_diff . -regex '\./..' diff --git a/tests/gnu/xtype_bind_mount.out b/tests/gnu/xtype_bind_mount.out index 16804ea..d18d706 100644 --- a/tests/gnu/xtype_bind_mount.out +++ b/tests/gnu/xtype_bind_mount.out @@ -1,2 +1,2 @@ -scratch/link -scratch/null +./link +./null diff --git a/tests/gnu/xtype_bind_mount.sh b/tests/gnu/xtype_bind_mount.sh index 1cc20ec..35fb3f5 100644 --- a/tests/gnu/xtype_bind_mount.sh +++ b/tests/gnu/xtype_bind_mount.sh @@ -1,10 +1,10 @@ test "$UNAME" = "Linux" || skip -clean_scratch -"$XTOUCH" scratch/{file,null} -ln -s /dev/null scratch/link +cd "$TEST" +"$XTOUCH" file null +ln -s /dev/null link -bfs_sudo mount --bind /dev/null scratch/null || skip -defer bfs_sudo umount scratch/null +bfs_sudo mount --bind /dev/null null || skip +defer bfs_sudo umount null -bfs_diff -L scratch -type c +bfs_diff . -xtype c diff --git a/tests/posix/L_xdev.out b/tests/posix/L_xdev.out index 2e80082..788579d 100644 --- a/tests/posix/L_xdev.out +++ b/tests/posix/L_xdev.out @@ -1,5 +1,5 @@ -scratch -scratch/foo -scratch/foo/bar -scratch/foo/qux -scratch/mnt +. +./foo +./foo/bar +./foo/qux +./mnt diff --git a/tests/posix/L_xdev.sh b/tests/posix/L_xdev.sh index d16c211..82d8605 100644 --- a/tests/posix/L_xdev.sh +++ b/tests/posix/L_xdev.sh @@ -1,13 +1,13 @@ test "$UNAME" = "Darwin" && skip -clean_scratch -mkdir scratch/{foo,mnt} +cd "$TEST" +mkdir foo mnt -bfs_sudo mount -t tmpfs tmpfs scratch/mnt || skip -defer bfs_sudo umount scratch/mnt +bfs_sudo mount -t tmpfs tmpfs mnt || skip +defer bfs_sudo umount mnt -ln -s ../mnt scratch/foo/bar -"$XTOUCH" scratch/mnt/baz -ln -s ../mnt/baz scratch/foo/qux +ln -s ../mnt foo/bar +"$XTOUCH" mnt/baz +ln -s ../mnt/baz foo/qux -bfs_diff -L scratch -xdev +bfs_diff -L . -xdev diff --git a/tests/posix/depth_error.out b/tests/posix/depth_error.out index ed0e9a1..7ed5f0d 100644 --- a/tests/posix/depth_error.out +++ b/tests/posix/depth_error.out @@ -1,2 +1,2 @@ -scratch -scratch/foo +. +./foo diff --git a/tests/posix/depth_error.sh b/tests/posix/depth_error.sh index a6429d1..db414ba 100644 --- a/tests/posix/depth_error.sh +++ b/tests/posix/depth_error.sh @@ -1,7 +1,7 @@ -clean_scratch -"$XTOUCH" -p scratch/foo/bar +cd "$TEST" +"$XTOUCH" -p foo/bar -chmod a-r scratch/foo -defer chmod +r scratch/foo +chmod a-r foo +defer chmod +r foo -! bfs_diff scratch -depth +! bfs_diff . -depth diff --git a/tests/posix/overlayfs.out b/tests/posix/overlayfs.out index 754d01d..b472b56 100644 --- a/tests/posix/overlayfs.out +++ b/tests/posix/overlayfs.out @@ -1,5 +1,5 @@ -scratch/merged -scratch/merged/bar -scratch/merged/baz -scratch/merged/baz/qux -scratch/merged/foo +merged +merged/bar +merged/baz +merged/baz/qux +merged/foo diff --git a/tests/posix/overlayfs.sh b/tests/posix/overlayfs.sh index 4cccebf..21ef22f 100644 --- a/tests/posix/overlayfs.sh +++ b/tests/posix/overlayfs.sh @@ -1,11 +1,11 @@ test "$UNAME" = "Linux" || skip -clean_scratch -"$XTOUCH" -p scratch/{lower/{foo,bar,baz},upper/{bar,baz/qux}} +cd "$TEST" +"$XTOUCH" -p lower/{foo,bar,baz} upper/{bar,baz/qux} -mkdir -p scratch/{work,merged} -bfs_sudo mount -t overlay overlay -olowerdir=scratch/lower,upperdir=scratch/upper,workdir=scratch/work scratch/merged || skip -defer bfs_sudo rm -rf scratch/work -defer bfs_sudo umount scratch/merged +mkdir -p work merged +bfs_sudo mount -t overlay overlay -olowerdir=lower,upperdir=upper,workdir=work merged || skip +defer bfs_sudo rm -rf work +defer bfs_sudo umount merged -bfs_diff scratch/merged +bfs_diff merged diff --git a/tests/posix/readdir_error.sh b/tests/posix/readdir_error.sh index fc48eb1..82fcd17 100644 --- a/tests/posix/readdir_error.sh +++ b/tests/posix/readdir_error.sh @@ -1,27 +1,27 @@ test "$UNAME" = "Linux" || skip -clean_scratch -mkfifo scratch/{fever,pid,wait,running} +cd "$TEST" +mkfifo hang pid wait running ( # Create a zombie process - cat scratch/fever >/dev/null & - # Write the PID to scratch/pid - echo $! >scratch/pid + cat hang >/dev/null & + # Write the PID to pid + echo $! >pid # Don't wait on the zombie process - exec cat scratch/wait scratch/fever >scratch/running + exec cat wait hang >running ) & # Kill the parent cat on exit defer kill -9 %1 # Read the child PID -read -r pid scratch/wait & -read -r _ wait & +read -r _ /dev/null; then - for path in "$TMP/scratch"/*; do - if mountpoint -q "$path"; then - sudo umount "$path" - fi - done - fi - - # Reset any modified permissions - chmod -R +rX "$TMP/scratch" - - rm -rf "$TMP/scratch" - fi - - mkdir "$TMP/scratch" } # Clean up temporary directories on exit @@ -169,8 +147,5 @@ clean_stddirs() { fi done - # In case a test left anything weird in scratch/ - clean_scratch - rm -rf "$TMP" } -- cgit v1.2.3 From 4c7a49a83e082a7bfff85a9305882f0912655123 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 23 Oct 2023 10:12:25 -0400 Subject: tests: Run test cases in parallel --- GNUmakefile | 7 ++++++- tests/getopts.sh | 12 +++++++++++ tests/run.sh | 61 ++++++++++++++++++++++++++++++++++++++++++-------------- 3 files changed, 64 insertions(+), 16 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 45797e2..62f24fb 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -287,8 +287,13 @@ check: $(CHECKS) $(UNIT_CHECKS): check-%: $(BIN)/tests/% $< +JOBS := $(filter -j%,$(MAKEFLAGS)) +ifndef JOBS + JOBS := -j1 +endif + $(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(TEST_UTILS) - ./tests/tests.sh --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) + ./tests/tests.sh $(JOBS) --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) # Custom test flags for distcheck DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" diff --git a/tests/getopts.sh b/tests/getopts.sh index 7d3ef4b..d34df4f 100644 --- a/tests/getopts.sh +++ b/tests/getopts.sh @@ -52,6 +52,7 @@ EOF # Parse the command line parse_args() { + JOBS=0 PATTERNS=() SUDO=() STOP=0 @@ -64,6 +65,9 @@ parse_args() { for arg; do case "$arg" in + -j*) + JOBS="${arg#-j}" + ;; --bfs=*) BFS="${arg#*=}" ;; @@ -127,6 +131,14 @@ parse_args() { esac done + if ((JOBS == 0)); then + if command -v nproc &>/dev/null; then + JOBS=$(nproc) + else + JOBS=1 + fi + fi + # Try to resolve the path to $BFS before we cd, while also supporting # --bfs="./bin/bfs -S ids" read -a BFS <<<"${BFS:-$BIN/bfs}" diff --git a/tests/run.sh b/tests/run.sh index 28801f4..b46fde6 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -42,6 +42,42 @@ run_test() ( source "$@" ) +# Run a test in the background +bg_test() { + if ((VERBOSE_ERRORS)); then + run_test "$1" + else + run_test "$1" 2>"$TMP/TEST.err" + fi + ret=$? + + if ((ret != 0 && ret != EX_SKIP)); then + ((VERBOSE_ERRORS)) || cat "$TMP/$TEST.err" >&2 + color printf "${BOL}${RED}%s failed!${RST}\n" "$TEST" + fi + + return $ret +} + +# Reap a background job +reap() { + wait -n + ret=$? + ((BG--)) + + case $ret in + 0) + ((++passed)) + ;; + $EX_SKIP) + ((++skipped)) + ;; + *) + ((++failed)) + ;; + esac +} + # Run all the tests run_tests() { if ((VERBOSE_TESTS)); then @@ -70,6 +106,8 @@ run_tests() { TEST_FMT="." fi + BG=0 + # Turn off set -e (but turn it back on in run_test) set +e @@ -79,23 +117,16 @@ run_tests() { mkdir -p "$TMP/$TEST" OUT="$TMP/$TEST.out" - if ((VERBOSE_ERRORS)); then - run_test "$TESTS/$TEST.sh" - else - run_test "$TESTS/$TEST.sh" 2>"$TMP/$TEST.err" + if ((BG >= JOBS)); then + reap fi - status=$? + ((++BG)) - if ((status == 0)); then - ((++passed)) - elif ((status == EX_SKIP)); then - ((++skipped)) - else - ((++failed)) - ((VERBOSE_ERRORS)) || cat "$TMP/$TEST.err" >&2 - color printf "${BOL}${RED}%s failed!${RST}\n" "$TEST" - ((STOP)) && break - fi + bg_test "$TESTS/$TEST.sh" & + done + + while ((BG > 0)); do + reap done printf "${BOL}" -- cgit v1.2.3 From de8e0fbb8c1e59b3f1af72a07477b63a8f0bbc75 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 23 Oct 2023 11:42:33 -0400 Subject: tests: Fix Bash 3 compatibility --- tests/bfs/execdir_plus_nonexistent.sh | 6 ++--- tests/common/execdir_nonexistent.sh | 6 ++--- tests/posix/L_loops.sh | 4 ++-- tests/posix/exec_nonexistent.sh | 7 ++---- tests/posix/exec_plus_nonexistent.sh | 6 ++--- tests/run.sh | 44 +++++++++++++++++++++++++---------- tests/util.sh | 11 ++++----- 7 files changed, 47 insertions(+), 37 deletions(-) diff --git a/tests/bfs/execdir_plus_nonexistent.sh b/tests/bfs/execdir_plus_nonexistent.sh index e3b4d2d..ed7ed56 100644 --- a/tests/bfs/execdir_plus_nonexistent.sh +++ b/tests/bfs/execdir_plus_nonexistent.sh @@ -1,4 +1,2 @@ -! stderr=$(invoke_bfs basic -execdir "$TESTS/nonexistent" {} + 2>&1 >/dev/null) -[ -n "$stderr" ] - -! bfs_diff basic -execdir "$TESTS/nonexistent" {} + -print +bfs_diff basic -execdir "$TESTS/nonexistent" {} + -print 2>"$TEST/err" && fail +test -s "$TEST/err" diff --git a/tests/common/execdir_nonexistent.sh b/tests/common/execdir_nonexistent.sh index 4bb4fdb..0ec013c 100644 --- a/tests/common/execdir_nonexistent.sh +++ b/tests/common/execdir_nonexistent.sh @@ -1,4 +1,2 @@ -! stderr=$(invoke_bfs basic -execdir "$TESTS/nonexistent" {} \; 2>&1 >/dev/null) -[ -n "$stderr" ] - -! bfs_diff basic -print -execdir "$TESTS/nonexistent" {} \; -print +bfs_diff basic -print -execdir "$TESTS/nonexistent" {} \; -print 2>"$TEST/err" && fail +test -s "$TEST/err" diff --git a/tests/posix/L_loops.sh b/tests/posix/L_loops.sh index 1314401..01b7efc 100644 --- a/tests/posix/L_loops.sh +++ b/tests/posix/L_loops.sh @@ -1,4 +1,4 @@ # POSIX says it's okay to either stop or keep going on seeing a filesystem # loop, as long as a diagnostic is printed -! errors=$(invoke_bfs -L loops 2>&1 >/dev/null) -[ -n "$errors" ] +invoke_bfs -L loops >/dev/null 2>"$OUT" && fail +test -s "$OUT" diff --git a/tests/posix/exec_nonexistent.sh b/tests/posix/exec_nonexistent.sh index 901be86..a9ff052 100644 --- a/tests/posix/exec_nonexistent.sh +++ b/tests/posix/exec_nonexistent.sh @@ -1,7 +1,4 @@ # Failure to execute the command should lead to an error message and # non-zero exit status. See https://unix.stackexchange.com/q/704522/56202 - -! stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} \; 2>&1 >/dev/null) -[ -n "$stderr" ] - -! bfs_diff basic -print -exec "$TESTS/nonexistent" {} \; -print +bfs_diff basic -print -exec "$TESTS/nonexistent" {} \; -print 2>"$TEST/err" && fail +test -s "$TEST/err" diff --git a/tests/posix/exec_plus_nonexistent.sh b/tests/posix/exec_plus_nonexistent.sh index 6bddc67..24582a3 100644 --- a/tests/posix/exec_plus_nonexistent.sh +++ b/tests/posix/exec_plus_nonexistent.sh @@ -1,4 +1,2 @@ -! stderr=$(invoke_bfs basic -exec "$TESTS/nonexistent" {} + 2>&1 >/dev/null) -[ -n "$stderr" ] - -! bfs_diff basic -exec "$TESTS/nonexistent" {} + -print +bfs_diff basic -exec "$TESTS/nonexistent" {} + -print 2>"$TEST/err" && fail +test -s "$TEST/err" diff --git a/tests/run.sh b/tests/run.sh index b46fde6..d581476 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -47,7 +47,7 @@ bg_test() { if ((VERBOSE_ERRORS)); then run_test "$1" else - run_test "$1" 2>"$TMP/TEST.err" + run_test "$1" 2>"$TMP/$TEST.err" fi ret=$? @@ -59,9 +59,21 @@ bg_test() { return $ret } -# Reap a background job -reap() { - wait -n +# Wait for any background job to complete +if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 3))); then + wait_any() { + wait -n + } +else + wait_any() { + read -ra jobs < <(jobs -p) + wait ${jobs[0]} + } +fi + +# Wait for a background test to finish +wait_test() { + wait_any ret=$? ((BG--)) @@ -118,7 +130,7 @@ run_tests() { OUT="$TMP/$TEST.out" if ((BG >= JOBS)); then - reap + wait_test fi ((++BG)) @@ -126,7 +138,7 @@ run_tests() { done while ((BG > 0)); do - reap + wait_test done printf "${BOL}" @@ -145,6 +157,14 @@ run_tests() { ## Utilities for the tests themselves +# Default return value for failed tests +EX_FAIL=1 + +# Fail the current test +fail() { + exit $EX_FAIL +} + # Return value when a test is skipped EX_SKIP=77 @@ -166,9 +186,9 @@ skip() { # Run a command and check its exit status check_exit() { local expected="$1" - local actual="0" + local actual=0 shift - "$@" || actual="$?" + "$@" || actual=$? ((actual == expected)) } @@ -253,9 +273,9 @@ invoke_bfs() { # Allow bfs to fail, but not crash if ((ret > 125)); then - exit "$ret" + exit $ret else - return "$ret" + return $ret fi } @@ -275,9 +295,9 @@ bfs_pty() { "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" || ret=$? if ((ret > 125)); then - exit "$ret" + exit $ret else - return "$ret" + return $ret fi } diff --git a/tests/util.sh b/tests/util.sh index 31a7b6c..bfa5d16 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -120,7 +120,7 @@ callers() { # Print a message including path, line number, and command debug() { - local file="${1/#*\/tests\//tests\/}" + local file="${1/#*\/tests\//tests/}" set -- "$file" "${@:2}" color printf "${BLD}%s:%d:${RST} %s\n %s\n" "$@" } @@ -136,14 +136,13 @@ quote() { fi } +DEFER_LEVEL=-1 + # Run a command when this (sub)shell exits defer() { - # Refresh trap state before trap -p - # See https://unix.stackexchange.com/a/556888/56202 - trap -- KILL - # Check if the EXIT trap is already set - if ! trap -p EXIT | grep -q pop_defers; then + if ((DEFER_LEVEL != BASH_SUBSHELL)); then + DEFER_LEVEL=$BASH_SUBSHELL DEFER_CMDS=() DEFER_LINES=() DEFER_FILES=() -- cgit v1.2.3 From 63c43d7fb463529219a4de4d1fddb8953056346d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 23 Oct 2023 13:00:51 -0400 Subject: tests: Clean up verbose logging a bit --- tests/run.sh | 56 ++++++++++++++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 20 deletions(-) diff --git a/tests/run.sh b/tests/run.sh index d581476..f93cb4b 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -239,25 +239,41 @@ bfs_verbose() { } bfs_verbose_impl() { - printf "${GRN}%q${RST} " "${BFS[@]}" + printf "${GRN}%q${RST}" "${BFS[0]}" + if ((${#BFS[@]} > 1)); then + printf " ${GRN}%q${RST}" "${BFS[1:]}" + fi - local expr_started= + local expr_started=0 color for arg; do - if [[ $arg == -[A-Z]* ]]; then - printf "${CYN}%q${RST} " "$arg" - elif [[ $arg == [\(!] || $arg == -[ao] || $arg == -and || $arg == -or || $arg == -not ]]; then - expr_started=yes - printf "${RED}%q${RST} " "$arg" - elif [[ $expr_started && $arg == [\),] ]]; then - printf "${RED}%q${RST} " "$arg" - elif [[ $arg == -?* ]]; then - expr_started=yes - printf "${BLU}%q${RST} " "$arg" - elif [ "$expr_started" ]; then - printf "${BLD}%q${RST} " "$arg" - else - printf "${MAG}%q${RST} " "$arg" - fi + case "$arg" in + -[A-Z]*|-[dsxf]|-j*) + color="${CYN}" + ;; + \(|!|-[ao]|-and|-or|-not|-exclude) + expr_started=1 + color="${RED}" + ;; + \)|,) + if ((expr_started)); then + color="${RED}" + else + color="${MAG}" + fi + ;; + -?*) + expr_started=1 + color="${BLU}" + ;; + *) + if ((expr_started)); then + color="${BLD}" + else + color="${MAG}" + fi + ;; + esac + printf " ${color}%q${RST}" "$arg" done printf '\n' @@ -336,8 +352,8 @@ make_xattrs() { EX_DIFF=20 # Detect colored diff support -if ((COLOR_STDERR)) && diff --color=always /dev/null /dev/null 2>/dev/null; then - DIFF="diff --color=always" +if diff --color /dev/null /dev/null 2>/dev/null; then + DIFF="diff --color" else DIFF="diff" fi @@ -354,7 +370,7 @@ diff_output() { if ((UPDATE)); then cp "$OUT" "$GOLD" else - $DIFF -u "$GOLD" "$OUT" >&2 + $DIFF -u "$GOLD" "$OUT" >&4 fi } -- cgit v1.2.3 From 62415c0d1530f271ce9d417fa075ac00b9473f1e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 23 Oct 2023 13:05:15 -0400 Subject: tests: Document -j --- tests/getopts.sh | 41 ++++++++++++++++++++--------------------- 1 file changed, 20 insertions(+), 21 deletions(-) diff --git a/tests/getopts.sh b/tests/getopts.sh index d34df4f..ac75140 100644 --- a/tests/getopts.sh +++ b/tests/getopts.sh @@ -5,14 +5,32 @@ ## Argument parsing +if command -v nproc &>/dev/null; then + JOBS=$(nproc) +else + JOBS=1 +fi +PATTERNS=() +SUDO=() +STOP=0 +CLEAN=1 +UPDATE=0 +VERBOSE_COMMANDS=0 +VERBOSE_ERRORS=0 +VERBOSE_SKIPPED=0 +VERBOSE_TESTS=0 + # Print usage information usage() { local pad=$(printf "%*s" ${#0} "") color cat </dev/null; then - JOBS=$(nproc) - else - JOBS=1 - fi - fi - # Try to resolve the path to $BFS before we cd, while also supporting # --bfs="./bin/bfs -S ids" read -a BFS <<<"${BFS:-$BIN/bfs}" -- cgit v1.2.3 From d2d5e5ad2e4b61493d27bfe8c9341fd81f703ae9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 23 Oct 2023 13:06:44 -0400 Subject: tests/bfs/printf_color: Fix race condition Invoking bfs in the top directory can lead it to explore other tests' scratch directories, so explicitly exclude them. --- tests/bfs/printf_color.out | 1 + tests/bfs/printf_color.sh | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/bfs/printf_color.out b/tests/bfs/printf_color.out index 6641e9a..77d21c3 100644 --- a/tests/bfs/printf_color.out +++ b/tests/bfs/printf_color.out @@ -1,4 +1,5 @@ . $'./rainbow/\e[1m' $'\e[0m' $'./rainbow/\e[1m/'$'\e[0m' $'rainbow/\e[1m/'$'\e[0m' +. . . . . . rainbow ./rainbow rainbow . ./rainbow exec.sh ./rainbow/exec.sh rainbow/exec.sh . ./rainbow $'\e[1m' $'./rainbow/\e[1m' $'rainbow/\e[1m' diff --git a/tests/bfs/printf_color.sh b/tests/bfs/printf_color.sh index 7bb38c2..3641ddb 100644 --- a/tests/bfs/printf_color.sh +++ b/tests/bfs/printf_color.sh @@ -1 +1 @@ -bfs_diff -color -path './rainbow*' -printf '%H %h %f %p %P %l\n' +bfs_diff -color -exclude \( -depth 1 -not -name rainbow \) -printf '%H %h %f %p %P %l\n' -- cgit v1.2.3 From 8c06e033483215e5148bc803b61207b99d458a48 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 23 Oct 2023 13:34:33 -0400 Subject: tests/bfs/help: Use ... && fail instead of ! ... ! false doesn't trigger an error with set -e. --- tests/bfs/help.sh | 8 ++++---- tests/bsd/f_incomplete.sh | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/tests/bfs/help.sh b/tests/bfs/help.sh index 2c0b28a..ec53867 100644 --- a/tests/bfs/help.sh +++ b/tests/bfs/help.sh @@ -1,4 +1,4 @@ -! invoke_bfs -help | grep -E '\{...?\}' -! invoke_bfs -D help | grep -E '\{...?\}' -! invoke_bfs -S help | grep -E '\{...?\}' -! invoke_bfs -regextype help | grep -E '\{...?\}' +invoke_bfs -help | grep -E '\{...?\}' && fail +invoke_bfs -D help | grep -E '\{...?\}' && fail +invoke_bfs -S help | grep -E '\{...?\}' && fail +invoke_bfs -regextype help | grep -E '\{...?\}' && fail diff --git a/tests/bsd/f_incomplete.sh b/tests/bsd/f_incomplete.sh index 50afe42..0dfb19f 100644 --- a/tests/bsd/f_incomplete.sh +++ b/tests/bsd/f_incomplete.sh @@ -1,2 +1 @@ ! invoke_bfs -f - -- cgit v1.2.3 From e1390e21d6fcfa97838bdaff968d9cfb26261065 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 23 Oct 2023 13:46:52 -0400 Subject: tests/bfs/help: Fix exit status --- tests/bfs/help.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/bfs/help.sh b/tests/bfs/help.sh index ec53867..5029c7e 100644 --- a/tests/bfs/help.sh +++ b/tests/bfs/help.sh @@ -1,4 +1,4 @@ -invoke_bfs -help | grep -E '\{...?\}' && fail -invoke_bfs -D help | grep -E '\{...?\}' && fail -invoke_bfs -S help | grep -E '\{...?\}' && fail -invoke_bfs -regextype help | grep -E '\{...?\}' && fail +! invoke_bfs -help | grep -E '\{...?\}' || fail +! invoke_bfs -D help | grep -E '\{...?\}' || fail +! invoke_bfs -S help | grep -E '\{...?\}' || fail +! invoke_bfs -regextype help | grep -E '\{...?\}' || fail -- cgit v1.2.3 From 70fe3b12852ca7fb5bf1529042754fc75e69baa0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 23 Oct 2023 15:00:31 -0400 Subject: ci: Run tests in parallel on macOS and FreeBSD --- .github/workflows/ci.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 065fcfd..bc0719e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -56,7 +56,8 @@ jobs: - name: Run tests run: | - make -j$(sysctl -n hw.ncpu) distcheck + jobs=$(sysctl -n hw.ncpu) + make -j$jobs distcheck JOBS=-j$jobs freebsd: name: FreeBSD @@ -82,4 +83,4 @@ jobs: mkdir ~/.ssh printf 'Host %s\n\tStrictHostKeyChecking=accept-new\n\tUser github\n' "$spurion" >~/.ssh/config rsync -rl --delete . "[$spurion]:bfs" - ssh "$spurion" '. ~/.ssh/rc; gmake -C bfs -j$(sysctl -n hw.ncpu) distcheck CC=clang16' + ssh "$spurion" '. ~/.ssh/rc; gmake -C bfs -j$(nproc) distcheck CC=clang16 JOBS=-j$(nproc)' -- cgit v1.2.3 From 8ced65189cbea5ff0b06482713d647ca57c91f81 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 26 Oct 2023 10:57:25 -0400 Subject: bfstd: Only free the locale if we dup'd it successfully --- src/bfstd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index ace9ed2..cdc33f1 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -302,10 +302,10 @@ const char *xstrerror(int errnum) { if (copy != (locale_t)0) { ret = strerror_l(errnum, copy); - } - if (loc == LC_GLOBAL_LOCALE) { - freelocale(copy); + if (loc == LC_GLOBAL_LOCALE) { + freelocale(copy); + } } #endif -- cgit v1.2.3 From 14ef89a442f7a027f52fd688b438c5fa627b6af7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 27 Oct 2023 10:58:47 -0400 Subject: bfstd: Expose xmbrtowc() and use it in eval_status() --- src/bfstd.c | 26 +++++++++++++------------- src/bfstd.h | 19 ++++++++++++++++++- src/eval.c | 39 +++++++++++++++------------------------ 3 files changed, 46 insertions(+), 38 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index cdc33f1..a5a7e54 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -586,18 +586,18 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * #endif } -/** mbrtowc() wrapper. */ -static int xmbrtowc(wchar_t *wc, size_t *i, const char *str, size_t len, mbstate_t *mb) { - size_t mblen = mbrtowc(wc, str + *i, len - *i, mb); +wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb) { + wchar_t wc; + size_t mblen = mbrtowc(&wc, str + *i, len - *i, mb); switch (mblen) { case -1: // Invalid byte sequence case -2: // Incomplete byte sequence *i += 1; memset(mb, 0, sizeof(*mb)); - return -1; + return WEOF; default: *i += mblen; - return 0; + return wc; } } @@ -609,12 +609,12 @@ size_t xstrwidth(const char *str) { memset(&mb, 0, sizeof(mb)); for (size_t i = 0; i < len;) { - wchar_t wc; - if (xmbrtowc(&wc, &i, str, len, &mb) == 0) { - ret += wcwidth(wc); - } else { + wint_t wc = xmbrtowc(str, &i, len, &mb); + if (wc == WEOF) { // Assume a single-width '?' ++ret; + } else { + ret += wcwidth(wc); } } @@ -729,8 +729,8 @@ multibyte: memset(&mb, 0, sizeof(mb)); for (size_t j = i; i < len; i = j) { - wchar_t wc; - if (xmbrtowc(&wc, &j, str, len, &mb) != 0) { + wint_t wc = xmbrtowc(str, &j, len, &mb); + if (wc == WEOF) { break; } if (!xiswprint(wc, flags)) { @@ -781,8 +781,8 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, en size_t start = i; bool safe = false; - wchar_t wc; - if (xmbrtowc(&wc, &i, str, len, &mb) == 0) { + wint_t wc = xmbrtowc(str, &i, len, &mb); + if (wc != WEOF) { safe = xiswprint(wc, flags); } diff --git a/src/bfstd.h b/src/bfstd.h index abde24e..db558c6 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -316,7 +316,24 @@ char *xconfstr(int name); */ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear); -// #include +#include + +/** + * Error-recovering mbrtowc() wrapper. + * + * @param str + * The string to convert. + * @param i + * The current index. + * @param len + * The length of the string. + * @param mb + * The multi-byte decoding state. + * @return + * The wide character at index *i, or WEOF if decoding fails. In either + * case, *i will be advanced to the next multi-byte character. + */ +wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb); /** * wcswidth() variant that works on narrow strings. diff --git a/src/eval.c b/src/eval.c index 3d396fa..6230353 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1114,6 +1114,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time const struct BFTW *ftwbuf = state->ftwbuf; + dchar *status = NULL; dchar *rhs = dstrprintf(" (visited: %zu, depth: %2zu)", count, ftwbuf->depth); if (!rhs) { return; @@ -1125,9 +1126,9 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time rhslen = 0; } - dchar *status = dstralloc(0); + status = dstralloc(0); if (!status) { - goto out_rhs; + goto out; } const char *path = ftwbuf->path; @@ -1139,20 +1140,14 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time // Try to make sure even wide characters fit in the status bar size_t pathmax = width - rhslen - 3; size_t pathwidth = 0; + size_t lhslen = 0; mbstate_t mb; memset(&mb, 0, sizeof(mb)); - while (pathlen > 0) { - wchar_t wc; - size_t len = mbrtowc(&wc, path, pathlen, &mb); + for (size_t i = lhslen; lhslen < pathlen; lhslen = i) { + wint_t wc = xmbrtowc(path, &i, pathlen, &mb); int cwidth; - if (len == (size_t)-1) { + if (wc == WEOF) { // Invalid byte sequence, assume a single-width '?' - len = 1; - cwidth = 1; - memset(&mb, 0, sizeof(mb)); - } else if (len == (size_t)-2) { - // Incomplete byte sequence, assume a single-width '?' - len = pathlen; cwidth = 1; } else { cwidth = wcwidth(wc); @@ -1164,35 +1159,31 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time if (pathwidth + cwidth > pathmax) { break; } - - if (dstrncat(&status, path, len) != 0) { - goto out_rhs; - } - - path += len; - pathlen -= len; pathwidth += cwidth; } + if (dstrncat(&status, path, lhslen) != 0) { + goto out; + } if (dstrcat(&status, "...") != 0) { - goto out_rhs; + goto out; } while (pathwidth < pathmax) { if (dstrapp(&status, ' ') != 0) { - goto out_rhs; + goto out; } ++pathwidth; } - if (dstrcat(&status, rhs) != 0) { - goto out_rhs; + if (dstrdcat(&status, rhs) != 0) { + goto out; } bfs_bar_update(bar, status); +out: dstrfree(status); -out_rhs: dstrfree(rhs); } -- cgit v1.2.3 From caeff0108d59d36a3c3fc62fd5ed788eaaf4f0a5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 27 Oct 2023 11:10:26 -0400 Subject: eval: TTY-escape paths in the status bar --- src/eval.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/eval.c b/src/eval.c index 6230353..05be5bb 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1137,6 +1137,12 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time pathlen = strlen(path); } + // Escape weird filename characters + if (dstrnescat(&status, path, pathlen, WESC_TTY) != 0) { + goto out; + } + pathlen = dstrlen(status); + // Try to make sure even wide characters fit in the status bar size_t pathmax = width - rhslen - 3; size_t pathwidth = 0; @@ -1144,7 +1150,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time mbstate_t mb; memset(&mb, 0, sizeof(mb)); for (size_t i = lhslen; lhslen < pathlen; lhslen = i) { - wint_t wc = xmbrtowc(path, &i, pathlen, &mb); + wint_t wc = xmbrtowc(status, &i, pathlen, &mb); int cwidth; if (wc == WEOF) { // Invalid byte sequence, assume a single-width '?' @@ -1161,10 +1167,8 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time } pathwidth += cwidth; } + dstresize(&status, lhslen); - if (dstrncat(&status, path, lhslen) != 0) { - goto out; - } if (dstrcat(&status, "...") != 0) { goto out; } -- cgit v1.2.3 From 0015289fec2c54de481cab87fb5b43bc2d1f2a4a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 27 Oct 2023 11:21:38 -0400 Subject: Use {0} to initialize mbstate_t rather than memset() --- src/bfstd.c | 15 +++++---------- src/eval.c | 3 +-- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index a5a7e54..eee02b8 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -593,7 +593,7 @@ wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb) { case -1: // Invalid byte sequence case -2: // Incomplete byte sequence *i += 1; - memset(mb, 0, sizeof(*mb)); + *mb = (mbstate_t){0}; return WEOF; default: *i += mblen; @@ -605,9 +605,7 @@ size_t xstrwidth(const char *str) { size_t len = strlen(str); size_t ret = 0; - mbstate_t mb; - memset(&mb, 0, sizeof(mb)); - + mbstate_t mb = {0}; for (size_t i = 0; i < len;) { wint_t wc = xmbrtowc(str, &i, len, &mb); if (wc == WEOF) { @@ -724,9 +722,8 @@ static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) } } - mbstate_t mb; -multibyte: - memset(&mb, 0, sizeof(mb)); +multibyte:; + mbstate_t mb = {0}; for (size_t j = i; i < len; i = j) { wint_t wc = xmbrtowc(str, &j, len, &mb); @@ -774,9 +771,7 @@ static const char *dollar_esc(char c) { static char *dollar_quote(char *dest, char *end, const char *str, size_t len, enum wesc_flags flags) { dest = xstpecpy(dest, end, "$'"); - mbstate_t mb; - memset(&mb, 0, sizeof(mb)); - + mbstate_t mb = {0}; for (size_t i = 0; i < len;) { size_t start = i; bool safe = false; diff --git a/src/eval.c b/src/eval.c index 05be5bb..56d7cd8 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1147,8 +1147,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time size_t pathmax = width - rhslen - 3; size_t pathwidth = 0; size_t lhslen = 0; - mbstate_t mb; - memset(&mb, 0, sizeof(mb)); + mbstate_t mb = {0}; for (size_t i = lhslen; lhslen < pathlen; lhslen = i) { wint_t wc = xmbrtowc(status, &i, pathlen, &mb); int cwidth; -- cgit v1.2.3 From 58004c7c18baae9374f857f07844fa14b56c9644 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 27 Oct 2023 12:17:10 -0400 Subject: typo: Shrink the key_coords table --- src/typo.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/typo.c b/src/typo.c index 305711d..b1c5c44 100644 --- a/src/typo.c +++ b/src/typo.c @@ -3,11 +3,12 @@ #include "typo.h" #include +#include #include #include // Assume QWERTY layout for now -static const int key_coords[UCHAR_MAX + 1][3] = { +static const int8_t key_coords[UCHAR_MAX + 1][3] = { ['`'] = { 0, 0, 0}, ['~'] = { 0, 0, 1}, ['1'] = { 3, 0, 0}, @@ -112,7 +113,7 @@ static const int key_coords[UCHAR_MAX + 1][3] = { }; static int char_distance(char a, char b) { - const int *ac = key_coords[(unsigned char)a], *bc = key_coords[(unsigned char)b]; + const int8_t *ac = key_coords[(unsigned char)a], *bc = key_coords[(unsigned char)b]; int ret = 0; for (int i = 0; i < 3; ++i) { ret += abs(ac[i] - bc[i]); -- cgit v1.2.3 From b18f56eaf260761d8b9154e3004587c90f8c67b8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 27 Oct 2023 12:45:10 -0400 Subject: thread: Move thread wrapper functions out of line --- GNUmakefile | 1 + src/thread.c | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/thread.h | 50 ++++++++++++++----------------------- 3 files changed, 100 insertions(+), 32 deletions(-) create mode 100644 src/thread.c diff --git a/GNUmakefile b/GNUmakefile index 62f24fb..2494b16 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -250,6 +250,7 @@ LIBBFS := \ $(OBJ)/src/printf.o \ $(OBJ)/src/pwcache.o \ $(OBJ)/src/stat.o \ + $(OBJ)/src/thread.o \ $(OBJ)/src/trie.o \ $(OBJ)/src/typo.o \ $(OBJ)/src/xregex.o \ diff --git a/src/thread.c b/src/thread.c new file mode 100644 index 0000000..200d8c3 --- /dev/null +++ b/src/thread.c @@ -0,0 +1,81 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "thread.h" +#include "bfstd.h" +#include "config.h" +#include "diag.h" +#include +#include + +#define THREAD_FALLIBLE(expr) \ + do { \ + int err = expr; \ + if (err == 0) { \ + return 0; \ + } else { \ + errno = err; \ + return -1; \ + } \ + } while (0) + +#define THREAD_INFALLIBLE(...) \ + THREAD_INFALLIBLE_(__VA_ARGS__, 0, ) + +#define THREAD_INFALLIBLE_(expr, allowed, ...) \ + int err = expr; \ + bfs_verify(err == 0 || err == allowed, "%s: %s", #expr, xstrerror(err)); \ + (void)0 + +int thread_create(pthread_t *thread, const pthread_attr_t *attr, thread_fn *fn, void *arg) { + THREAD_FALLIBLE(pthread_create(thread, attr, fn, arg)); +} + +void thread_join(pthread_t thread, void **ret) { + THREAD_INFALLIBLE(pthread_join(thread, ret)); +} + +int mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr) { + THREAD_FALLIBLE(pthread_mutex_init(mutex, attr)); +} + +void mutex_lock(pthread_mutex_t *mutex) { + THREAD_INFALLIBLE(pthread_mutex_lock(mutex)); +} + +bool mutex_trylock(pthread_mutex_t *mutex) { + THREAD_INFALLIBLE(pthread_mutex_trylock(mutex), EBUSY); + return err == 0; +} + +void mutex_unlock(pthread_mutex_t *mutex) { + THREAD_INFALLIBLE(pthread_mutex_unlock(mutex)); +} + +void mutex_destroy(pthread_mutex_t *mutex) { + THREAD_INFALLIBLE(pthread_mutex_destroy(mutex)); +} + +int cond_init(pthread_cond_t *cond, pthread_condattr_t *attr) { + THREAD_FALLIBLE(pthread_cond_init(cond, attr)); +} + +void cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex) { + THREAD_INFALLIBLE(pthread_cond_wait(cond, mutex)); +} + +void cond_signal(pthread_cond_t *cond) { + THREAD_INFALLIBLE(pthread_cond_signal(cond)); +} + +void cond_broadcast(pthread_cond_t *cond) { + THREAD_INFALLIBLE(pthread_cond_broadcast(cond)); +} + +void cond_destroy(pthread_cond_t *cond) { + THREAD_INFALLIBLE(pthread_cond_destroy(cond)); +} + +void invoke_once(pthread_once_t *once, once_fn *fn) { + THREAD_INFALLIBLE(pthread_once(once, fn)); +} diff --git a/src/thread.h b/src/thread.h index a59033c..b37d45f 100644 --- a/src/thread.h +++ b/src/thread.h @@ -8,12 +8,8 @@ #ifndef BFS_THREAD_H #define BFS_THREAD_H -#include "bfstd.h" #include "config.h" -#include "diag.h" -#include #include -#include #if __STDC_VERSION__ < 202311L && !defined(thread_local) # if BFS_USE_THREADS_H @@ -23,8 +19,8 @@ # endif #endif -#define thread_verify(expr, cond) \ - bfs_verify((errno = (expr), (cond)), "%s: %s", #expr, xstrerror(errno)) +/** Thread entry point type. */ +typedef void *thread_fn(void *arg); /** * Wrapper for pthread_create(). @@ -32,26 +28,22 @@ * @return * 0 on success, -1 on error. */ -#define thread_create(thread, attr, fn, arg) \ - ((errno = pthread_create(thread, attr, fn, arg)) ? -1 : 0) +int thread_create(pthread_t *thread, const pthread_attr_t *attr, thread_fn *fn, void *arg); /** * Wrapper for pthread_join(). */ -#define thread_join(thread, ret) \ - thread_verify(pthread_join(thread, ret), errno == 0) +void thread_join(pthread_t thread, void **ret); /** * Wrapper for pthread_mutex_init(). */ -#define mutex_init(mutex, attr) \ - ((errno = pthread_mutex_init(mutex, attr)) ? -1 : 0) +int mutex_init(pthread_mutex_t *mutex, pthread_mutexattr_t *attr); /** * Wrapper for pthread_mutex_lock(). */ -#define mutex_lock(mutex) \ - thread_verify(pthread_mutex_lock(mutex), errno == 0) +void mutex_lock(pthread_mutex_t *mutex); /** * Wrapper for pthread_mutex_trylock(). @@ -59,55 +51,49 @@ * @return * Whether the mutex was locked. */ -#define mutex_trylock(mutex) \ - (thread_verify(pthread_mutex_trylock(mutex), errno == 0 || errno == EBUSY), errno == 0) +bool mutex_trylock(pthread_mutex_t *mutex); /** * Wrapper for pthread_mutex_unlock(). */ -#define mutex_unlock(mutex) \ - thread_verify(pthread_mutex_unlock(mutex), errno == 0) +void mutex_unlock(pthread_mutex_t *mutex); /** * Wrapper for pthread_mutex_destroy(). */ -#define mutex_destroy(mutex) \ - thread_verify(pthread_mutex_destroy(mutex), errno == 0) +void mutex_destroy(pthread_mutex_t *mutex); /** * Wrapper for pthread_cond_init(). */ -#define cond_init(cond, attr) \ - ((errno = pthread_cond_init(cond, attr)) ? -1 : 0) +int cond_init(pthread_cond_t *cond, pthread_condattr_t *attr); /** * Wrapper for pthread_cond_wait(). */ -#define cond_wait(cond, mutex) \ - thread_verify(pthread_cond_wait(cond, mutex), errno == 0) +void cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex); /** * Wrapper for pthread_cond_signal(). */ -#define cond_signal(cond) \ - thread_verify(pthread_cond_signal(cond), errno == 0) +void cond_signal(pthread_cond_t *cond); /** * Wrapper for pthread_cond_broadcast(). */ -#define cond_broadcast(cond) \ - thread_verify(pthread_cond_broadcast(cond), errno == 0) +void cond_broadcast(pthread_cond_t *cond); /** * Wrapper for pthread_cond_destroy(). */ -#define cond_destroy(cond) \ - thread_verify(pthread_cond_destroy(cond), errno == 0) +void cond_destroy(pthread_cond_t *cond); + +/** pthread_once() callback type. */ +typedef void once_fn(void); /** * Wrapper for pthread_once(). */ -#define invoke_once(once, fn) \ - thread_verify(pthread_once(once, fn), errno == 0) +void invoke_once(pthread_once_t *once, once_fn *fn); #endif // BFS_THREAD_H -- cgit v1.2.3 From 06ee53d5b1fc58073aaa3d57f6073256d13502f2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 31 Oct 2023 12:30:07 -0400 Subject: bftw: Leave work for the main thread if profitable --- src/bftw.c | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index dff949f..3982515 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -627,6 +627,13 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return op; } +/** Check if we should block on the I/O queue even if not strictly necessary. */ +static bool bftw_ioq_block(const struct bftw_state *state) { + // With more than two threads, it is faster to wait for an I/O + // operation to complete than it is to do it ourselves + return state->nthreads > 2; +} + /** Try to reserve space in the I/O queue. */ static int bftw_ioq_reserve(struct bftw_state *state) { struct ioq *ioq = state->ioq; @@ -638,10 +645,7 @@ static int bftw_ioq_reserve(struct bftw_state *state) { return 0; } - // With more than two threads, it is faster to wait for an I/O operation - // to complete than it is to do it ourselves - bool block = state->nthreads > 2; - if (bftw_ioq_pop(state, block) < 0) { + if (bftw_ioq_pop(state, bftw_ioq_block(state)) < 0) { return -1; } @@ -895,6 +899,11 @@ static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) break; } SLIST_POP(queue); + + if (!bftw_ioq_block(state)) { + // Leave some work for the main thread + break; + } } } -- cgit v1.2.3 From 8aaf670c13ade259f7bcdd50332968c1c6290b34 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 31 Oct 2023 13:25:04 -0400 Subject: bfstd: New xwaitpid() wrapper --- src/bfstd.c | 9 +++++++++ src/bfstd.h | 7 +++++++ src/exec.c | 2 +- src/parse.c | 2 +- src/xspawn.c | 2 +- 5 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index eee02b8..06226a2 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -391,6 +392,14 @@ int xminor(dev_t dev) { #endif } +pid_t xwaitpid(pid_t pid, int *status, int flags) { + pid_t ret; + do { + ret = waitpid(pid, status, flags); + } while (ret < 0 && errno == EINTR); + return ret; +} + 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 db558c6..4e36aca 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -213,6 +213,13 @@ int xminor(dev_t dev); # define st_birthtim st_birthtimespec #endif +// #include + +/** + * waitpid() wrapper that handles EINTR. + */ +pid_t xwaitpid(pid_t pid, int *status, int flags); + // #include /** diff --git a/src/exec.c b/src/exec.c index 0e0d585..97cfafa 100644 --- a/src/exec.c +++ b/src/exec.c @@ -390,7 +390,7 @@ fail: } int wstatus; - if (waitpid(pid, &wstatus, 0) < 0) { + if (xwaitpid(pid, &wstatus, 0) < 0) { return -1; } diff --git a/src/parse.c b/src/parse.c index fafd787..3f32021 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2980,7 +2980,7 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg if (pager > 0) { cfclose(cout); - waitpid(pager, NULL, 0); + xwaitpid(pager, NULL, 0); } state->just_info = true; diff --git a/src/xspawn.c b/src/xspawn.c index 7fb63e0..64759e0 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -234,7 +234,7 @@ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char xclose(pipefd[0]); if (nbytes == sizeof(error)) { int wstatus; - waitpid(pid, &wstatus, 0); + xwaitpid(pid, &wstatus, 0); errno = error; return -1; } -- cgit v1.2.3 From b2ab7a151fca517f4879e76e626ec85ad3de97c7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 1 Nov 2023 11:18:39 -0400 Subject: bftw: Improve ioq balancing logic --- src/bftw.c | 35 ++++++++++++++++++++++------------- 1 file changed, 22 insertions(+), 13 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 3982515..5a55037 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -430,6 +430,8 @@ struct bftw_state { struct ioq *ioq; /** The number of I/O threads. */ size_t nthreads; + /** Tracks the imbalance between main thread and background I/O. */ + long imbalance; /** A batch of directories to open. */ struct bftw_list dir_batch; @@ -541,6 +543,8 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->dir_flags |= BFS_DIR_WHITEOUTS; } + state->imbalance = 0; + SLIST_INIT(&state->dir_batch); SLIST_INIT(&state->to_open); SLIST_INIT(&state->to_read); @@ -571,6 +575,12 @@ static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, boo } } +/** Adjust the I/O queue balance. */ +static void bftw_ioq_balance(struct bftw_state *state, long delta) { + // Avoid signed overflow + state->imbalance = (unsigned long)state->imbalance + (unsigned long)delta; +} + /** Pop a response from the I/O queue. */ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct ioq *ioq = state->ioq; @@ -627,13 +637,6 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return op; } -/** Check if we should block on the I/O queue even if not strictly necessary. */ -static bool bftw_ioq_block(const struct bftw_state *state) { - // With more than two threads, it is faster to wait for an I/O - // operation to complete than it is to do it ourselves - return state->nthreads > 2; -} - /** Try to reserve space in the I/O queue. */ static int bftw_ioq_reserve(struct bftw_state *state) { struct ioq *ioq = state->ioq; @@ -641,11 +644,19 @@ static int bftw_ioq_reserve(struct bftw_state *state) { return -1; } + // With only one background thread, we should balance I/O between it and + // the main thread. With more than one background thread, it's faster + // to wait on background I/O than it is to do it on the main thread. + bool balance = state->nthreads <= 1; + if (balance && state->imbalance < 0) { + return -1; + } + if (ioq_capacity(ioq) > 0) { return 0; } - if (bftw_ioq_pop(state, bftw_ioq_block(state)) < 0) { + if (bftw_ioq_pop(state, !balance) < 0) { return -1; } @@ -880,6 +891,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { file->ioqueued = true; --cache->capacity; + bftw_ioq_balance(state, -1); return 0; free: @@ -899,11 +911,6 @@ static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) break; } SLIST_POP(queue); - - if (!bftw_ioq_block(state)) { - // Leave some work for the main thread - break; - } } } @@ -1041,6 +1048,8 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f return NULL; } + bftw_ioq_balance(state, +1); + if (bfs_opendir(dir, fd, NULL, state->dir_flags) != 0) { bftw_freedir(cache, dir); return NULL; -- cgit v1.2.3 From 1cfa66a4268a4eefea29f207baaefd76d7792027 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 2 Nov 2023 11:29:28 -0400 Subject: tests: Add tests for ulimit -n after -exec --- tests/gnu/execdir_ulimit.out | 16 ++++++++++++++++ tests/gnu/execdir_ulimit.sh | 2 ++ tests/posix/exec_ulimit.out | 16 ++++++++++++++++ tests/posix/exec_ulimit.sh | 2 ++ 4 files changed, 36 insertions(+) create mode 100644 tests/gnu/execdir_ulimit.out create mode 100644 tests/gnu/execdir_ulimit.sh create mode 100644 tests/posix/exec_ulimit.out create mode 100644 tests/posix/exec_ulimit.sh diff --git a/tests/gnu/execdir_ulimit.out b/tests/gnu/execdir_ulimit.out new file mode 100644 index 0000000..7931f9a --- /dev/null +++ b/tests/gnu/execdir_ulimit.out @@ -0,0 +1,16 @@ +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE diff --git a/tests/gnu/execdir_ulimit.sh b/tests/gnu/execdir_ulimit.sh new file mode 100644 index 0000000..3245dad --- /dev/null +++ b/tests/gnu/execdir_ulimit.sh @@ -0,0 +1,2 @@ +ulimit -Sn 16 +bfs_diff deep -type f -execdir bash -c 'printf "%d %s\n" $(ulimit -Sn) "$1"' bash {} \; diff --git a/tests/posix/exec_ulimit.out b/tests/posix/exec_ulimit.out new file mode 100644 index 0000000..587ece2 --- /dev/null +++ b/tests/posix/exec_ulimit.out @@ -0,0 +1,16 @@ +16 deep/0/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/1/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/2/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/3/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/4/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/5/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/6/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/7/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/8/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/9/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/A/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/B/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/C/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/D/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/E/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +16 deep/F/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE diff --git a/tests/posix/exec_ulimit.sh b/tests/posix/exec_ulimit.sh new file mode 100644 index 0000000..8ac8ff1 --- /dev/null +++ b/tests/posix/exec_ulimit.sh @@ -0,0 +1,2 @@ +ulimit -Sn 16 +bfs_diff deep -type f -exec bash -c 'printf "%d %s\n" $(ulimit -Sn) "${1:0:6}/.../${1##*/}"' bash {} \; -- cgit v1.2.3 From 5fb1ed2d9803bfa5eefdc49ae232f9ea4afff018 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 2 Nov 2023 11:53:50 -0400 Subject: bench: New -exec benchmarks --- bench/bench.sh | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 89 insertions(+), 3 deletions(-) diff --git a/bench/bench.sh b/bench/bench.sh index b1d52bb..3436b19 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -20,10 +20,11 @@ EARLY_QUIT_DEFAULT=(chromium) PRINT_DEFAULT=(linux) STRATEGIES_DEFAULT=(rust) JOBS_DEFAULT=(rust) +EXEC_DEFAULT=(linux) usage() { - printf 'Usage: tailfin run %s [--default]\n' "${BASH_SOURCE[0]}" - printf ' [--complete] [--early-quit] [--print] [--strategies]\n' + printf 'Usage: tailfin run %s\n' "${BASH_SOURCE[0]}" + printf ' [--default] [-- [--...]]\n' printf ' [--build=...] [--bfs] [--find] [--fd]\n' printf ' [--no-clean] [--help]\n\n' @@ -50,6 +51,10 @@ usage() { printf ' Parallelism benchmark.\n' printf ' Default corpus is --jobs=%s\n\n' "${JOBS_DEFAULT[*]}" + printf ' --exec[=CORPUS]\n' + printf ' Process spawning benchmark.\n' + printf ' Default corpus is --exec=%s\n\n' "${EXEC_DEFAULT[*]}" + printf ' --build=COMMIT\n' printf ' Build this bfs commit and benchmark it. Specify multiple times to\n' printf ' compare, e.g. --build=3.0.1 --build=3.0.2\n\n' @@ -109,6 +114,7 @@ setup() { PRINT=() STRATEGIES=() JOBS=() + EXEC=() for arg; do case "$arg" in @@ -171,12 +177,19 @@ setup() { --jobs=*) read -ra JOBS <<<"${arg#*=}" ;; + --exec) + EXEC=("${EXEC_DEFAULT[@]}") + ;; + --exec=*) + read -ra EXEC <<<"${arg#*=}" + ;; --default) COMPLETE=("${COMPLETE_DEFAULT[@]}") EARLY_QUIT=("${EARLY_QUIT_DEFAULT[@]}") PRINT=("${PRINT_DEFAULT[@]}") STRATEGIES=("${STRATEGIES_DEFAULT[@]}") JOBS=("${JOBS_DEFAULT[@]}") + EXEC=("${EXEC_DEFAULT[@]}") ;; --help) usage @@ -200,7 +213,7 @@ setup() { as-user mkdir -p bench/corpus declare -A cloned=() - for corpus in "${COMPLETE[@]}" "${EARLY_QUIT[@]}" "${PRINT[@]}" "${STRATEGIES[@]}" "${JOBS[@]}"; do + for corpus in "${COMPLETE[@]}" "${EARLY_QUIT[@]}" "${PRINT[@]}" "${STRATEGIES[@]}" "${JOBS[@]}" "${EXEC[@]}"; do if ((cloned["$corpus"])); then continue fi @@ -254,6 +267,7 @@ setup() { export_array PRINT export_array STRATEGIES export_array JOBS + export_array EXEC if ((UID == 0)); then turbo-off @@ -518,6 +532,76 @@ bench-jobs() { fi } +# One file/process +bench-exec-single() { + subsubgroup "One file per process" + + cmds=() + for cmd in "${BFS[@]}" "${FIND[@]}"; do + cmds+=("$cmd $1 -maxdepth 2 -exec true -- {} \;") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -u --search-path $1 --max-depth=2 -x true --") + # Without -j1, fd runs multiple processes in parallel, which is unfair + cmds+=("$fd -j1 -u --search-path $1 --max-depth=2 -x true --") + done + + do-hyperfine "${cmds[@]}" +} + +# Many files/process +bench-exec-multi() { + subsubgroup "Many files per process" + + cmds=() + for cmd in "${BFS[@]}" "${FIND[@]}"; do + cmds+=("$cmd $1 -exec true -- {} +") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -u --search-path $1 -X true --") + done + + do-hyperfine "${cmds[@]}" +} + +# Many files, same dir +bench-exec-chdir() { + if ((${#BFS[@]} + ${#FIND[@]} == 0)); then + return + fi + + subsubgroup "Spawn in parent directory" + + cmds=() + for cmd in "${BFS[@]}" "${FIND[@]}"; do + cmds+=("$cmd $1 -maxdepth 3 -execdir true -- {} +") + done + + do-hyperfine "${cmds[@]}" +} + +# Benchmark process spawning +bench-exec-corpus() { + subgroup '%s' "$1" + + bench-exec-single "$2" + bench-exec-multi "$2" + bench-exec-chdir "$2" +} + +# All process spawning benchmarks +bench-exec() { + if (($#)); then + group "Process spawning" + + for corpus; do + bench-exec-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" + done + fi +} + # Print benchmarked versions bench-versions() { subgroup "Versions" @@ -564,11 +648,13 @@ bench() { import_array PRINT import_array STRATEGIES import_array JOBS + import_array EXEC bench-complete "${COMPLETE[@]}" bench-early-quit "${EARLY_QUIT[@]}" bench-print "${PRINT[@]}" bench-strategies "${STRATEGIES[@]}" bench-jobs "${JOBS[@]}" + bench-exec "${EXEC[@]}" bench-details } -- cgit v1.2.3 From 816574513e0e163aff9f183721697f157eb158fa Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 6 Nov 2023 10:03:43 -0500 Subject: bfstd: Expose rlim_cmp() --- src/bfstd.c | 33 +++++++++++++++++++++++++++++++++ src/bfstd.h | 7 +++++++ src/eval.c | 33 --------------------------------- 3 files changed, 40 insertions(+), 33 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 06226a2..985a268 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -18,6 +18,7 @@ #include #include #include +#include #include #include #include @@ -368,6 +369,38 @@ void xstrmode(mode_t mode, char str[11]) { } } +/** Check if an rlimit value is infinite. */ +static bool rlim_isinf(rlim_t r) { + // Consider RLIM_{INFINITY,SAVED_{CUR,MAX}} all equally infinite + if (r == RLIM_INFINITY) { + return true; + } + +#ifdef RLIM_SAVED_CUR + if (r == RLIM_SAVED_CUR) { + return true; + } +#endif + +#ifdef RLIM_SAVED_MAX + if (r == RLIM_SAVED_MAX) { + return true; + } +#endif + + return false; +} + +int rlim_cmp(rlim_t a, rlim_t b) { + bool a_inf = rlim_isinf(a); + bool b_inf = rlim_isinf(b); + if (a_inf || b_inf) { + return a_inf - b_inf; + } + + return (a > b) - (a < b); +} + dev_t xmakedev(int ma, int mi) { #ifdef makedev return makedev(ma, mi); diff --git a/src/bfstd.h b/src/bfstd.h index 4e36aca..6cb2d7b 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -187,6 +187,13 @@ const char *xstrerror(int errnum); */ void xstrmode(mode_t mode, char str[11]); +#include + +/** + * Compare two rlim_t values, accounting for infinite limits. + */ +int rlim_cmp(rlim_t a, rlim_t b); + #include /** diff --git a/src/eval.c b/src/eval.c index 56d7cd8..5ba3de8 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1427,39 +1427,6 @@ done: return state.action; } -/** Check if an rlimit value is infinite. */ -static bool rlim_isinf(rlim_t r) { - // Consider RLIM_{INFINITY,SAVED_{CUR,MAX}} all equally infinite - if (r == RLIM_INFINITY) { - return true; - } - -#ifdef RLIM_SAVED_CUR - if (r == RLIM_SAVED_CUR) { - return true; - } -#endif - -#ifdef RLIM_SAVED_MAX - if (r == RLIM_SAVED_MAX) { - return true; - } -#endif - - return false; -} - -/** Compare two rlimit values, accounting for RLIM_INFINITY etc. */ -static int rlim_cmp(rlim_t a, rlim_t b) { - bool a_inf = rlim_isinf(a); - bool b_inf = rlim_isinf(b); - if (a_inf || b_inf) { - return a_inf - b_inf; - } - - return (a > b) - (a < b); -} - /** Raise RLIMIT_NOFILE if possible, and return the new limit. */ static int raise_fdlimit(const struct bfs_ctx *ctx) { rlim_t target = 64 << 10; -- cgit v1.2.3 From ad1b36291fe87ca9d647980e3afdc71344d66519 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 6 Nov 2023 10:06:41 -0500 Subject: ctx: Store the original and current RLIMIT_NOFILE --- src/ctx.c | 6 ++---- src/ctx.h | 8 ++++---- src/eval.c | 33 +++++++++++++++++++-------------- src/eval.h | 2 +- src/exec.c | 26 ++++++++++++-------------- 5 files changed, 38 insertions(+), 37 deletions(-) diff --git a/src/ctx.c b/src/ctx.c index 561ecae..692f5c5 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -30,12 +30,10 @@ struct bfs_ctx *bfs_ctx_new(void) { trie_init(&ctx->files); - struct rlimit rl; - if (getrlimit(RLIMIT_NOFILE, &rl) != 0) { + if (getrlimit(RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { goto fail; } - ctx->nofile_soft = rl.rlim_cur; - ctx->nofile_hard = rl.rlim_max; + ctx->cur_nofile = ctx->orig_nofile; ctx->users = bfs_users_new(); if (!ctx->users) { diff --git a/src/ctx.h b/src/ctx.h index 96406bd..1c7df63 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -87,10 +87,10 @@ struct bfs_ctx { /** The number of files owned by the context. */ int nfiles; - /** The initial RLIMIT_NOFILE soft limit. */ - rlim_t nofile_soft; - /** The initial RLIMIT_NOFILE hard limit. */ - rlim_t nofile_hard; + /** The initial RLIMIT_NOFILE limits. */ + struct rlimit orig_nofile; + /** The current RLIMIT_NOFILE limits. */ + struct rlimit cur_nofile; /** The current time. */ struct timespec now; diff --git a/src/eval.c b/src/eval.c index 5ba3de8..b511eba 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1428,25 +1428,30 @@ done: } /** Raise RLIMIT_NOFILE if possible, and return the new limit. */ -static int raise_fdlimit(const struct bfs_ctx *ctx) { +static int raise_fdlimit(struct bfs_ctx *ctx) { + rlim_t cur = ctx->orig_nofile.rlim_cur; + rlim_t max = ctx->orig_nofile.rlim_max; + rlim_t target = 64 << 10; - if (rlim_cmp(target, ctx->nofile_hard) > 0) { - target = ctx->nofile_hard; + if (rlim_cmp(target, max) > 0) { + target = max; } - int ret = target; + if (rlim_cmp(target, cur) <= 0) { + return target; + } - if (rlim_cmp(target, ctx->nofile_soft) > 0) { - const struct rlimit rl = { - .rlim_cur = target, - .rlim_max = ctx->nofile_hard, - }; - if (setrlimit(RLIMIT_NOFILE, &rl) != 0) { - ret = ctx->nofile_soft; - } + const struct rlimit rl = { + .rlim_cur = target, + .rlim_max = max, + }; + + if (setrlimit(RLIMIT_NOFILE, &rl) != 0) { + return cur; } - return ret; + ctx->cur_nofile = rl; + return target; } /** Preallocate the fd table in the kernel. */ @@ -1581,7 +1586,7 @@ static bool eval_must_buffer(const struct bfs_expr *expr) { return false; } -int bfs_eval(const struct bfs_ctx *ctx) { +int bfs_eval(struct bfs_ctx *ctx) { if (!ctx->expr) { return EXIT_SUCCESS; } diff --git a/src/eval.h b/src/eval.h index bdb9440..98bbc08 100644 --- a/src/eval.h +++ b/src/eval.h @@ -39,7 +39,7 @@ typedef bool bfs_eval_fn(const struct bfs_expr *expr, struct bfs_eval *state); * @return * EXIT_SUCCESS on success, otherwise on failure. */ -int bfs_eval(const struct bfs_ctx *ctx); +int bfs_eval(struct bfs_ctx *ctx); // Predicate evaluation functions diff --git a/src/exec.c b/src/exec.c index 97cfafa..a3297e8 100644 --- a/src/exec.c +++ b/src/exec.c @@ -327,8 +327,10 @@ static void bfs_exec_closewd(struct bfs_exec *execbuf, const struct BFTW *ftwbuf /** Actually spawn the process. */ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { + const struct bfs_ctx *ctx = execbuf->ctx; + // Flush the context state for consistency with the external process - bfs_ctx_flush(execbuf->ctx); + bfs_ctx_flush(ctx); if (execbuf->flags & BFS_EXEC_CONFIRM) { for (size_t i = 0; i < execbuf->argc; ++i) { @@ -356,34 +358,30 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { pid_t pid = -1; int error; - struct bfs_spawn ctx; - if (bfs_spawn_init(&ctx) != 0) { + struct bfs_spawn spawn; + if (bfs_spawn_init(&spawn) != 0) { return -1; } - if (bfs_spawn_setflags(&ctx, BFS_SPAWN_USEPATH) != 0) { + if (bfs_spawn_setflags(&spawn, BFS_SPAWN_USEPATH) != 0) { goto fail; } // Reset RLIMIT_NOFILE, to avoid breaking applications that use select() - struct rlimit rl = { - .rlim_cur = execbuf->ctx->nofile_soft, - .rlim_max = execbuf->ctx->nofile_hard, - }; - if (bfs_spawn_addsetrlimit(&ctx, RLIMIT_NOFILE, &rl) != 0) { + if (bfs_spawn_addsetrlimit(&spawn, RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { goto fail; } if (execbuf->wd_fd >= 0) { - if (bfs_spawn_addfchdir(&ctx, execbuf->wd_fd) != 0) { + if (bfs_spawn_addfchdir(&spawn, execbuf->wd_fd) != 0) { goto fail; } } - pid = bfs_spawn(execbuf->argv[0], &ctx, execbuf->argv, NULL); + pid = bfs_spawn(execbuf->argv[0], &spawn, execbuf->argv, NULL); fail: error = errno; - bfs_spawn_destroy(&ctx); + bfs_spawn_destroy(&spawn); if (pid < 0) { errno = error; return -1; @@ -409,9 +407,9 @@ fail: if (!str) { str = "unknown"; } - bfs_warning(execbuf->ctx, "Command '${ex}%s${rs}' terminated by signal %d (%s)\n", execbuf->argv[0], sig, str); + bfs_warning(ctx, "Command '${ex}%s${rs}' terminated by signal %d (%s)\n", execbuf->argv[0], sig, str); } else { - bfs_warning(execbuf->ctx, "Command '${ex}%s${rs}' terminated abnormally\n", execbuf->argv[0]); + bfs_warning(ctx, "Command '${ex}%s${rs}' terminated abnormally\n", execbuf->argv[0]); } errno = 0; -- cgit v1.2.3 From 95fbde17a66377b6fbe7ff1f014301dbbf09270d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 2 Nov 2023 11:54:03 -0400 Subject: xspawn: Wrap the real posix_spawn() if possible Fixes #47. --- src/exec.c | 2 +- src/xspawn.c | 167 +++++++++++++++++++++++++++++++++++++++++++---------------- src/xspawn.h | 15 +++++- 3 files changed, 136 insertions(+), 48 deletions(-) diff --git a/src/exec.c b/src/exec.c index a3297e8..e3f7b97 100644 --- a/src/exec.c +++ b/src/exec.c @@ -363,7 +363,7 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { return -1; } - if (bfs_spawn_setflags(&spawn, BFS_SPAWN_USEPATH) != 0) { + if (bfs_spawn_setflags(&spawn, BFS_SPAWN_USE_PATH) != 0) { goto fail; } diff --git a/src/xspawn.c b/src/xspawn.c index 64759e0..ac9e401 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -8,6 +8,7 @@ #include "list.h" #include #include +#include #include #include #include @@ -43,12 +44,27 @@ struct bfs_spawn_action { }; int bfs_spawn_init(struct bfs_spawn *ctx) { - ctx->flags = 0; + ctx->flags = BFS_SPAWN_USE_POSIX; SLIST_INIT(ctx); + + errno = posix_spawnattr_init(&ctx->attr); + if (errno != 0) { + return -1; + } + + errno = posix_spawn_file_actions_init(&ctx->actions); + if (errno != 0) { + posix_spawnattr_destroy(&ctx->attr); + return -1; + } + return 0; } int bfs_spawn_destroy(struct bfs_spawn *ctx) { + posix_spawn_file_actions_destroy(&ctx->actions); + posix_spawnattr_destroy(&ctx->attr); + for_slist (struct bfs_spawn_action, action, ctx) { free(action); } @@ -57,12 +73,12 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) { } int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { - ctx->flags = flags; + ctx->flags |= flags; return 0; } -/** Add a spawn action to the chain. */ -static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_spawn_op op) { +/** Allocate a spawn action. */ +static struct bfs_spawn_action *bfs_spawn_action(enum bfs_spawn_op op) { struct bfs_spawn_action *action = ALLOC(struct bfs_spawn_action); if (!action) { return NULL; @@ -72,70 +88,122 @@ static struct bfs_spawn_action *bfs_spawn_add(struct bfs_spawn *ctx, enum bfs_sp action->op = op; action->in_fd = -1; action->out_fd = -1; - - SLIST_APPEND(ctx, action); return action; } int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { - if (fd < 0) { - errno = EBADF; + struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_CLOSE); + if (!action) { return -1; } - struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_CLOSE); - if (action) { - action->out_fd = fd; - return 0; - } else { - return -1; + if (ctx->flags & BFS_SPAWN_USE_POSIX) { + errno = posix_spawn_file_actions_addclose(&ctx->actions, fd); + if (errno != 0) { + free(action); + return -1; + } } + + action->out_fd = fd; + SLIST_APPEND(ctx, action); + return 0; } int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { - if (oldfd < 0 || newfd < 0) { - errno = EBADF; + struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_DUP2); + if (!action) { return -1; } - struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_DUP2); - if (action) { - action->in_fd = oldfd; - action->out_fd = newfd; - return 0; - } else { - return -1; + if (ctx->flags & BFS_SPAWN_USE_POSIX) { + errno = posix_spawn_file_actions_adddup2(&ctx->actions, oldfd, newfd); + if (errno != 0) { + free(action); + return -1; + } } + + action->in_fd = oldfd; + action->out_fd = newfd; + SLIST_APPEND(ctx, action); + return 0; } int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { - if (fd < 0) { - errno = EBADF; + struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR); + if (!action) { return -1; } - struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_FCHDIR); - if (action) { - action->in_fd = fd; - return 0; - } else { - return -1; +#ifndef BFS_HAS_POSIX_SPAWN_FCHDIR +# define BFS_HAS_POSIX_SPAWN_FCHDIR __NetBSD__ +#endif + +#ifndef BFS_HAS_POSIX_SPAWN_FCHDIR_NP +# if __GLIBC__ +# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP __GLIBC_PREREQ(2, 29) +# elif __ANDROID__ +# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP (__ANDROID_API__ >= 34) +# else +# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP (__linux__ || __FreeBSD__ || __APPLE__) +# endif +#endif + +#if BFS_HAS_POSIX_SPAWN_FCHDIR || BFS_HAS_POSIX_SPAWN_FCHDIR_NP + if (ctx->flags & BFS_SPAWN_USE_POSIX) { +# if BFS_HAS_POSIX_SPAWN_FCHDIR + errno = posix_spawn_file_actions_addfchdir(&ctx->actions, fd); +# else + errno = posix_spawn_file_actions_addfchdir_np(&ctx->actions, fd); +# endif + if (errno != 0) { + free(action); + return -1; + } } +#else + ctx->flags &= ~BFS_SPAWN_USE_POSIX; +#endif + + action->in_fd = fd; + SLIST_APPEND(ctx, action); + return 0; } int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) { - struct bfs_spawn_action *action = bfs_spawn_add(ctx, BFS_SPAWN_SETRLIMIT); - if (action) { - action->resource = resource; - action->rlimit = *rl; - return 0; - } else { + struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_SETRLIMIT); + if (!action) { return -1; } + + ctx->flags &= ~BFS_SPAWN_USE_POSIX; + + action->resource = resource; + action->rlimit = *rl; + SLIST_APPEND(ctx, action); + return 0; +} + +/** bfs_spawn() implementation using posix_spawn(). */ +static pid_t bfs_posix_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { + pid_t ret; + + if (ctx->flags & BFS_SPAWN_USE_PATH) { + errno = posix_spawnp(&ret, exe, &ctx->actions, &ctx->attr, argv, envp); + } else { + errno = posix_spawn(&ret, exe, &ctx->actions, &ctx->attr, argv, envp); + } + + if (errno != 0) { + ret = -1; + } + + return ret; } /** Actually exec() the new process. */ -static void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { +static noreturn void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { xclose(pipefd[0]); for_slist (const struct bfs_spawn_action, action, ctx) { @@ -193,14 +261,10 @@ fail: _Exit(127); } -pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { - extern char **environ; - if (!envp) { - envp = environ; - } - +/** bfs_spawn() implementation using fork()/exec(). */ +static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { char *resolved = NULL; - if (ctx->flags & BFS_SPAWN_USEPATH) { + if (ctx->flags & BFS_SPAWN_USE_PATH) { exe = resolved = bfs_spawn_resolve(exe); if (!resolved) { return -1; @@ -242,6 +306,19 @@ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char return pid; } +pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { + extern char **environ; + if (!envp) { + envp = environ; + } + + if (ctx->flags & BFS_SPAWN_USE_POSIX) { + return bfs_posix_spawn(exe, ctx, argv, envp); + } else { + return bfs_fork_spawn(exe, ctx, argv, envp); + } +} + char *bfs_spawn_resolve(const char *exe) { if (strchr(exe, '/')) { return strdup(exe); diff --git a/src/xspawn.h b/src/xspawn.h index d9b4a2e..2a3d736 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -8,6 +8,8 @@ #ifndef BFS_XSPAWN_H #define BFS_XSPAWN_H +#include "config.h" +#include #include #include @@ -16,16 +18,25 @@ */ enum bfs_spawn_flags { /** Use the PATH variable to resolve the executable (like execvp()). */ - BFS_SPAWN_USEPATH = 1 << 0, + BFS_SPAWN_USE_PATH = 1 << 0, + /** Whether posix_spawn() can be used. */ + BFS_SPAWN_USE_POSIX = 1 << 1, }; /** * bfs_spawn() attributes, controlling the context of the new process. */ struct bfs_spawn { + /** Spawn flags. */ enum bfs_spawn_flags flags; + + /** Linked list of actions. */ struct bfs_spawn_action *head; struct bfs_spawn_action **tail; + + /** pthread_spawn() context, for when we can use it. */ + posix_spawnattr_t attr; + posix_spawn_file_actions_t actions; }; /** @@ -95,7 +106,7 @@ int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rli pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp); /** - * Look up an executable in the current PATH, as BFS_SPAWN_USEPATH or execvp() + * Look up an executable in the current PATH, as BFS_SPAWN_USE_PATH or execvp() * would do. * * @param exe -- cgit v1.2.3 From 7d69fef6a0b80ad57acde7bac8a22b83531fec0d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 6 Nov 2023 10:11:46 -0500 Subject: exec: Try harder to use posix_spawn() --- src/exec.c | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/exec.c b/src/exec.c index e3f7b97..f0730d2 100644 --- a/src/exec.c +++ b/src/exec.c @@ -357,6 +357,7 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { pid_t pid = -1; int error; + bool reset_nofile = false; struct bfs_spawn spawn; if (bfs_spawn_init(&spawn) != 0) { @@ -367,20 +368,40 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { goto fail; } - // Reset RLIMIT_NOFILE, to avoid breaking applications that use select() - if (bfs_spawn_addsetrlimit(&spawn, RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { - goto fail; - } - if (execbuf->wd_fd >= 0) { if (bfs_spawn_addfchdir(&spawn, execbuf->wd_fd) != 0) { goto fail; } } + // Reset RLIMIT_NOFILE if necessary, to avoid breaking applications that use select() + if (rlim_cmp(ctx->orig_nofile.rlim_cur, ctx->cur_nofile.rlim_cur) < 0) { + // posix_spawn() doesn't have a setrlimit() action, so adding one would force us + // to use the slower fork()/exec() path. Instead, drop the rlimit temporarily in + // the parent. This can race with other threads, but we always recover from + // EMFILE in the main thread anyway. + if (spawn.flags & BFS_SPAWN_USE_POSIX) { + if (setrlimit(RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { + goto fail; + } + reset_nofile = true; + } else { + if (bfs_spawn_addsetrlimit(&spawn, RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { + goto fail; + } + } + } + pid = bfs_spawn(execbuf->argv[0], &spawn, execbuf->argv, NULL); fail: error = errno; + + if (reset_nofile) { + if (setrlimit(RLIMIT_NOFILE, &ctx->cur_nofile) != 0) { + bfs_bug("setrlimit(RLIMIT_NOFILE): %s", xstrerror(errno)); + } + } + bfs_spawn_destroy(&spawn); if (pid < 0) { errno = error; -- cgit v1.2.3 From 79aee58a4621d01c4b1e98c332775f3b87213ddb Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 6 Nov 2023 13:08:56 -0500 Subject: Treat NO_COLOR="" the same as unset The docs say > Command-line software which adds ANSI color to its output by default > should check for a NO_COLOR environment variable that, when present > and not an empty string (regardless of its value), prevents the > addition of ANSI color. but we were not checking for the empty string. Link: https://no-color.org/ Link: https://github.com/sharkdp/fd/pull/1421 --- src/parse.c | 3 ++- tests/bfs/nocolor_env_empty.out | 27 +++++++++++++++++++++++++++ tests/bfs/nocolor_env_empty.sh | 5 +++++ 3 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 tests/bfs/nocolor_env_empty.out create mode 100644 tests/bfs/nocolor_env_empty.sh diff --git a/src/parse.c b/src/parse.c index 3f32021..09cfdd3 100644 --- a/src/parse.c +++ b/src/parse.c @@ -3670,7 +3670,8 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } enum use_color use_color = COLOR_AUTO; - if (getenv("NO_COLOR")) { + const char *no_color = getenv("NO_COLOR"); + if (no_color && *no_color) { // https://no-color.org/ use_color = COLOR_NEVER; } diff --git a/tests/bfs/nocolor_env_empty.out b/tests/bfs/nocolor_env_empty.out new file mode 100644 index 0000000..a439814 --- /dev/null +++ b/tests/bfs/nocolor_env_empty.out @@ -0,0 +1,27 @@ +$'rainbow/\e[1m' +$'rainbow/\e[1m/'$'\e[0m' +rainbow +rainbow/exec.sh +rainbow/socket +rainbow/broken +rainbow/chardev_link +rainbow/link.txt +rainbow/sticky_ow +rainbow/sgid +rainbow/pipe +rainbow/ow +rainbow/sugid +rainbow/suid +rainbow/sticky +rainbow/file.dat +rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ +rainbow/mh1 +rainbow/mh2 +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/bfs/nocolor_env_empty.sh b/tests/bfs/nocolor_env_empty.sh new file mode 100644 index 0000000..0ffc046 --- /dev/null +++ b/tests/bfs/nocolor_env_empty.sh @@ -0,0 +1,5 @@ +command -v unbuffer &>/dev/null || skip + +NO_COLOR= bfs_pty rainbow >"$OUT" +sort_output +diff_output -- cgit v1.2.3 From 31aadf6b6ffdcf9cef6c92d139d52e580938d1d4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 Nov 2023 11:36:45 -0500 Subject: xspawn: Do $PATH resolution up-front posix_spawnp() is typically implemented like execvp(), i.e., by repeatedly trying execv() with each $PATH component until it succeeds. This is much slower than resolving the executable path up-front and then calling execv() once, so do that. Fixes: https://github.com/tavianator/bfs/pull/127#issuecomment-1795095126 --- src/xspawn.c | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/src/xspawn.c b/src/xspawn.c index ac9e401..01f21e9 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -188,15 +188,9 @@ int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rli /** bfs_spawn() implementation using posix_spawn(). */ static pid_t bfs_posix_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { pid_t ret; - - if (ctx->flags & BFS_SPAWN_USE_PATH) { - errno = posix_spawnp(&ret, exe, &ctx->actions, &ctx->attr, argv, envp); - } else { - errno = posix_spawn(&ret, exe, &ctx->actions, &ctx->attr, argv, envp); - } - + errno = posix_spawn(&ret, exe, &ctx->actions, &ctx->attr, argv, envp); if (errno != 0) { - ret = -1; + return -1; } return ret; @@ -263,18 +257,9 @@ fail: /** bfs_spawn() implementation using fork()/exec(). */ static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { - char *resolved = NULL; - if (ctx->flags & BFS_SPAWN_USE_PATH) { - exe = resolved = bfs_spawn_resolve(exe); - if (!resolved) { - return -1; - } - } - // Use a pipe to report errors from the child int pipefd[2]; if (pipe_cloexec(pipefd) != 0) { - free(resolved); return -1; } @@ -282,7 +267,6 @@ static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char * if (pid < 0) { close_quietly(pipefd[1]); close_quietly(pipefd[0]); - free(resolved); return -1; } else if (pid == 0) { // Child @@ -291,7 +275,6 @@ static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char * // Parent xclose(pipefd[1]); - free(resolved); int error; ssize_t nbytes = xread(pipefd[0], &error, sizeof(error)); @@ -307,16 +290,31 @@ static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char * } pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { + // execvp()/posix_spawnp() are typically implemented with repeated + // execv() calls for each $PATH component until one succeeds. It's + // faster to resolve the full path ahead of time. + char *resolved = NULL; + if (ctx->flags & BFS_SPAWN_USE_PATH) { + exe = resolved = bfs_spawn_resolve(exe); + if (!resolved) { + return -1; + } + } + extern char **environ; if (!envp) { envp = environ; } + pid_t ret; if (ctx->flags & BFS_SPAWN_USE_POSIX) { - return bfs_posix_spawn(exe, ctx, argv, envp); + ret = bfs_posix_spawn(exe, ctx, argv, envp); } else { - return bfs_fork_spawn(exe, ctx, argv, envp); + ret = bfs_fork_spawn(exe, ctx, argv, envp); } + + free(resolved); + return ret; } char *bfs_spawn_resolve(const char *exe) { -- cgit v1.2.3 From ce90dc9bc00c46b27b437467bb2f053ab2307fbc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 Nov 2023 13:09:21 -0500 Subject: tests: Fix output interleaving with -j1 --- tests/run.sh | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/run.sh b/tests/run.sh index f93cb4b..8f0a8fc 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -124,17 +124,17 @@ run_tests() { set +e for TEST in "${TEST_CASES[@]}"; do + if ((BG >= JOBS)); then + wait_test + fi + printf "$TEST_FMT" "$TEST" mkdir -p "$TMP/$TEST" OUT="$TMP/$TEST.out" - if ((BG >= JOBS)); then - wait_test - fi - ((++BG)) - bg_test "$TESTS/$TEST.sh" & + ((++BG)) done while ((BG > 0)); do -- cgit v1.2.3 From 163baf1c9af13be0ce705b133e41e0c3d6427398 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 Nov 2023 13:10:44 -0500 Subject: parse: Reject -{exec,ok}dir if $PATH contains a relative path This matches the behaviour of GNU find. --- src/parse.c | 26 ++++++++++++++++++++++++++ tests/gnu/execdir_path_dot.sh | 1 + tests/gnu/execdir_path_empty.sh | 1 + tests/gnu/execdir_path_relative.sh | 1 + tests/gnu/okdir_path_dot.sh | 1 + tests/gnu/okdir_path_empty.sh | 1 + tests/gnu/okdir_path_relative.sh | 1 + 7 files changed, 32 insertions(+) create mode 100644 tests/gnu/execdir_path_dot.sh create mode 100644 tests/gnu/execdir_path_empty.sh create mode 100644 tests/gnu/execdir_path_relative.sh create mode 100644 tests/gnu/okdir_path_dot.sh create mode 100644 tests/gnu/okdir_path_empty.sh create mode 100644 tests/gnu/okdir_path_relative.sh diff --git a/src/parse.c b/src/parse.c index 09cfdd3..d21ab40 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1263,6 +1263,28 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar 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(state, expr, + "This action would be unsafe, since ${bld}$$PATH${rs} contains the relative path ${bld}%pq${rs}\n", comp); + free(comp); + } else { + parse_perror(state, "strndup()"); + } + goto fail; + } + + path = strchr(path, ':'); + if (path) { + ++path; + } + } + // To dup() the parent directory if (execbuf->flags & BFS_EXEC_MULTI) { ++expr->persistent_fds; @@ -1276,6 +1298,10 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar } return expr; + +fail: + bfs_expr_free(expr); + return NULL; } /** diff --git a/tests/gnu/execdir_path_dot.sh b/tests/gnu/execdir_path_dot.sh new file mode 100644 index 0000000..632dbb4 --- /dev/null +++ b/tests/gnu/execdir_path_dot.sh @@ -0,0 +1 @@ +! PATH=".:$PATH" invoke_bfs basic -execdir echo {} + diff --git a/tests/gnu/execdir_path_empty.sh b/tests/gnu/execdir_path_empty.sh new file mode 100644 index 0000000..eda6b1c --- /dev/null +++ b/tests/gnu/execdir_path_empty.sh @@ -0,0 +1 @@ +! PATH=":$PATH" invoke_bfs basic -execdir echo {} + diff --git a/tests/gnu/execdir_path_relative.sh b/tests/gnu/execdir_path_relative.sh new file mode 100644 index 0000000..69899ad --- /dev/null +++ b/tests/gnu/execdir_path_relative.sh @@ -0,0 +1 @@ +! PATH="foo:$PATH" invoke_bfs basic -execdir echo {} + diff --git a/tests/gnu/okdir_path_dot.sh b/tests/gnu/okdir_path_dot.sh new file mode 100644 index 0000000..5b40e27 --- /dev/null +++ b/tests/gnu/okdir_path_dot.sh @@ -0,0 +1 @@ +! PATH=".:$PATH" invoke_bfs basic -okdir echo {} \; diff --git a/tests/gnu/okdir_path_empty.sh b/tests/gnu/okdir_path_empty.sh new file mode 100644 index 0000000..2669ee8 --- /dev/null +++ b/tests/gnu/okdir_path_empty.sh @@ -0,0 +1 @@ +! PATH=":$PATH" invoke_bfs basic -okdir echo {} \; diff --git a/tests/gnu/okdir_path_relative.sh b/tests/gnu/okdir_path_relative.sh new file mode 100644 index 0000000..05100a1 --- /dev/null +++ b/tests/gnu/okdir_path_relative.sh @@ -0,0 +1 @@ +! PATH="foo:$PATH" invoke_bfs basic -okdir echo {} \; -- cgit v1.2.3 From 5fe11b94b38bfb4d43637e05ac24da0d7d72b9ea Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 Nov 2023 16:43:35 -0500 Subject: ioq: Implement a better non-blocking pop --- src/bftw.c | 2 +- src/ioq.c | 276 +++++++++++++++++++++++++++++++++++++++++-------------------- src/ioq.h | 13 +-- 3 files changed, 189 insertions(+), 102 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 5a55037..0b74cd9 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -588,7 +588,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return -1; } - struct ioq_ent *ent = block ? ioq_pop(ioq) : ioq_trypop(ioq); + struct ioq_ent *ent = ioq_pop(ioq, block); if (!ent) { return -1; } diff --git a/src/ioq.c b/src/ioq.c index 8c1bdbe..244b2cc 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -1,6 +1,127 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +/** + * An asynchronous I/O queue implementation. + * + * struct ioq is composed of two separate queues: + * + * struct ioqq *pending; // Pending I/O requests + * struct ioqq *ready; // Ready I/O responses + * + * Worker threads pop requests from `pending`, execute them, and push them back + * to the `ready` queue. The main thread pushes requests to `pending` and pops + * them from `ready`. + * + * struct ioqq is a blocking MPMC queue (though it could be SPMC/MPSC for + * pending/ready respectively). It is implemented as a circular buffer: + * + * size_t mask; // (1 << N) - 1 + * [padding] + * size_t head; // Writer index + * [padding] + * size_t tail; // Reader index + * [padding] + * ioq_slot slots[1 << N]; // Queue contents + * + * Pushes are implemented with an unconditional + * + * fetch_add(&ioqq->head, IOQ_STRIDE) + * + * which scales better on many architectures than compare-and-swap (see [1] for + * details). Pops are implemented similarly. We add IOQ_STRIDE rather than 1 + * so that successive queue elements are on different cache lines, but the + * exposition below uses 1 for simplicity. + * + * Since the fetch-and-adds are unconditional, non-blocking readers can get + * ahead of writers: + * + * Reader Writer + * ──────────────── ────────────────────── + * head: 0 → 1 + * slots[0]: empty + * tail: 0 → 1 + * slots[0]: empty → full + * head: 1 → 2 + * slots[1]: empty! + * + * To avoid this, non-blocking reads (ioqq_pop(ioqq, false)) must mark the slots + * somehow so that writers can skip them: + * + * Reader Writer + * ─────────────────────── ─────────────────────── + * head: 0 → 1 + * slots[0]: empty → skip + * tail: 0 → 1 + * slots[0]: skip → empty + * tail: 1 → 2 + * slots[1]: empty → full + * head: 1 → 2 + * slots[1]: full → empty + * + * As well, a reader might "lap" a writer (or another reader), so slots need to + * count how many times they should be skipped: + * + * Reader Writer + * ────────────────────────── ───────────────────────── + * head: 0 → 1 + * slots[0]: empty → skip(1) + * head: 1 → 2 + * slots[1]: empty → skip(1) + * ... + * head: M → 0 + * slots[M]: empty → skip(1) + * head: 0 → 1 + * slots[0]: skip(1 → 2) + * tail: 0 → 1 + * slots[0]: skip(2 → 1) + * tail: 1 → 2 + * slots[1]: skip(1) → empty + * ... + * tail: M → 0 + * slots[M]: skip(1) → empty + * tail: 0 → 1 + * slots[0]: skip(1) → empty + * tail: 1 → 2 + * slots[1]: empty → full + * head: 1 → 2 + * slots[1]: full → empty + * + * As described in [1], this approach is susceptible to livelock if readers stay + * ahead of writers. This is okay for us because we don't retry failed non- + * blocking reads. + * + * The slot representation uses tag bits to hold either a pointer or skip(N): + * + * IOQ_SKIP (highest bit) IOQ_BLOCKED (lowest bit) + * ↓ ↓ + * 0 0 0 ... 0 0 0 + * └──────────┬──────────┘ + * │ + * value bits + * + * If IOQ_SKIP is unset, the value bits hold a pointer (or zero/NULL for empty). + * If IOQ_SKIP is set, the value bits hold a negative skip count. Writers can + * reduce the skip count by adding 1 to the value bits, and when the count hits + * zero, the carry will automatically clear IOQ_SKIP: + * + * IOQ_SKIP IOQ_BLOCKED + * ↓ ↓ + * 1 1 1 ... 1 0 0 skip(2) + * 1 1 1 ... 1 1 0 skip(1) + * 0 0 0 ... 0 0 0 empty + * + * The IOQ_BLOCKED flag is used to track sleeping waiters, futex-style. To wait + * for a slot to change, waiters call ioq_slot_wait() which sets IOQ_BLOCKED and + * goes to sleep. Whenever a slot is updated, if the old value had IOQ_BLOCKED + * set, ioq_slot_wake() must be called to wake up that waiter. + * + * Blocking/waking uses a pool of monitors (mutex, condition variable pairs). + * Slots are assigned round-robin to a monitor from the pool. + * + * [1]: https://arxiv.org/abs/2201.02179 + */ + #include "ioq.h" #include "alloc.h" #include "atomic.h" @@ -51,24 +172,15 @@ static void ioq_monitor_destroy(struct ioq_monitor *monitor) { /** A single entry in a command queue. */ typedef atomic uintptr_t ioq_slot; -/** Slot flag bit to indicate waiters. */ +/** Someone might be waiting on this slot. */ #define IOQ_BLOCKED ((uintptr_t)1) -bfs_static_assert(alignof(struct ioq_ent) > 1); - -/** Check if a slot has waiters. */ -static bool ioq_slot_blocked(uintptr_t value) { - return value & IOQ_BLOCKED; -} - -/** Extract the pointer from a slot. */ -static struct ioq_ent *ioq_slot_ptr(uintptr_t value) { - return (struct ioq_ent *)(value & ~IOQ_BLOCKED); -} +/** The next push(es) should skip this slot. */ +#define IOQ_SKIP ((uintptr_t)1 << (UINTPTR_WIDTH - 1)) +/** Amount to add for an additional skip. */ +#define IOQ_SKIP_ONE (~IOQ_BLOCKED) -/** Check if a slot is empty. */ -static bool ioq_slot_empty(uintptr_t value) { - return !ioq_slot_ptr(value); -} +// Need room for two flag bits +bfs_static_assert(alignof(struct ioq_ent) > 2); /** * An MPMC queue of I/O commands. @@ -205,80 +317,85 @@ static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { cond_broadcast(&monitor->cond); } -/** Get the next slot for writing. */ -static ioq_slot *ioqq_write(struct ioqq *ioqq) { - size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); - return &ioqq->slots[i & ioqq->slot_mask]; -} - /** Push an entry into a slot. */ -static void ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent) { - uintptr_t addr = (uintptr_t)ent; - bfs_assert(!ioq_slot_blocked(addr)); - +static bool ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent) { uintptr_t prev = load(slot, relaxed); - do { - while (!ioq_slot_empty(prev)) { + while (true) { + uintptr_t next; + if (prev & IOQ_SKIP) { + // skip(1) → empty + // skip(n) → skip(n - 1) + next = (prev - IOQ_SKIP_ONE) & ~IOQ_BLOCKED; + } else if (prev > IOQ_BLOCKED) { + // full(ptr) → wait prev = ioq_slot_wait(ioqq, slot, prev); + continue; + } else { + // empty → full(ptr) + next = (uintptr_t)ent >> 1; } - } while (!compare_exchange_weak(slot, &prev, addr, release, relaxed)); - if (ioq_slot_blocked(prev)) { + if (compare_exchange_weak(slot, &prev, next, release, relaxed)) { + break; + } + } + + if (prev & IOQ_BLOCKED) { ioq_slot_wake(ioqq, slot); } + + return !(prev & IOQ_SKIP); } /** Push an entry onto the queue. */ static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { - ioq_slot *slot = ioqq_write(ioqq); - ioq_slot_push(ioqq, slot, ent); -} - -/** Get the next slot for reading. */ -static ioq_slot *ioqq_read(struct ioqq *ioqq) { - size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); - return &ioqq->slots[i & ioqq->slot_mask]; + while (true) { + size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); + ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; + if (ioq_slot_push(ioqq, slot, ent)) { + break; + } + } } /** (Try to) pop an entry from a slot. */ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool block) { uintptr_t prev = load(slot, relaxed); - do { - while (ioq_slot_empty(prev)) { - if (block) { - prev = ioq_slot_wait(ioqq, slot, prev); - } else { - return NULL; - } + while (true) { + // empty → skip(1) + // skip(n) → skip(n + 1) + // full(ptr) → full(ptr - 1) + uintptr_t next = prev + IOQ_SKIP_ONE; + // skip(n) → ~IOQ_BLOCKED + // full(ptr) → 0 + next &= (next & IOQ_SKIP) ? ~IOQ_BLOCKED : 0; + + if (block && next) { + prev = ioq_slot_wait(ioqq, slot, prev); + continue; + } + + if (compare_exchange_weak(slot, &prev, next, acquire, relaxed)) { + break; } - } while (!compare_exchange_weak(slot, &prev, 0, acquire, relaxed)); + } - if (ioq_slot_blocked(prev)) { + if (prev & IOQ_BLOCKED) { ioq_slot_wake(ioqq, slot); } - return ioq_slot_ptr(prev); + // empty → 0 + // skip(n) → 0 + // full(ptr) → ptr + prev &= (prev & IOQ_SKIP) ? 0 : ~IOQ_BLOCKED; + return (struct ioq_ent *)(prev << 1); } /** Pop an entry from the queue. */ -static struct ioq_ent *ioqq_pop(struct ioqq *ioqq) { - ioq_slot *slot = ioqq_read(ioqq); - return ioq_slot_pop(ioqq, slot, true); -} - -/** Pop an entry from the queue if one is available. */ -static struct ioq_ent *ioqq_trypop(struct ioqq *ioqq) { - size_t i = load(&ioqq->tail, relaxed); +static struct ioq_ent *ioqq_pop(struct ioqq *ioqq, bool block) { + size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; - - struct ioq_ent *ret = ioq_slot_pop(ioqq, slot, false); - if (ret) { - size_t j = exchange(&ioqq->tail, i + IOQ_STRIDE, relaxed); - bfs_assert(j == i, "Detected multiple consumers"); - (void)j; - } - - return ret; + return ioq_slot_pop(ioqq, slot, block); } /** Sentinel stop command. */ @@ -378,8 +495,6 @@ struct ioq_ring_state { struct ioq *ioq; /** The io_uring. */ struct io_uring *ring; - /** The current ioq->pending slot. */ - ioq_slot *slot; /** Number of prepped, unsubmitted SQEs. */ size_t prepped; /** Number of submitted, unreaped SQEs. */ @@ -394,20 +509,9 @@ static struct ioq_ent *ioq_ring_pop(struct ioq_ring_state *state) { return NULL; } - // Advance to the next slot if necessary - struct ioq *ioq = state->ioq; - if (!state->slot) { - state->slot = ioqq_read(ioq->pending); - } - // Block if we have nothing else to do bool block = !state->prepped && !state->submitted; - struct ioq_ent *ret = ioq_slot_pop(ioq->pending, state->slot, block); - - if (ret) { - // Got an entry, move to the next slot next time - state->slot = NULL; - } + struct ioq_ent *ret = ioqq_pop(state->ioq->pending, block); if (ret == &IOQ_STOP) { state->stop = true; @@ -536,7 +640,7 @@ static void ioq_sync_work(struct ioq_thread *thread) { struct ioq *ioq = thread->parent; while (true) { - struct ioq_ent *ent = ioqq_pop(ioq->pending); + struct ioq_ent *ent = ioqq_pop(ioq->pending, true); if (ent == &IOQ_STOP) { break; } @@ -687,20 +791,12 @@ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr) { return 0; } -struct ioq_ent *ioq_pop(struct ioq *ioq) { - if (ioq->size == 0) { - return NULL; - } - - return ioqq_pop(ioq->ready); -} - -struct ioq_ent *ioq_trypop(struct ioq *ioq) { +struct ioq_ent *ioq_pop(struct ioq *ioq, bool block) { if (ioq->size == 0) { return NULL; } - return ioqq_trypop(ioq->ready); + return ioqq_pop(ioq->ready, block); } void ioq_free(struct ioq *ioq, struct ioq_ent *ent) { diff --git a/src/ioq.h b/src/ioq.h index eab89ec..87727cb 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -8,6 +8,7 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H +#include "config.h" #include "dir.h" #include @@ -136,17 +137,7 @@ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr); * @return * The next response, or NULL. */ -struct ioq_ent *ioq_pop(struct ioq *ioq); - -/** - * Pop a response from the queue, without blocking. - * - * @param ioq - * The I/O queue. - * @return - * The next response, or NULL. - */ -struct ioq_ent *ioq_trypop(struct ioq *ioq); +struct ioq_ent *ioq_pop(struct ioq *ioq, bool block); /** * Free a queue entry. -- cgit v1.2.3 From ffa465b759204272a5e94d851ce3696c827e9d96 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 9 Nov 2023 14:34:40 -0500 Subject: list: Simplify slist_remove_impl() We now assume that all-bits-zero is a null pointer, so memset is fine. --- src/list.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/list.h b/src/list.h index 61c22e3..91f416f 100644 --- a/src/list.h +++ b/src/list.h @@ -317,15 +317,15 @@ #define SLIST_REMOVE__(list, cursor, next) \ (list->tail = (*cursor)->next ? list->tail : cursor, \ - slist_remove_impl(*cursor, cursor, &(*cursor)->next, list->tail, sizeof(*cursor))) + slist_remove_impl(*cursor, cursor, &(*cursor)->next, sizeof(*cursor))) // Helper for SLIST_REMOVE() -static inline void *slist_remove_impl(void *ret, void *cursor, void *next, void *tail, size_t size) { +static inline void *slist_remove_impl(void *ret, void *cursor, void *next, size_t size) { // ret = *cursor; // *cursor = ret->next; memcpy(cursor, next, size); - // ret->next = *list->tail; (NULL) - memcpy(next, tail, size); + // ret->next = NULL; + memset(next, 0, size); return ret; } -- cgit v1.2.3 From b5b1e98a66aef5b64409e3d02149733bf3f475fb Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 9 Nov 2023 15:29:04 -0500 Subject: config: Remove BFS_SUPPRESS() --- src/config.h | 15 --------------- src/printf.c | 18 ++++++++++++++---- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/config.h b/src/config.h index 38ca69b..e11941a 100644 --- a/src/config.h +++ b/src/config.h @@ -202,19 +202,4 @@ # endif #endif -/** - * Ignore a particular GCC warning for a region of code. - */ -#if __GNUC__ -# define BFS_PRAGMA_STRINGIFY(...) _Pragma(#__VA_ARGS__) -# define BFS_SUPPRESS(warning) \ - _Pragma("GCC diagnostic push"); \ - BFS_PRAGMA_STRINGIFY(GCC diagnostic ignored warning) -# define BFS_UNSUPPRESS() \ - _Pragma("GCC diagnostic pop") -#else -# define BFS_SUPPRESS(warning) -# define BFS_UNSUPPRESS() -#endif - #endif // BFS_CONFIG_H diff --git a/src/printf.c b/src/printf.c index 704e26d..02ca586 100644 --- a/src/printf.c +++ b/src/printf.c @@ -83,9 +83,14 @@ static int dyn_fprintf(FILE *file, const struct bfs_printf *directive, ...) { va_list args; va_start(args, directive); - BFS_SUPPRESS("-Wformat-nonliteral"); +#if __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif int ret = vfprintf(file, directive->str, args); - BFS_UNSUPPRESS(); +#if __GNUC__ +# pragma GCC diagnostic pop +#endif va_end(args); return ret; @@ -183,9 +188,14 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, // POSIX strftime() features default: format[1] = directive->c; - BFS_SUPPRESS("-Wformat-nonliteral"); +#if __GNUC__ +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wformat-nonliteral" +#endif ret = strftime(buf, sizeof(buf), format, &tm); - BFS_UNSUPPRESS(); +#if __GNUC__ +# pragma GCC diagnostic pop +#endif break; } -- cgit v1.2.3 From 026e8fbd248561396752552efa3cc04e0ac832b7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 9 Nov 2023 12:59:20 -0500 Subject: config: s/BFS_FORMATTER/attr_format/ --- src/bar.c | 2 +- src/color.c | 4 ++-- src/color.h | 4 ++-- src/config.h | 4 ++-- src/diag.h | 14 +++++++------- src/dstring.h | 8 ++++---- src/eval.c | 2 +- src/exec.c | 2 +- src/opt.c | 4 ++-- src/parse.c | 14 +++++++------- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/bar.c b/src/bar.c index 57cc4dd..2e50dbe 100644 --- a/src/bar.c +++ b/src/bar.c @@ -127,7 +127,7 @@ static void reset_before_death_by(int sig) { } /** printf() to the status bar with a single write(). */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { va_list args; va_start(args, format); diff --git a/src/color.c b/src/color.c index fbb5edf..7644ca3 100644 --- a/src/color.c +++ b/src/color.c @@ -1105,7 +1105,7 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { } /** Format some colored output to the buffer. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) static int cbuff(CFILE *cfile, const char *format, ...); /** Dump a parsed expression tree, for debugging. */ @@ -1177,7 +1177,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { return 0; } -BFS_FORMATTER(2, 0) +attr_format(2, 0) static int cvbuff(CFILE *cfile, const char *format, va_list args) { const struct colors *colors = cfile->colors; int error = errno; diff --git a/src/color.h b/src/color.h index b118f77..8a81573 100644 --- a/src/color.h +++ b/src/color.h @@ -100,13 +100,13 @@ int cfclose(CFILE *cfile); * @return * 0 on success, -1 on failure. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) int cfprintf(CFILE *cfile, const char *format, ...); /** * cfprintf() variant that takes a va_list. */ -BFS_FORMATTER(2, 0) +attr_format(2, 0) int cvfprintf(CFILE *cfile, const char *format, va_list args); #endif // BFS_COLOR_H diff --git a/src/config.h b/src/config.h index e11941a..f19677c 100644 --- a/src/config.h +++ b/src/config.h @@ -188,9 +188,9 @@ * Adds compiler warnings for bad printf()-style function calls, if supported. */ #if __has_attribute(format) -# define BFS_FORMATTER(fmt, args) __attribute__((format(printf, fmt, args))) +# define attr_format(fmt, args) __attribute__((format(printf, fmt, args))) #else -# define BFS_FORMATTER(fmt, args) +# define attr_format(fmt, args) #endif /** diff --git a/src/diag.h b/src/diag.h index 838a794..8c7ed57 100644 --- a/src/diag.h +++ b/src/diag.h @@ -44,7 +44,7 @@ struct bfs_loc { /** * Print a message to standard error and abort. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); /** @@ -121,7 +121,7 @@ void bfs_perror(const struct bfs_ctx *ctx, const char *str); /** * Shorthand for printing error messages. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -129,7 +129,7 @@ void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a warning was printed. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -137,25 +137,25 @@ bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a debug message was printed. */ -BFS_FORMATTER(3, 4) +attr_format(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. */ -BFS_FORMATTER(2, 0) +attr_format(2, 0) void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_warning() variant that takes a va_list. */ -BFS_FORMATTER(2, 0) +attr_format(2, 0) bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_debug() variant that takes a va_list. */ -BFS_FORMATTER(3, 0) +attr_format(3, 0) bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args); /** diff --git a/src/dstring.h b/src/dstring.h index 6496a4f..fd98df8 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -230,7 +230,7 @@ int dstrxcpy(dchar **dest, const char *str, size_t len); * @return * The created string, or NULL on failure. */ -BFS_FORMATTER(1, 2) +attr_format(1, 2) char *dstrprintf(const char *format, ...); /** @@ -243,7 +243,7 @@ char *dstrprintf(const char *format, ...); * @return * The created string, or NULL on failure. */ -BFS_FORMATTER(1, 0) +attr_format(1, 0) char *dstrvprintf(const char *format, va_list args); /** @@ -258,7 +258,7 @@ char *dstrvprintf(const char *format, va_list args); * @return * 0 on success, -1 on failure. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) int dstrcatf(dchar **str, const char *format, ...); /** @@ -273,7 +273,7 @@ int dstrcatf(dchar **str, const char *format, ...); * @return * 0 on success, -1 on failure. */ -BFS_FORMATTER(2, 0) +attr_format(2, 0) int dstrvcatf(dchar **str, const char *format, va_list args); /** diff --git a/src/eval.c b/src/eval.c index b511eba..eb4a0ca 100644 --- a/src/eval.c +++ b/src/eval.c @@ -58,7 +58,7 @@ struct bfs_eval { /** * Print an error message. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) static void eval_error(struct bfs_eval *state, const char *format, ...) { // By POSIX, any errors should be accompanied by a non-zero exit status *state->ret = EXIT_FAILURE; diff --git a/src/exec.c b/src/exec.c index f0730d2..ba82439 100644 --- a/src/exec.c +++ b/src/exec.c @@ -22,7 +22,7 @@ #include /** Print some debugging info. */ -BFS_FORMATTER(2, 3) +attr_format(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/opt.c b/src/opt.c index 77c2798..5da73d8 100644 --- a/src/opt.c +++ b/src/opt.c @@ -305,7 +305,7 @@ struct opt_state { }; /** Log an optimization. */ -BFS_FORMATTER(3, 4) +attr_format(3, 4) static bool opt_debug(const struct opt_state *state, int level, const char *format, ...) { bfs_assert(state->ctx->optlevel >= level); @@ -321,7 +321,7 @@ static bool opt_debug(const struct opt_state *state, int level, const char *form } /** Warn about an expression. */ -BFS_FORMATTER(3, 4) +attr_format(3, 4) static void opt_warning(const struct opt_state *state, const struct bfs_expr *expr, const char *format, ...) { if (bfs_expr_warning(state->ctx, expr)) { va_list args; diff --git a/src/parse.c b/src/parse.c index d21ab40..d3938fc 100644 --- a/src/parse.c +++ b/src/parse.c @@ -247,7 +247,7 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, /** * Print an error message during parsing. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) static void parse_error(const struct parser_state *state, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -267,7 +267,7 @@ static void parse_error(const struct parser_state *state, const char *format, .. /** * Print an error about some command line arguments. */ -BFS_FORMATTER(4, 5) +attr_format(4, 5) static void parse_argv_error(const struct parser_state *state, char **argv, size_t argc, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -287,7 +287,7 @@ static void parse_argv_error(const struct parser_state *state, char **argv, size /** * Print an error about conflicting command line arguments. */ -BFS_FORMATTER(6, 7) +attr_format(6, 7) static void parse_conflict_error(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -308,7 +308,7 @@ static void parse_conflict_error(const struct parser_state *state, char **argv1, /** * Print an error about an expression. */ -BFS_FORMATTER(3, 4) +attr_format(3, 4) static void parse_expr_error(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -325,7 +325,7 @@ static void parse_expr_error(const struct parser_state *state, const struct bfs_ /** * Print a warning message during parsing. */ -BFS_FORMATTER(2, 3) +attr_format(2, 3) static bool parse_warning(const struct parser_state *state, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -348,7 +348,7 @@ static bool parse_warning(const struct parser_state *state, const char *format, /** * Print a warning about conflicting command line arguments. */ -BFS_FORMATTER(6, 7) +attr_format(6, 7) static bool parse_conflict_warning(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -372,7 +372,7 @@ static bool parse_conflict_warning(const struct parser_state *state, char **argv /** * Print a warning about an expression. */ -BFS_FORMATTER(3, 4) +attr_format(3, 4) static bool parse_expr_warning(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; -- cgit v1.2.3 From 0a5091a9005b485fccad689a4cbf081802860a5a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 9 Nov 2023 13:02:59 -0500 Subject: config: New attr_target_clones() macro --- src/config.h | 9 +++++++++ src/trie.c | 16 ++++++++-------- 2 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/config.h b/src/config.h index f19677c..3100cec 100644 --- a/src/config.h +++ b/src/config.h @@ -202,4 +202,13 @@ # endif #endif +/** + * Apply the target_clones attribute, if available. + */ +#if BFS_USE_TARGET_CLONES +# define attr_target_clones(...) __attribute__((target_clones(__VA_ARGS__))) +#else +# define attr_target_clones(...) +#endif + #endif // BFS_CONFIG_H diff --git a/src/trie.c b/src/trie.c index 23b70ff..cf55cee 100644 --- a/src/trie.c +++ b/src/trie.c @@ -94,10 +94,10 @@ bfs_static_assert(CHAR_WIDTH == 8); -#if BFS_USE_TARGET_CLONES && (__i386__ || __x86_64__) -# define TARGET_CLONES_POPCNT __attribute__((target_clones("popcnt", "default"))) +#if __i386__ || __x86_64__ +# define trie_clones attr_target_clones("popcnt", "default") #else -# define TARGET_CLONES_POPCNT +# define trie_clones #endif /** Number of bits for the sparse array bitmap, aka the range of a nibble. */ @@ -192,7 +192,7 @@ static unsigned char trie_key_nibble(const void *key, size_t offset) { * that case, the first mismatch between the key and the representative will be * the depth at which to make a new branch to insert the key. */ -TARGET_CLONES_POPCNT +trie_clones static struct trie_leaf *trie_representative(const struct trie *trie, const void *key, size_t length) { uintptr_t ptr = trie->root; if (!ptr) { @@ -271,7 +271,7 @@ static bool trie_check_prefix(struct trie_leaf *leaf, size_t skip, const char *k } } -TARGET_CLONES_POPCNT +trie_clones static struct trie_leaf *trie_find_prefix_impl(const struct trie *trie, const char *key) { uintptr_t ptr = trie->root; if (!ptr) { @@ -429,7 +429,7 @@ static size_t trie_mismatch(const struct trie_leaf *rep, const void *key, size_t * | Z * +--->... */ -TARGET_CLONES_POPCNT +trie_clones static struct trie_leaf *trie_node_insert(struct trie *trie, uintptr_t *ptr, struct trie_leaf *leaf, unsigned char nibble) { struct trie_node *node = trie_decode_node(*ptr); unsigned int size = count_ones(node->bitmap); @@ -552,7 +552,7 @@ struct trie_leaf *trie_insert_str(struct trie *trie, const char *key) { return trie_insert_mem(trie, key, strlen(key) + 1); } -TARGET_CLONES_POPCNT +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); @@ -654,7 +654,7 @@ static int trie_collapse_node(struct trie *trie, uintptr_t *parent, struct trie_ return 0; } -TARGET_CLONES_POPCNT +trie_clones static void trie_remove_impl(struct trie *trie, struct trie_leaf *leaf) { uintptr_t *child = &trie->root; uintptr_t *parent = NULL; -- cgit v1.2.3 From c745df94a182b8a569cb833ecfbe8da33bf01f98 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 9 Nov 2023 14:34:21 -0500 Subject: config: New attr_noinline and attr_cold macros --- src/alloc.c | 1 + src/config.h | 18 ++++++++++++++++++ src/diag.h | 15 +++++++++++++++ src/ioq.c | 2 ++ 4 files changed, 36 insertions(+) diff --git a/src/alloc.c b/src/alloc.c index 0b978ba..3b9972f 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -107,6 +107,7 @@ void arena_init(struct arena *arena, size_t align, size_t size) { } /** Allocate a new slab. */ +attr_cold static int slab_alloc(struct arena *arena) { void **slabs = realloc(arena->slabs, sizeof_array(void *, arena->nslabs + 1)); if (!slabs) { diff --git a/src/config.h b/src/config.h index 3100cec..b95abaa 100644 --- a/src/config.h +++ b/src/config.h @@ -184,6 +184,24 @@ # define fallthru ((void)0) #endif +/** + * Hint to avoid inlining a function. + */ +#if __has_attribute(noinline) +# define attr_noinline __attribute__((noinline)) +#else +# define attr_noinline +#endif + +/** + * Hint that a function is unlikely to be called. + */ +#if __has_attribute(cold) +# define attr_cold attr_noinline __attribute__((cold)) +#else +# define attr_cold attr_noinline +#endif + /** * Adds compiler warnings for bad printf()-style function calls, if supported. */ diff --git a/src/diag.h b/src/diag.h index 8c7ed57..870264e 100644 --- a/src/diag.h +++ b/src/diag.h @@ -44,6 +44,7 @@ struct bfs_loc { /** * Print a message to standard error and abort. */ +attr_cold attr_format(2, 3) noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); @@ -116,11 +117,13 @@ const char *debug_flag_name(enum debug_flags flag); /** * Like perror(), but decorated like bfs_error(). */ +attr_cold void bfs_perror(const struct bfs_ctx *ctx, const char *str); /** * Shorthand for printing error messages. */ +attr_cold attr_format(2, 3) void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); @@ -129,6 +132,7 @@ void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a warning was printed. */ +attr_cold attr_format(2, 3) bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); @@ -137,60 +141,71 @@ bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a debug message was printed. */ +attr_cold attr_format(3, 4) bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, ...); /** * bfs_error() variant that takes a va_list. */ +attr_cold attr_format(2, 0) void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_warning() variant that takes a va_list. */ +attr_cold attr_format(2, 0) bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_debug() variant that takes a va_list. */ +attr_cold attr_format(3, 0) bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args); /** * Print the error message prefix. */ +attr_cold void bfs_error_prefix(const struct bfs_ctx *ctx); /** * Print the warning message prefix. */ +attr_cold bool bfs_warning_prefix(const struct bfs_ctx *ctx); /** * Print the debug message prefix. */ +attr_cold bool bfs_debug_prefix(const struct bfs_ctx *ctx, enum debug_flags flag); /** * Highlight parts of the command line in an error message. */ +attr_cold void bfs_argv_error(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in an error message. */ +attr_cold void bfs_expr_error(const struct bfs_ctx *ctx, const struct bfs_expr *expr); /** * Highlight parts of the command line in a warning message. */ +attr_cold bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in a warning message. */ +attr_cold bool bfs_expr_warning(const struct bfs_ctx *ctx, const struct bfs_expr *expr); #endif // BFS_DIAG_H diff --git a/src/ioq.c b/src/ioq.c index 244b2cc..ef89fa8 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -269,6 +269,7 @@ static struct ioq_monitor *ioq_slot_monitor(struct ioqq *ioqq, ioq_slot *slot) { } /** Atomically wait for a slot to change. */ +attr_noinline static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) { struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); mutex_lock(&monitor->mutex); @@ -298,6 +299,7 @@ done: } /** Wake up any threads waiting on a slot. */ +attr_noinline static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); -- cgit v1.2.3 From e25261a90222de75781726a93ab809c660208afd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 9 Nov 2023 15:16:04 -0500 Subject: config: Add (de)allocator attributes --- src/alloc.h | 40 +++++++++++++++++++++++----------------- src/config.h | 49 +++++++++++++++++++++++++++++++++++++++++++++++++ src/dstring.h | 21 +++++++++++++-------- tests/alloc.c | 4 ++++ 4 files changed, 89 insertions(+), 25 deletions(-) diff --git a/src/alloc.h b/src/alloc.h index fd3e5f0..34f6949 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -9,7 +9,7 @@ #define BFS_ALLOC_H #include "config.h" -#include +#include /** Round down to a multiple of an alignment. */ static inline size_t align_floor(size_t align, size_t size) { @@ -108,6 +108,8 @@ static inline size_t flex_size(size_t align, size_t min, size_t offset, size_t s * @return * The allocated memory, or NULL on failure. */ +attr_malloc(free, 1) +attr_aligned_alloc(1, 2) void *alloc(size_t align, size_t size); /** @@ -120,6 +122,8 @@ void *alloc(size_t align, size_t size); * @return * The allocated memory, or NULL on failure. */ +attr_malloc(free, 1) +attr_aligned_alloc(1, 2) void *zalloc(size_t align, size_t size); /** Allocate memory for the given type. */ @@ -176,14 +180,15 @@ void arena_init(struct arena *arena, size_t align, size_t size); arena_init((arena), alignof(type), sizeof(type)) /** - * Allocate an object out of the arena. + * Free an object from the arena. */ -void *arena_alloc(struct arena *arena); +void arena_free(struct arena *arena, void *ptr); /** - * Free an object from the arena. + * Allocate an object out of the arena. */ -void arena_free(struct arena *arena, void *ptr); +attr_malloc(arena_free, 2) +void *arena_alloc(struct arena *arena); /** * Free all allocations from an arena. @@ -242,6 +247,18 @@ void varena_init(struct varena *varena, size_t align, size_t min, size_t offset, #define VARENA_INIT(varena, type, member) \ varena_init(varena, alignof(type), sizeof(type), offsetof(type, member), sizeof_member(type, member[0])) +/** + * Free an arena-allocated flexible struct. + * + * @param varena + * The that allocated the object. + * @param ptr + * The object to free. + * @param count + * The length of the flexible array. + */ +void varena_free(struct varena *varena, void *ptr, size_t count); + /** * Arena-allocate a flexible struct. * @@ -252,6 +269,7 @@ void varena_init(struct varena *varena, size_t align, size_t min, size_t offset, * @return * The allocated struct, or NULL on failure. */ +attr_malloc(varena_free, 2) void *varena_alloc(struct varena *varena, size_t count); /** @@ -284,18 +302,6 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t */ void *varena_grow(struct varena *varena, void *ptr, size_t *count); -/** - * Free an arena-allocated flexible struct. - * - * @param varena - * The that allocated the object. - * @param ptr - * The object to free. - * @param count - * The length of the flexible array. - */ -void varena_free(struct varena *varena, void *ptr, size_t count); - /** * Free all allocations from a varena. */ diff --git a/src/config.h b/src/config.h index b95abaa..db62ef8 100644 --- a/src/config.h +++ b/src/config.h @@ -184,6 +184,17 @@ # define fallthru ((void)0) #endif +/** + * Warn if a value is unused. + */ +#if __has_c_attribute(nodiscard) +# define attr_nodiscard [[nodiscard]] +#elif __has_attribute(nodiscard) +# define attr_nodiscard __attribute__((nodiscard)) +#else +# define attr_nodiscard +#endif + /** * Hint to avoid inlining a function. */ @@ -211,6 +222,44 @@ # define attr_format(fmt, args) #endif +/** + * Annotates allocator-like functions. + */ +#if __has_attribute(malloc) +# if __clang__ +# define attr_malloc(...) attr_nodiscard __attribute__((malloc)) +# else +# define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__))) +# endif +#else +# define attr_malloc(...) attr_nodiscard +#endif + +/** + * Specifies that a function returns allocations with a given alignment. + */ +#if __has_attribute(alloc_align) +# define attr_alloc_align(param) __attribute__((alloc_align(param))) +#else +# define attr_alloc_align(param) +#endif + +/** + * Specifies that a function returns allocations with a given size. + */ +#if __has_attribute(alloc_size) +# define attr_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) +#else +# define attr_alloc_size(...) +#endif + +/** + * Shorthand for attr_alloc_align() and attr_alloc_size(). + */ +#define attr_aligned_alloc(align, ...) \ + attr_alloc_align(align) \ + attr_alloc_size(__VA_ARGS__) + /** * Check if function multiversioning via GNU indirect functions (ifunc) is supported. */ diff --git a/src/dstring.h b/src/dstring.h index fd98df8..07b4ee9 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -27,12 +27,21 @@ typedef __attribute__((aligned(alignof(size_t)))) char dchar; typedef char dchar; #endif +/** + * Free a dynamic string. + * + * @param dstr + * The string to free. + */ +void dstrfree(dchar *dstr); + /** * Allocate a dynamic string. * * @param capacity * The initial capacity of the string. */ +attr_malloc(dstrfree, 1) dchar *dstralloc(size_t capacity); /** @@ -41,6 +50,7 @@ dchar *dstralloc(size_t capacity); * @param str * The NUL-terminated string to copy. */ +attr_malloc(dstrfree, 1) dchar *dstrdup(const char *str); /** @@ -51,6 +61,7 @@ dchar *dstrdup(const char *str); * @param n * The maximum number of characters to copy from str. */ +attr_malloc(dstrfree, 1) dchar *dstrndup(const char *str, size_t n); /** @@ -59,6 +70,7 @@ dchar *dstrndup(const char *str, size_t n); * @param dstr * The dynamic string to copy. */ +attr_malloc(dstrfree, 1) dchar *dstrddup(const dchar *dstr); /** @@ -69,6 +81,7 @@ dchar *dstrddup(const dchar *dstr); * @param len * The length of the string, which may include internal NUL bytes. */ +attr_malloc(dstrfree, 1) dchar *dstrxdup(const char *str, size_t len); /** @@ -306,12 +319,4 @@ int dstrescat(dchar **dest, const char *str, enum wesc_flags flags); */ int dstrnescat(dchar **dest, const char *str, size_t n, enum wesc_flags flags); -/** - * Free a dynamic string. - * - * @param dstr - * The string to free. - */ -void dstrfree(dchar *dstr); - #endif // BFS_DSTRING_H diff --git a/tests/alloc.c b/tests/alloc.c index 2334241..37b70bf 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -24,6 +24,10 @@ int main(void) { bfs_verify(flex_size(8, 16, 4, 4, 1) == 16); // Make sure we detect allocation size overflows +#if __GNUC__ && !__clang__ +# pragma GCC diagnostic ignored "-Walloc-size-larger-than=" +#endif + bfs_verify(ALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); bfs_verify(ZALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); bfs_verify(ALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW); -- cgit v1.2.3 From 4efbe8eca395c90fc0053c7ba1038ccb7bf69e61 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 10 Nov 2023 09:58:57 -0500 Subject: config: Add constants for C standard versions --- src/bit.h | 8 ++++---- src/config.h | 10 +++++++++- src/diag.h | 4 ++-- src/thread.h | 2 +- 4 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/bit.h b/src/bit.h index 3f756f6..21a8076 100644 --- a/src/bit.h +++ b/src/bit.h @@ -12,7 +12,7 @@ #include #include -#if __STDC_VERSION__ >= 202311L +#if __STDC_VERSION__ >= C23 # include #endif @@ -173,7 +173,7 @@ # define ENDIAN_NATIVE 0 #endif -#if __STDC_VERSION__ >= 202311L +#if __STDC_VERSION__ >= C23 # define bswap16 stdc_memreverse8u16 # define bswap32 stdc_memreverse8u32 # define bswap64 stdc_memreverse8u64 @@ -236,7 +236,7 @@ static inline uint8_t bswap8(uint8_t n) { // C23 polyfill: bit utilities -#if __STDC_VERSION__ >= 202311L +#if __STDC_VERSION__ >= C23 # define count_ones stdc_count_ones # define count_zeros stdc_count_zeros # define rotate_left stdc_rotate_left @@ -395,6 +395,6 @@ UINT_OVERLOADS(BIT_CEIL) #define bit_floor(n) UINT_SELECT(n, bit_floor)(n) #define bit_ceil(n) UINT_SELECT(n, bit_ceil)(n) -#endif // __STDC_VERSION__ < 202311L +#endif // __STDC_VERSION__ < C23 #endif // BFS_BIT_H diff --git a/src/config.h b/src/config.h index db62ef8..821e4a9 100644 --- a/src/config.h +++ b/src/config.h @@ -8,9 +8,17 @@ #ifndef BFS_CONFIG_H #define BFS_CONFIG_H +// Possible __STDC_VERSION__ values + +#define C95 199409L +#define C99 199901L +#define C11 201112L +#define C17 201710L +#define C23 202311L + #include -#if __STDC_VERSION__ < 202311L +#if __STDC_VERSION__ < C23 # include # include # include diff --git a/src/diag.h b/src/diag.h index 870264e..981419e 100644 --- a/src/diag.h +++ b/src/diag.h @@ -14,7 +14,7 @@ /** * static_assert() with an optional second argument. */ -#if __STDC_VERSION__ >= 202311L +#if __STDC_VERSION__ >= C23 # define bfs_static_assert static_assert #else # define bfs_static_assert(...) bfs_static_assert_(__VA_ARGS__, #__VA_ARGS__, ) @@ -35,7 +35,7 @@ struct bfs_loc { /** * Get the current source code location. */ -#if __STDC_VERSION__ >= 202311L +#if __STDC_VERSION__ >= C23 # define bfs_location() (&(static const struct bfs_loc)BFS_LOC_INIT) #else # define bfs_location() (&(const struct bfs_loc)BFS_LOC_INIT) diff --git a/src/thread.h b/src/thread.h index b37d45f..8174fe4 100644 --- a/src/thread.h +++ b/src/thread.h @@ -11,7 +11,7 @@ #include "config.h" #include -#if __STDC_VERSION__ < 202311L && !defined(thread_local) +#if __STDC_VERSION__ < C23 && !defined(thread_local) # if BFS_USE_THREADS_H # include # else -- cgit v1.2.3 From 208376ef99da243545efcd6fb02d3469b4c068ed Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 10 Nov 2023 12:28:00 -0500 Subject: bit: Implement a branchless has_single_bit() --- src/bit.h | 3 ++- tests/bit.c | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bit.h b/src/bit.h index 21a8076..e680fed 100644 --- a/src/bit.h +++ b/src/bit.h @@ -350,7 +350,8 @@ UINT_OVERLOADS(FIRST_TRAILING_ONE) #define HAS_SINGLE_BIT(type, suffix, width) \ static inline bool has_single_bit##suffix(type n) { \ - return n && !(n & (n - 1)); \ + /** Branchless n && !(n & (n - 1)) */ \ + return n < (n ^ (n - 1)) + 1; \ } UINT_OVERLOADS(ROTATE_LEFT) diff --git a/tests/bit.c b/tests/bit.c index cb339f4..7b20770 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -117,5 +117,8 @@ int main(void) { verify_eq(bit_floor(0), 0); verify_eq(bit_ceil(0), 1); + bfs_verify(!has_single_bit(0)); + bfs_verify(!has_single_bit(UINT32_MAX)); + return EXIT_SUCCESS; } -- cgit v1.2.3 From e44e07a6bff0dd21a3fb08f28cd161e03360328b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 10 Nov 2023 10:08:26 -0500 Subject: exec: Don't do setrlimit() in the parent This was hacky, but it's also broken because it can make posix_spawn() fail with EMFILE, as happens on at least musl. Fixes: 7d69fef6a0b80ad57acde7bac8a22b83531fec0d Link: https://www.austingroupbugs.net/view.php?id=603 Link: https://sourceware.org/bugzilla/show_bug.cgi?id=31049 --- src/exec.c | 26 ++++---------------------- 1 file changed, 4 insertions(+), 22 deletions(-) diff --git a/src/exec.c b/src/exec.c index ba82439..87250ac 100644 --- a/src/exec.c +++ b/src/exec.c @@ -356,8 +356,6 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { } pid_t pid = -1; - int error; - bool reset_nofile = false; struct bfs_spawn spawn; if (bfs_spawn_init(&spawn) != 0) { @@ -376,31 +374,15 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { // Reset RLIMIT_NOFILE if necessary, to avoid breaking applications that use select() if (rlim_cmp(ctx->orig_nofile.rlim_cur, ctx->cur_nofile.rlim_cur) < 0) { - // posix_spawn() doesn't have a setrlimit() action, so adding one would force us - // to use the slower fork()/exec() path. Instead, drop the rlimit temporarily in - // the parent. This can race with other threads, but we always recover from - // EMFILE in the main thread anyway. - if (spawn.flags & BFS_SPAWN_USE_POSIX) { - if (setrlimit(RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { - goto fail; - } - reset_nofile = true; - } else { - if (bfs_spawn_addsetrlimit(&spawn, RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { - goto fail; - } + if (bfs_spawn_addsetrlimit(&spawn, RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { + goto fail; } } pid = bfs_spawn(execbuf->argv[0], &spawn, execbuf->argv, NULL); -fail: - error = errno; - if (reset_nofile) { - if (setrlimit(RLIMIT_NOFILE, &ctx->cur_nofile) != 0) { - bfs_bug("setrlimit(RLIMIT_NOFILE): %s", xstrerror(errno)); - } - } +fail:; + int error = errno; bfs_spawn_destroy(&spawn); if (pid < 0) { -- cgit v1.2.3 From 640fa83406bb8c08d971be68b32b7e222e92e286 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 10 Nov 2023 22:22:01 -0500 Subject: Initial support for Cosmopolitan Libc --- src/alloc.c | 1 + src/bfstd.c | 2 +- src/config.h | 4 ++++ src/eval.c | 4 +++- src/xspawn.c | 25 ++++++++++++++++++++++++- 5 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index 3b9972f..ff3ec6d 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -3,6 +3,7 @@ #include "alloc.h" #include "bit.h" +#include "config.h" #include "diag.h" #include "sanity.h" #include diff --git a/src/bfstd.c b/src/bfstd.c index 985a268..16cd82e 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -284,7 +284,7 @@ const char *xstrerror(int errnum) { const char *ret = NULL; static thread_local char buf[256]; -#if __APPLE__ +#if __APPLE__ || __COSMOPOLITAN__ // No strerror_l() on macOS if (strerror_r(errnum, buf, sizeof(buf)) == 0) { ret = buf; diff --git a/src/config.h b/src/config.h index 821e4a9..a474fb3 100644 --- a/src/config.h +++ b/src/config.h @@ -179,6 +179,10 @@ */ #define cache_align alignas(FALSE_SHARING_SIZE) +#if __COSMOPOLITAN__ +typedef long double max_align_t; +#endif + // Wrappers for attributes /** diff --git a/src/eval.c b/src/eval.c index eb4a0ca..6aa5104 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1558,7 +1558,9 @@ static const char *dump_bftw_strategy(enum bftw_strategy strategy) { /** Check if we need to enable BFTW_BUFFER. */ static bool eval_must_buffer(const struct bfs_expr *expr) { -#if __FreeBSD__ +#if __COSMOPOLITAN__ + return true; +#elif __FreeBSD__ // FreeBSD doesn't properly handle adding/removing directory entries // during readdir() on NFS mounts. Work around it by passing BFTW_BUFFER // whenever we could be mutating the directory ourselves through -delete diff --git a/src/xspawn.c b/src/xspawn.c index 01f21e9..6a2ebba 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -174,15 +174,38 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_SETRLIMIT); if (!action) { - return -1; + goto fail; + } + +#ifdef POSIX_SPAWN_SETRLIMIT + short flags; + errno = posix_spawnattr_getflags(&ctx->attr, &flags); + if (errno != 0) { + goto fail; + } + + flags |= POSIX_SPAWN_SETRLIMIT; + errno = posix_spawnattr_setflags(&ctx->attr, flags); + if (errno != 0) { + goto fail; } + errno = posix_spawnattr_setrlimit(&ctx->attr, resource, rl); + if (errno != 0) { + goto fail; + } +#else ctx->flags &= ~BFS_SPAWN_USE_POSIX; +#endif action->resource = resource; action->rlimit = *rl; SLIST_APPEND(ctx, action); return 0; + +fail: + free(action); + return -1; } /** bfs_spawn() implementation using posix_spawn(). */ -- cgit v1.2.3 From 56e35928daaec12c97ccc4707546f669db1d909d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 11 Nov 2023 13:46:21 -0500 Subject: Revert "bit: Implement a branchless has_single_bit()" Doesn't work for the highest bit due to overflow. This reverts commit 208376ef99da243545efcd6fb02d3469b4c068ed. --- src/bit.h | 3 +-- tests/bit.c | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bit.h b/src/bit.h index e680fed..21a8076 100644 --- a/src/bit.h +++ b/src/bit.h @@ -350,8 +350,7 @@ UINT_OVERLOADS(FIRST_TRAILING_ONE) #define HAS_SINGLE_BIT(type, suffix, width) \ static inline bool has_single_bit##suffix(type n) { \ - /** Branchless n && !(n & (n - 1)) */ \ - return n < (n ^ (n - 1)) + 1; \ + return n && !(n & (n - 1)); \ } UINT_OVERLOADS(ROTATE_LEFT) diff --git a/tests/bit.c b/tests/bit.c index 7b20770..f9071be 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -119,6 +119,7 @@ int main(void) { bfs_verify(!has_single_bit(0)); bfs_verify(!has_single_bit(UINT32_MAX)); + bfs_verify(has_single_bit((uint32_t)1 << (UINT_WIDTH - 1))); return EXIT_SUCCESS; } -- cgit v1.2.3 From 0733f4338ff409727f0a032b0f01268db45ebaf3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 13 Nov 2023 11:18:39 -0500 Subject: tests: Fix --verbose=commands with --bfs="wrapper bfs" --- tests/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run.sh b/tests/run.sh index 8f0a8fc..c7e819a 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -241,7 +241,7 @@ bfs_verbose() { bfs_verbose_impl() { printf "${GRN}%q${RST}" "${BFS[0]}" if ((${#BFS[@]} > 1)); then - printf " ${GRN}%q${RST}" "${BFS[1:]}" + printf " ${GRN}%q${RST}" "${BFS[@]:1}" fi local expr_started=0 color -- cgit v1.2.3 From 32daab769e4c0902255d9e55843eb94c66d7cb33 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 13 Nov 2023 13:09:35 -0500 Subject: eval: Remove Cosmopolitan-specific workaround Turns out this is not specific to Cosmopolitan, it's a recent Linux behaviour change. Link: https://lore.kernel.org/linux-fsdevel/20231113180616.2831430-1-tavianator@tavianator.com/ --- src/eval.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/eval.c b/src/eval.c index 6aa5104..eb4a0ca 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1558,9 +1558,7 @@ static const char *dump_bftw_strategy(enum bftw_strategy strategy) { /** Check if we need to enable BFTW_BUFFER. */ static bool eval_must_buffer(const struct bfs_expr *expr) { -#if __COSMOPOLITAN__ - return true; -#elif __FreeBSD__ +#if __FreeBSD__ // FreeBSD doesn't properly handle adding/removing directory entries // during readdir() on NFS mounts. Work around it by passing BFTW_BUFFER // whenever we could be mutating the directory ourselves through -delete -- cgit v1.2.3 From cc7d66416b91e0972c1022d0a835804a63ab7ab1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 13 Nov 2023 15:13:27 -0500 Subject: bit: Implement a branchless has_single_bit() --- src/bit.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/bit.h b/src/bit.h index 21a8076..7f71d04 100644 --- a/src/bit.h +++ b/src/bit.h @@ -350,7 +350,8 @@ UINT_OVERLOADS(FIRST_TRAILING_ONE) #define HAS_SINGLE_BIT(type, suffix, width) \ static inline bool has_single_bit##suffix(type n) { \ - return n && !(n & (n - 1)); \ + /** Branchless n && !(n & (n - 1)) */ \ + return n - 1 < (n ^ (n - 1)); \ } UINT_OVERLOADS(ROTATE_LEFT) -- cgit v1.2.3 From 8b312eb6553235c36f5483d4e46c5034dcc03ce2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 15 Nov 2023 09:21:53 -0500 Subject: xspawn: API tweaks --- src/exec.c | 6 ++---- src/xspawn.c | 52 ++++++++++++++++++++++++++++++---------------------- src/xspawn.h | 33 ++++++++++++++++----------------- 3 files changed, 48 insertions(+), 43 deletions(-) diff --git a/src/exec.c b/src/exec.c index 87250ac..3ff6f92 100644 --- a/src/exec.c +++ b/src/exec.c @@ -362,9 +362,7 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { return -1; } - if (bfs_spawn_setflags(&spawn, BFS_SPAWN_USE_PATH) != 0) { - goto fail; - } + spawn.flags |= BFS_SPAWN_USE_PATH; if (execbuf->wd_fd >= 0) { if (bfs_spawn_addfchdir(&spawn, execbuf->wd_fd) != 0) { @@ -374,7 +372,7 @@ static int bfs_exec_spawn(const struct bfs_exec *execbuf) { // Reset RLIMIT_NOFILE if necessary, to avoid breaking applications that use select() if (rlim_cmp(ctx->orig_nofile.rlim_cur, ctx->cur_nofile.rlim_cur) < 0) { - if (bfs_spawn_addsetrlimit(&spawn, RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { + if (bfs_spawn_setrlimit(&spawn, RLIMIT_NOFILE, &ctx->orig_nofile) != 0) { goto fail; } } diff --git a/src/xspawn.c b/src/xspawn.c index 6a2ebba..3974768 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -47,14 +47,14 @@ int bfs_spawn_init(struct bfs_spawn *ctx) { ctx->flags = BFS_SPAWN_USE_POSIX; SLIST_INIT(ctx); - errno = posix_spawnattr_init(&ctx->attr); + errno = posix_spawn_file_actions_init(&ctx->actions); if (errno != 0) { return -1; } - errno = posix_spawn_file_actions_init(&ctx->actions); + errno = posix_spawnattr_init(&ctx->attr); if (errno != 0) { - posix_spawnattr_destroy(&ctx->attr); + posix_spawn_file_actions_destroy(&ctx->actions); return -1; } @@ -62,8 +62,8 @@ int bfs_spawn_init(struct bfs_spawn *ctx) { } int bfs_spawn_destroy(struct bfs_spawn *ctx) { - posix_spawn_file_actions_destroy(&ctx->actions); posix_spawnattr_destroy(&ctx->attr); + posix_spawn_file_actions_destroy(&ctx->actions); for_slist (struct bfs_spawn_action, action, ctx) { free(action); @@ -72,8 +72,22 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) { return 0; } -int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags) { - ctx->flags |= flags; +/** Set some posix_spawnattr flags. */ +static inline int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { + short prev; + errno = posix_spawnattr_getflags(&ctx->attr, &prev); + if (errno != 0) { + return -1; + } + + short next = prev | flags; + if (next != prev) { + errno = posix_spawnattr_setflags(&ctx->attr, next); + if (errno != 0) { + return -1; + } + } + return 0; } @@ -150,13 +164,15 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { # endif #endif -#if BFS_HAS_POSIX_SPAWN_FCHDIR || BFS_HAS_POSIX_SPAWN_FCHDIR_NP +#if BFS_HAS_POSIX_SPAWN_FCHDIR +# define BFS_POSIX_SPAWN_FCHDIR posix_spawn_file_actions_addfchdir +#elif BFS_HAS_POSIX_SPAWN_FCHDIR_NP +# define BFS_POSIX_SPAWN_FCHDIR posix_spawn_file_actions_addfchdir_np +#endif + +#ifdef BFS_POSIX_SPAWN_FCHDIR if (ctx->flags & BFS_SPAWN_USE_POSIX) { -# if BFS_HAS_POSIX_SPAWN_FCHDIR - errno = posix_spawn_file_actions_addfchdir(&ctx->actions, fd); -# else - errno = posix_spawn_file_actions_addfchdir_np(&ctx->actions, fd); -# endif + errno = BFS_POSIX_SPAWN_FCHDIR(&ctx->actions, fd); if (errno != 0) { free(action); return -1; @@ -171,22 +187,14 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { return 0; } -int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) { +int bfs_spawn_setrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_SETRLIMIT); if (!action) { goto fail; } #ifdef POSIX_SPAWN_SETRLIMIT - short flags; - errno = posix_spawnattr_getflags(&ctx->attr, &flags); - if (errno != 0) { - goto fail; - } - - flags |= POSIX_SPAWN_SETRLIMIT; - errno = posix_spawnattr_setflags(&ctx->attr, flags); - if (errno != 0) { + if (bfs_spawn_addflags(ctx, POSIX_SPAWN_SETRLIMIT) != 0) { goto fail; } diff --git a/src/xspawn.h b/src/xspawn.h index 2a3d736..e3ad1eb 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -34,59 +34,58 @@ struct bfs_spawn { struct bfs_spawn_action *head; struct bfs_spawn_action **tail; - /** pthread_spawn() context, for when we can use it. */ - posix_spawnattr_t attr; + /** posix_spawn() context, for when we can use it. */ posix_spawn_file_actions_t actions; + posix_spawnattr_t attr; }; /** * Create a new bfs_spawn() context. * - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int bfs_spawn_init(struct bfs_spawn *ctx); /** * Destroy a bfs_spawn() context. * - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int bfs_spawn_destroy(struct bfs_spawn *ctx); -/** - * Set the flags for a bfs_spawn() context. - * - * @return 0 on success, -1 on failure. - */ -int bfs_spawn_setflags(struct bfs_spawn *ctx, enum bfs_spawn_flags flags); - /** * Add a close() action to a bfs_spawn() context. * - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd); /** * Add a dup2() action to a bfs_spawn() context. * - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd); /** * Add an fchdir() action to a bfs_spawn() context. * - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd); /** - * Add a setrlimit() action to a bfs_spawn() context. + * Apply setrlimit() to a bfs_spawn() context. * - * @return 0 on success, -1 on failure. + * @return + * 0 on success, -1 on failure. */ -int bfs_spawn_addsetrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl); +int bfs_spawn_setrlimit(struct bfs_spawn *ctx, int resource, const struct rlimit *rl); /** * Spawn a new process. -- cgit v1.2.3 From fb4760db85c552b0f538f7af2ad40fa631c20540 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 15 Nov 2023 09:45:49 -0500 Subject: tests: Fix a possible infinite loop The POSIX spec for readdir() [1] says: > If a file is removed from or added to the directory after the most > recent call to opendir() or rewinddir(), whether a subsequent call > to readdir() returns an entry for that file is unspecified. which implies that a loop of readdir()/unlink()/creat() may continue to return new files unendingly. This was even observed on a Linux 6.6 tmpfs mount [2]. It's not clear whether find(1) is also permitted to loop endlessly in this case, but in case it is, let's avoid the whole problem by limiting the -exec to happen at most once. [1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html [2]: https://lore.kernel.org/linux-fsdevel/20231113180616.2831430-1-tavianator@tavianator.com/ --- tests/gnu/ignore_readdir_race_notdir.sh | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/gnu/ignore_readdir_race_notdir.sh b/tests/gnu/ignore_readdir_race_notdir.sh index 8b03164..12e9fe6 100644 --- a/tests/gnu/ignore_readdir_race_notdir.sh +++ b/tests/gnu/ignore_readdir_race_notdir.sh @@ -1,5 +1,7 @@ # Check -ignore_readdir_race handling when a directory is replaced with a file cd "$TEST" -"$XTOUCH" -p foo/bar +mkdir foo -invoke_bfs . -mindepth 1 -ignore_readdir_race -execdir rm -r {} \; -execdir "$XTOUCH" {} \; +invoke_bfs . -mindepth 1 -ignore_readdir_race \ + -type d -execdir rmdir {} \; \ + -execdir "$XTOUCH" {} \; -- cgit v1.2.3 From f4e8084414e07a8e03cb279090bbcf9eea76ed1e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 15 Nov 2023 10:04:57 -0500 Subject: config: New attr_maybe_unused macro --- src/config.h | 13 ++++++++++++- src/fsade.c | 7 +++---- src/mtab.c | 3 ++- src/xspawn.c | 3 ++- 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/config.h b/src/config.h index a474fb3..9f95674 100644 --- a/src/config.h +++ b/src/config.h @@ -186,7 +186,7 @@ typedef long double max_align_t; // Wrappers for attributes /** - * Silence compiler warnings about switch/case fall-throughs. + * Silence warnings about switch/case fall-throughs. */ #if __has_c_attribute(fallthrough) # define fallthru [[fallthrough]] @@ -196,6 +196,17 @@ typedef long double max_align_t; # define fallthru ((void)0) #endif +/** + * Silence warnings about unused declarations. + */ +#if __has_c_attribute(maybe_unused) +# define attr_maybe_unused [[maybe_unused]] +#elif __has_attribute(unused) +# define attr_maybe_unused __attribute__((unused)) +#else +# define attr_maybe_unused +#endif + /** * Warn if a value is unused. */ diff --git a/src/fsade.c b/src/fsade.c index cbff47b..4d22d99 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -28,12 +28,11 @@ # include #endif -#if BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS - /** * Many of the APIs used here don't have *at() variants, but we can try to * emulate something similar if /proc/self/fd is available. */ +attr_maybe_unused static const char *fake_at(const struct BFTW *ftwbuf) { static atomic int proc_works = -1; @@ -67,6 +66,7 @@ fail: return ftwbuf->path; } +attr_maybe_unused static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { dstrfree((dchar *)path); @@ -76,6 +76,7 @@ static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { /** * Check if an error was caused by the absence of support or data for a feature. */ +attr_maybe_unused static bool is_absence_error(int error) { // If the OS doesn't support the feature, it's obviously not enabled for // any files @@ -114,8 +115,6 @@ static bool is_absence_error(int error) { return false; } -#endif // BFS_CAN_CHECK_ACL || BFS_CAN_CHECK_CAPABILITIES || BFS_CAN_CHECK_XATTRS - #if BFS_CAN_CHECK_ACL /** Check if a POSIX.1e ACL is non-trivial. */ diff --git a/src/mtab.c b/src/mtab.c index 384fdfc..6b6f9d1 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -59,7 +59,8 @@ struct bfs_mtab { /** * Add an entry to the mount table. */ -static inline int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { +attr_maybe_unused +static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { struct bfs_mtab_entry entry = { .path = strdup(path), .type = strdup(type), diff --git a/src/xspawn.c b/src/xspawn.c index 3974768..51432dd 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -73,7 +73,8 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) { } /** Set some posix_spawnattr flags. */ -static inline int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { +attr_maybe_unused +static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { short prev; errno = posix_spawnattr_getflags(&ctx->attr, &prev); if (errno != 0) { -- cgit v1.2.3 From 949a197112e2de23c31f2afb1247bf3568097b69 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 15 Nov 2023 16:09:47 -0500 Subject: ioq: Don't crash on allocation failures --- src/ioq.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/ioq.c b/src/ioq.c index ef89fa8..bd8d111 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -216,6 +216,10 @@ bfs_static_assert(IOQ_STRIDE % 2 == 1); /** Destroy an I/O command queue. */ static void ioqq_destroy(struct ioqq *ioqq) { + if (!ioqq) { + return; + } + for (size_t i = 0; i < ioqq->monitor_mask + 1; ++i) { ioq_monitor_destroy(&ioqq->monitors[i]); } -- cgit v1.2.3 From d79b7be41ea85cf52a724e348d1911a19503e959 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 19 Nov 2023 15:17:25 -0500 Subject: xspawn: Detect posix_spawn() support --- src/xspawn.c | 40 +++++++++++++++++++++++++++++++--------- src/xspawn.h | 8 +++++++- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/xspawn.c b/src/xspawn.c index 51432dd..03287c3 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -8,7 +8,6 @@ #include "list.h" #include #include -#include #include #include #include @@ -20,6 +19,10 @@ # include #endif +#if _POSIX_SPAWN > 0 +# include +#endif + /** * Types of spawn actions. */ @@ -44,9 +47,12 @@ struct bfs_spawn_action { }; int bfs_spawn_init(struct bfs_spawn *ctx) { - ctx->flags = BFS_SPAWN_USE_POSIX; + ctx->flags = 0; SLIST_INIT(ctx); +#if _POSIX_SPAWN > 0 + ctx->flags |= BFS_SPAWN_USE_POSIX; + errno = posix_spawn_file_actions_init(&ctx->actions); if (errno != 0) { return -1; @@ -57,13 +63,16 @@ int bfs_spawn_init(struct bfs_spawn *ctx) { posix_spawn_file_actions_destroy(&ctx->actions); return -1; } +#endif return 0; } int bfs_spawn_destroy(struct bfs_spawn *ctx) { +#if _POSIX_SPAWN > 0 posix_spawnattr_destroy(&ctx->attr); posix_spawn_file_actions_destroy(&ctx->actions); +#endif for_slist (struct bfs_spawn_action, action, ctx) { free(action); @@ -72,6 +81,7 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) { return 0; } +#if _POSIX_SPAWN > 0 /** Set some posix_spawnattr flags. */ attr_maybe_unused static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { @@ -91,6 +101,7 @@ static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { return 0; } +#endif // _POSIX_SPAWN > 0 /** Allocate a spawn action. */ static struct bfs_spawn_action *bfs_spawn_action(enum bfs_spawn_op op) { @@ -112,6 +123,7 @@ int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { return -1; } +#if _POSIX_SPAWN > 0 if (ctx->flags & BFS_SPAWN_USE_POSIX) { errno = posix_spawn_file_actions_addclose(&ctx->actions, fd); if (errno != 0) { @@ -119,6 +131,7 @@ int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { return -1; } } +#endif action->out_fd = fd; SLIST_APPEND(ctx, action); @@ -131,6 +144,7 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { return -1; } +#if _POSIX_SPAWN > 0 if (ctx->flags & BFS_SPAWN_USE_POSIX) { errno = posix_spawn_file_actions_adddup2(&ctx->actions, oldfd, newfd); if (errno != 0) { @@ -138,6 +152,7 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { return -1; } } +#endif action->in_fd = oldfd; action->out_fd = newfd; @@ -217,6 +232,7 @@ fail: return -1; } +#if _POSIX_SPAWN > 0 /** bfs_spawn() implementation using posix_spawn(). */ static pid_t bfs_posix_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { pid_t ret; @@ -227,6 +243,7 @@ static pid_t bfs_posix_spawn(const char *exe, const struct bfs_spawn *ctx, char return ret; } +#endif /** Actually exec() the new process. */ static noreturn void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { @@ -321,6 +338,17 @@ static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char * return pid; } +/** Call the right bfs_spawn() implementation. */ +static pid_t bfs_spawn_impl(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { +#if _POSIX_SPAWN > 0 + if (ctx->flags & BFS_SPAWN_USE_POSIX) { + return bfs_posix_spawn(exe, ctx, argv, envp); + } +#endif + + return bfs_fork_spawn(exe, ctx, argv, envp); +} + pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { // execvp()/posix_spawnp() are typically implemented with repeated // execv() calls for each $PATH component until one succeeds. It's @@ -338,13 +366,7 @@ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char envp = environ; } - pid_t ret; - if (ctx->flags & BFS_SPAWN_USE_POSIX) { - ret = bfs_posix_spawn(exe, ctx, argv, envp); - } else { - ret = bfs_fork_spawn(exe, ctx, argv, envp); - } - + pid_t ret = bfs_spawn_impl(exe, ctx, argv, envp); free(resolved); return ret; } diff --git a/src/xspawn.h b/src/xspawn.h index e3ad1eb..77273ee 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -9,9 +9,13 @@ #define BFS_XSPAWN_H #include "config.h" -#include #include #include +#include + +#if _POSIX_SPAWN > 0 +# include +#endif /** * bfs_spawn() flags. @@ -34,9 +38,11 @@ struct bfs_spawn { struct bfs_spawn_action *head; struct bfs_spawn_action **tail; +#if _POSIX_SPAWN > 0 /** posix_spawn() context, for when we can use it. */ posix_spawn_file_actions_t actions; posix_spawnattr_t attr; +#endif }; /** -- cgit v1.2.3 From dcf18f988e7737a5bbb77618da99cc5dc7035539 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 11:51:14 -0500 Subject: tests: Fix --stop --- tests/run.sh | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/tests/run.sh b/tests/run.sh index c7e819a..785c414 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -80,12 +80,15 @@ wait_test() { case $ret in 0) ((++passed)) + return 0 ;; $EX_SKIP) ((++skipped)) + return 0 ;; *) ((++failed)) + return $ret ;; esac } @@ -126,6 +129,9 @@ run_tests() { for TEST in "${TEST_CASES[@]}"; do if ((BG >= JOBS)); then wait_test + if (($? && STOP)); then + break + fi fi printf "$TEST_FMT" "$TEST" -- cgit v1.2.3 From 3114d6c94332cf4b7f2c0c87a425e513c006dd14 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:55:10 -0500 Subject: tests: Set abort_on_error for the sanitizers --- tests/util.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/util.sh b/tests/util.sh index bfa5d16..5bd3328 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -27,7 +27,7 @@ stdenv() { export LC_ALL=C export TZ=UTC0 - local SAN_OPTIONS="halt_on_error=1:log_to_syslog=0" + local SAN_OPTIONS="abort_on_error=1:halt_on_error=1:log_to_syslog=0" export ASAN_OPTIONS="$SAN_OPTIONS" export LSAN_OPTIONS="$SAN_OPTIONS" export MSAN_OPTIONS="$SAN_OPTIONS" -- cgit v1.2.3 From b9ed3f51d636bb0f182b0cc8a86ebf928b37f8c5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:41:35 -0500 Subject: bftw: Add some whitespace to struct bftw_args --- src/bftw.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/bftw.h b/src/bftw.h index 2b36b8b..e325d14 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -182,18 +182,22 @@ struct bftw_args { const char **paths; /** The number of starting paths. */ size_t npaths; + /** The callback to invoke. */ bftw_callback *callback; /** A pointer which is passed to the callback. */ void *ptr; + /** The maximum number of file descriptors to keep open. */ int nopenfd; /** The maximum number of threads to use. */ int nthreads; + /** Flags that control bftw() behaviour. */ enum bftw_flags flags; /** The search strategy to use. */ enum bftw_strategy strategy; + /** The parsed mount table, if available. */ const struct bfs_mtab *mtab; }; -- cgit v1.2.3 From 3fe1e7ca64bcd3724b00cf399ed9ae6f60e0a008 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 22 Nov 2023 16:52:33 -0500 Subject: alloc: New is_aligned() helper --- src/alloc.c | 10 +++++----- src/alloc.h | 5 +++++ 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index ff3ec6d..97f90bc 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -14,7 +14,7 @@ static void *xmemalign(size_t align, size_t size) { bfs_assert(has_single_bit(align)); bfs_assert(align >= sizeof(void *)); - bfs_assert((size & (align - 1)) == 0); + bfs_assert(is_aligned(align, size)); #if __APPLE__ void *ptr = NULL; @@ -27,7 +27,7 @@ static void *xmemalign(size_t align, size_t size) { void *alloc(size_t align, size_t size) { bfs_assert(has_single_bit(align)); - bfs_assert((size & (align - 1)) == 0); + bfs_assert(is_aligned(align, size)); if (size >> (SIZE_WIDTH - 1)) { errno = EOVERFLOW; @@ -43,7 +43,7 @@ void *alloc(size_t align, size_t size) { void *zalloc(size_t align, size_t size) { bfs_assert(has_single_bit(align)); - bfs_assert((size & (align - 1)) == 0); + bfs_assert(is_aligned(align, size)); if (size >> (SIZE_WIDTH - 1)) { errno = EOVERFLOW; @@ -90,7 +90,7 @@ static void chunk_set_next(const struct arena *arena, union chunk *chunk, union void arena_init(struct arena *arena, size_t align, size_t size) { bfs_assert(has_single_bit(align)); - bfs_assert((size & (align - 1)) == 0); + bfs_assert(is_aligned(align, size)); if (align < alignof(union chunk)) { align = alignof(union chunk); @@ -98,7 +98,7 @@ void arena_init(struct arena *arena, size_t align, size_t size) { if (size < sizeof(union chunk)) { size = sizeof(union chunk); } - bfs_assert((size & (align - 1)) == 0); + bfs_assert(is_aligned(align, size)); arena->chunks = NULL; arena->nslabs = 0; diff --git a/src/alloc.h b/src/alloc.h index 34f6949..15e4983 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -11,6 +11,11 @@ #include "config.h" #include +/** Check if a size is properly aligned. */ +static inline bool is_aligned(size_t align, size_t size) { + return (size & (align - 1)) == 0; +} + /** Round down to a multiple of an alignment. */ static inline size_t align_floor(size_t align, size_t size) { return size & ~(align - 1); -- cgit v1.2.3 From f9f43fe44f4a013aac94d5787cf827ec04b4c861 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:03:49 -0500 Subject: alloc: New ALLOC_MAX macro --- src/alloc.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index 97f90bc..8c88813 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -8,8 +8,16 @@ #include "sanity.h" #include #include +#include #include +/** The largest possible allocation size. */ +#if PTRDIFF_MAX < SIZE_MAX / 2 +# define ALLOC_MAX ((size_t)PTRDIFF_MAX) +#else +# define ALLOC_MAX (SIZE_MAX / 2) +#endif + /** Portable aligned_alloc()/posix_memalign(). */ static void *xmemalign(size_t align, size_t size) { bfs_assert(has_single_bit(align)); @@ -29,7 +37,7 @@ void *alloc(size_t align, size_t size) { bfs_assert(has_single_bit(align)); bfs_assert(is_aligned(align, size)); - if (size >> (SIZE_WIDTH - 1)) { + if (size > ALLOC_MAX) { errno = EOVERFLOW; return NULL; } @@ -45,7 +53,7 @@ void *zalloc(size_t align, size_t size) { bfs_assert(has_single_bit(align)); bfs_assert(is_aligned(align, size)); - if (size >> (SIZE_WIDTH - 1)) { + if (size > ALLOC_MAX) { errno = EOVERFLOW; return NULL; } -- cgit v1.2.3 From ae18c20d5a585ae4bc1e9ee6859230fee7f73ed8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:08:04 -0500 Subject: alloc: New helpers for aligned reallocation There is no aligned_realloc(), so the new xrealloc() function emulates it by manually reallocating and copying for over-aligned types. The new REALLOC_ARRAY() and REALLOC_FLEX() macros wrap xrealloc(). --- src/alloc.c | 42 ++++++++++++++++++++++++++++++++++++++---- src/alloc.h | 24 ++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index 8c88813..e83b273 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -69,6 +69,38 @@ void *zalloc(size_t align, size_t size) { return ret; } +void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size) { + bfs_assert(has_single_bit(align)); + bfs_assert(is_aligned(align, old_size)); + bfs_assert(is_aligned(align, new_size)); + + if (new_size == 0) { + free(ptr); + return NULL; + } else if (new_size > ALLOC_MAX) { + errno = EOVERFLOW; + return NULL; + } + + if (align <= alignof(max_align_t)) { + return realloc(ptr, new_size); + } + + // There is no aligned_realloc(), so reallocate and copy manually + void *ret = xmemalign(align, new_size); + if (!ret) { + return NULL; + } + + size_t min_size = old_size < new_size ? old_size : new_size; + if (min_size) { + memcpy(ret, ptr, min_size); + } + + free(ptr); + return ret; +} + /** * An arena allocator chunk. */ @@ -118,7 +150,8 @@ void arena_init(struct arena *arena, size_t align, size_t size) { /** Allocate a new slab. */ attr_cold static int slab_alloc(struct arena *arena) { - void **slabs = realloc(arena->slabs, sizeof_array(void *, arena->nslabs + 1)); + size_t nslabs = arena->nslabs; + void **slabs = REALLOC_ARRAY(void *, arena->slabs, nslabs, nslabs + 1); if (!slabs) { return -1; } @@ -132,7 +165,7 @@ static int slab_alloc(struct arena *arena) { // Trim off the excess size -= size % arena->size; // Double the size for every slab - size <<= arena->nslabs; + size <<= nslabs; // Allocate the slab void *slab = zalloc(arena->align, size); @@ -147,7 +180,8 @@ static int slab_alloc(struct arena *arena) { // We can rely on zero-initialized slabs, but others shouldn't sanitize_uninit(slab, size); - arena->chunks = arena->slabs[arena->nslabs++] = slab; + arena->chunks = arena->slabs[nslabs] = slab; + ++arena->nslabs; return 0; } @@ -220,7 +254,7 @@ static struct arena *varena_get(struct varena *varena, size_t count) { if (i >= varena->narenas) { size_t narenas = i + 1; - struct arena *arenas = realloc(varena->arenas, sizeof_array(struct arena, narenas)); + struct arena *arenas = REALLOC_ARRAY(struct arena, varena->arenas, varena->narenas, narenas); if (!arenas) { return NULL; } diff --git a/src/alloc.h b/src/alloc.h index 15e4983..a6dee99 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -155,6 +155,30 @@ void *zalloc(size_t align, size_t size); #define ZALLOC_FLEX(type, member, count) \ (type *)zalloc(alignof(type), sizeof_flex(type, member, count)) +/** + * Alignment-aware realloc(). + * + * @param ptr + * The pointer to reallocate. + * @param align + * The required alignment. + * @param old_size + * The previous allocation size. + * @param new_size + * The new allocation size. + * @return + * The reallocated memory, or NULL on failure. + */ +void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size); + +/** Reallocate memory for an array. */ +#define REALLOC_ARRAY(type, ptr, old_count, new_count) \ + (type *)xrealloc((ptr), alignof(type), sizeof_array(type, old_count), sizeof_array(type, new_count)) + +/** Reallocate memory for a flexible struct. */ +#define REALLOC_FLEX(type, member, ptr, old_count, new_count) \ + (type *)xrealloc((ptr), alignof(type), sizeof_flex(type, member, old_count), sizeof_flex(type, member, new_count)) + /** * An arena allocator for fixed-size types. * -- cgit v1.2.3 From 181e045f0c02d530060d453055934c0d542ed341 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:13:07 -0500 Subject: dstring: Use the new REALLOC_FLEX() macro While I'm at it, switch the capacity to include the NUL byte, so that it could be used by __counted_by() for example. And fix a theoretical provenance issue. --- src/dstring.c | 104 +++++++++++++++++++++++++++++++++------------------------- src/dstring.h | 12 +++---- 2 files changed, 65 insertions(+), 51 deletions(-) diff --git a/src/dstring.c b/src/dstring.c index f947741..bc18308 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -12,46 +12,60 @@ #include /** - * The memory representation of a dynamic string. Users get a pointer to data. + * The memory representation of a dynamic string. Users get a pointer to str. */ struct dstring { - size_t capacity; - size_t length; - alignas(dchar) char data[]; + /** Capacity of the string, *including* the terminating NUL. */ + size_t cap; + /** Length of the string, *excluding* the terminating NUL. */ + size_t len; + /** The string itself. */ + alignas(dchar) char str[]; }; -/** Get the string header from the string data pointer. */ +#define DSTR_OFFSET offsetof(struct dstring, str) + +/** Back up to the header from a pointer to dstring::str. */ static struct dstring *dstrheader(const dchar *dstr) { - return (struct dstring *)(dstr - offsetof(struct dstring, data)); + return (struct dstring *)(dstr - DSTR_OFFSET); } -/** Get the correct size for a dstring with the given capacity. */ -static size_t dstrsize(size_t capacity) { - return sizeof_flex(struct dstring, data, capacity + 1); +/** + * In some provenance models, the expression `header->str` has its provenance + * restricted to just the `str` field itself, making a future dstrheader() + * illegal. This alternative is guaranteed to preserve provenance for the entire + * allocation. + * + * - https://stackoverflow.com/q/25296019 + * - https://mastodon.social/@void_friend@tech.lgbt/111144859908104311 + */ +static dchar *dstrdata(struct dstring *header) { + return (char *)header + DSTR_OFFSET; } /** Allocate a dstring with the given contents. */ -static dchar *dstralloc_impl(size_t capacity, size_t length, const char *data) { +static dchar *dstralloc_impl(size_t cap, size_t len, const char *str) { // Avoid reallocations for small strings - if (capacity < 7) { - capacity = 7; + if (cap < DSTR_OFFSET) { + cap = DSTR_OFFSET; } - struct dstring *header = malloc(dstrsize(capacity)); + struct dstring *header = ALLOC_FLEX(struct dstring, str, cap); if (!header) { return NULL; } - header->capacity = capacity; - header->length = length; + header->cap = cap; + header->len = len; - memcpy(header->data, data, length); - header->data[length] = '\0'; - return header->data; + char *ret = dstrdata(header); + memcpy(ret, str, len); + ret[len] = '\0'; + return ret; } -dchar *dstralloc(size_t capacity) { - return dstralloc_impl(capacity, 0, ""); +dchar *dstralloc(size_t cap) { + return dstralloc_impl(cap + 1, 0, ""); } dchar *dstrdup(const char *str) { @@ -67,44 +81,45 @@ dchar *dstrddup(const dchar *dstr) { } dchar *dstrxdup(const char *str, size_t len) { - return dstralloc_impl(len, len, str); + return dstralloc_impl(len + 1, len, str); } size_t dstrlen(const dchar *dstr) { - return dstrheader(dstr)->length; + return dstrheader(dstr)->len; } -int dstreserve(dchar **dstr, size_t capacity) { +int dstreserve(dchar **dstr, size_t cap) { if (!*dstr) { - *dstr = dstralloc(capacity); + *dstr = dstralloc(cap); return *dstr ? 0 : -1; } struct dstring *header = dstrheader(*dstr); + size_t old_cap = header->cap; + size_t new_cap = cap + 1; // Terminating NUL + if (old_cap >= new_cap) { + return 0; + } - if (capacity > header->capacity) { - capacity = bit_ceil(capacity + 1) - 1; - - header = realloc(header, dstrsize(capacity)); - if (!header) { - return -1; - } - header->capacity = capacity; - - *dstr = header->data; + new_cap = bit_ceil(new_cap); + header = REALLOC_FLEX(struct dstring, str, header, old_cap, new_cap); + if (!header) { + return -1; } + header->cap = new_cap; + *dstr = dstrdata(header); return 0; } -int dstresize(dchar **dstr, size_t length) { - if (dstreserve(dstr, length) != 0) { +int dstresize(dchar **dstr, size_t len) { + if (dstreserve(dstr, len) != 0) { return -1; } struct dstring *header = dstrheader(*dstr); - header->length = length; - header->data[length] = '\0'; + header->len = len; + header->str[len] = '\0'; return 0; } @@ -196,21 +211,21 @@ int dstrvcatf(dchar **str, const char *format, va_list args) { // Guess a capacity to try to avoid calling vsnprintf() twice size_t len = dstrlen(*str); dstreserve(str, len + 2 * strlen(format)); - size_t cap = dstrheader(*str)->capacity; + size_t cap = dstrheader(*str)->cap; va_list copy; va_copy(copy, args); char *tail = *str + len; - int ret = vsnprintf(tail, cap - len + 1, format, args); + size_t tail_cap = cap - len; + int ret = vsnprintf(tail, tail_cap, format, args); if (ret < 0) { goto fail; } size_t tail_len = ret; - if (tail_len > cap - len) { - cap = len + tail_len; - if (dstreserve(str, cap) != 0) { + if (tail_len >= tail_cap) { + if (dstreserve(str, len + tail_len) != 0) { goto fail; } @@ -224,8 +239,7 @@ int dstrvcatf(dchar **str, const char *format, va_list args) { va_end(copy); - struct dstring *header = dstrheader(*str); - header->length += tail_len; + dstrheader(*str)->len += tail_len; return 0; fail: diff --git a/src/dstring.h b/src/dstring.h index 07b4ee9..2a94438 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -38,11 +38,11 @@ void dstrfree(dchar *dstr); /** * Allocate a dynamic string. * - * @param capacity + * @param cap * The initial capacity of the string. */ attr_malloc(dstrfree, 1) -dchar *dstralloc(size_t capacity); +dchar *dstralloc(size_t cap); /** * Create a dynamic copy of a string. @@ -99,24 +99,24 @@ size_t dstrlen(const dchar *dstr); * * @param dstr * The dynamic string to preallocate. - * @param capacity + * @param cap * The new capacity for the string. * @return * 0 on success, -1 on failure. */ -int dstreserve(dchar **dstr, size_t capacity); +int dstreserve(dchar **dstr, size_t cap); /** * Resize a dynamic string. * * @param dstr * The dynamic string to resize. - * @param length + * @param len * The new length for the dynamic string. * @return * 0 on success, -1 on failure. */ -int dstresize(dchar **dstr, size_t length); +int dstresize(dchar **dstr, size_t len); /** * Append to a dynamic string. -- cgit v1.2.3 From 5c91f597459de2c5973c9210a3854a19bbc67577 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:18:35 -0500 Subject: exec: Use the new REALLOC_ARRAY() macro --- src/exec.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exec.c b/src/exec.c index 3ff6f92..90b3598 100644 --- a/src/exec.c +++ b/src/exec.c @@ -601,7 +601,7 @@ static int bfs_exec_push(struct bfs_exec *execbuf, char *arg) { if (execbuf->argc + 1 >= execbuf->argv_cap) { size_t cap = 2 * execbuf->argv_cap; - char **argv = realloc(execbuf->argv, sizeof_array(char *, cap)); + char **argv = REALLOC_ARRAY(char *, execbuf->argv, execbuf->argv_cap, cap); if (!argv) { return -1; } -- cgit v1.2.3 From 9032d107ab759450c21fea8f82865ff48c743132 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:31:33 -0500 Subject: alloc: New helpers for growing dynamic arrays --- src/alloc.c | 66 ++++++++++++++++++++++++++++++++++++++++--------------------- src/alloc.h | 36 +++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 22 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index e83b273..467f0f0 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -101,6 +101,34 @@ void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size) { return ret; } +void *reserve(void *ptr, size_t align, size_t size, size_t count) { + // No need to overflow-check the current size + size_t old_size = size * count; + + // Capacity is doubled every power of two, from 0→1, 1→2, 2→4, etc. + // If we stayed within the same size class, re-use ptr. + if (count & (count - 1)) { + // Tell sanitizers about the new array element + sanitize_alloc((char *)ptr + old_size, size); + errno = 0; + return ptr; + } + + // No need to overflow-check; xrealloc() will fail before we overflow + size_t new_size = count ? 2 * old_size : size; + void *ret = xrealloc(ptr, align, old_size, new_size); + if (!ret) { + // errno is used to communicate success/failure to the RESERVE() macro + bfs_assert(errno != 0); + return ptr; + } + + // Pretend we only allocated one more element + sanitize_free((char *)ret + old_size + size, new_size - old_size - size); + errno = 0; + return ret; +} + /** * An arena allocator chunk. */ @@ -150,13 +178,6 @@ void arena_init(struct arena *arena, size_t align, size_t size) { /** Allocate a new slab. */ attr_cold static int slab_alloc(struct arena *arena) { - size_t nslabs = arena->nslabs; - void **slabs = REALLOC_ARRAY(void *, arena->slabs, nslabs, nslabs + 1); - if (!slabs) { - return -1; - } - arena->slabs = slabs; - // Make the initial allocation size ~4K size_t size = 4096; if (size < arena->size) { @@ -165,7 +186,7 @@ static int slab_alloc(struct arena *arena) { // Trim off the excess size -= size % arena->size; // Double the size for every slab - size <<= nslabs; + size <<= arena->nslabs; // Allocate the slab void *slab = zalloc(arena->align, size); @@ -173,6 +194,13 @@ static int slab_alloc(struct arena *arena) { return -1; } + // Grow the slab array + void **pslab = RESERVE(void *, &arena->slabs, &arena->nslabs); + if (!pslab) { + free(slab); + return -1; + } + // Fix the last chunk->next offset void *last = (char *)slab + size - arena->size; chunk_set_next(arena, last, arena->chunks); @@ -180,8 +208,7 @@ static int slab_alloc(struct arena *arena) { // We can rely on zero-initialized slabs, but others shouldn't sanitize_uninit(slab, size); - arena->chunks = arena->slabs[nslabs] = slab; - ++arena->nslabs; + arena->chunks = *pslab = slab; return 0; } @@ -252,21 +279,16 @@ static size_t varena_exact_size(const struct varena *varena, size_t count) { static struct arena *varena_get(struct varena *varena, size_t count) { size_t i = varena_size_class(varena, count); - if (i >= varena->narenas) { - size_t narenas = i + 1; - struct arena *arenas = REALLOC_ARRAY(struct arena, varena->arenas, varena->narenas, narenas); - if (!arenas) { + while (i >= varena->narenas) { + size_t j = varena->narenas; + struct arena *arena = RESERVE(struct arena, &varena->arenas, &varena->narenas); + if (!arena) { return NULL; } - for (size_t j = varena->narenas; j < narenas; ++j) { - size_t shift = j + varena->shift; - size_t size = varena_exact_size(varena, (size_t)1 << shift); - arena_init(&arenas[j], varena->align, size); - } - - varena->narenas = narenas; - varena->arenas = arenas; + size_t shift = j + varena->shift; + size_t size = varena_exact_size(varena, (size_t)1 << shift); + arena_init(arena, varena->align, size); } return &varena->arenas[i]; diff --git a/src/alloc.h b/src/alloc.h index a6dee99..7132470 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -9,6 +9,7 @@ #define BFS_ALLOC_H #include "config.h" +#include #include /** Check if a size is properly aligned. */ @@ -179,6 +180,41 @@ void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size); #define REALLOC_FLEX(type, member, ptr, old_count, new_count) \ (type *)xrealloc((ptr), alignof(type), sizeof_flex(type, member, old_count), sizeof_flex(type, member, new_count)) +/** + * Reserve space for one more element in a dynamic array. + * + * @param ptr + * The pointer to reallocate. + * @param align + * The required alignment. + * @param count + * The current size of the array. + * @return + * The reallocated memory, on both success *and* failure. On success, + * errno will be set to zero, and the returned pointer will have room + * for (count + 1) elements. On failure, errno will be non-zero, and + * ptr will returned unchanged. + */ +void *reserve(void *ptr, size_t align, size_t size, size_t count); + +/** + * Convenience macro to grow a dynamic array. + * + * @param type + * The array element type. + * @param type **ptr + * A pointer to the array. + * @param size_t *count + * A pointer to the array's size. + * @return + * On success, a pointer to the newly reserved array element, i.e. + * `*ptr + *count++`. On failure, NULL is returned, and both *ptr and + * *count remain unchanged. + */ +#define RESERVE(type, ptr, count) \ + ((*ptr) = reserve((*ptr), alignof(type), sizeof(type), (*count)), \ + errno ? NULL : (*ptr) + (*count)++) + /** * An arena allocator for fixed-size types. * -- cgit v1.2.3 From cdbd5e6fcf1f447b515e717f7d7194471ef714a7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:35:57 -0500 Subject: ctx: Switch paths from darray to RESERVE() --- src/ctx.c | 5 ++--- src/ctx.h | 3 +++ src/eval.c | 3 +-- src/parse.c | 20 ++++++++++---------- 4 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/ctx.c b/src/ctx.c index 692f5c5..f5dc465 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -4,7 +4,6 @@ #include "ctx.h" #include "alloc.h" #include "color.h" -#include "darray.h" #include "diag.h" #include "expr.h" #include "mtab.h" @@ -242,10 +241,10 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { free_colors(ctx->colors); - for (size_t i = 0; i < darray_length(ctx->paths); ++i) { + for (size_t i = 0; i < ctx->npaths; ++i) { free((char *)ctx->paths[i]); } - darray_free(ctx->paths); + free(ctx->paths); free(ctx->argv); free(ctx); diff --git a/src/ctx.h b/src/ctx.h index 1c7df63..75891da 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -27,6 +27,9 @@ struct bfs_ctx { /** The root paths. */ const char **paths; + /** The number of root paths. */ + size_t npaths; + /** The main command line expression. */ struct bfs_expr *expr; /** An expression for files to filter out. */ diff --git a/src/eval.c b/src/eval.c index eb4a0ca..55b14f0 100644 --- a/src/eval.c +++ b/src/eval.c @@ -12,7 +12,6 @@ #include "color.h" #include "config.h" #include "ctx.h" -#include "darray.h" #include "diag.h" #include "dir.h" #include "dstring.h" @@ -1622,7 +1621,7 @@ int bfs_eval(struct bfs_ctx *ctx) { struct bftw_args bftw_args = { .paths = ctx->paths, - .npaths = darray_length(ctx->paths), + .npaths = ctx->npaths, .callback = eval_callback, .ptr = &args, .nopenfd = fdlimit, diff --git a/src/parse.c b/src/parse.c index d3938fc..13e65bc 100644 --- a/src/parse.c +++ b/src/parse.c @@ -15,7 +15,6 @@ #include "color.h" #include "config.h" #include "ctx.h" -#include "darray.h" #include "diag.h" #include "dir.h" #include "eval.h" @@ -481,16 +480,17 @@ static char **parser_advance(struct parser_state *state, enum token_type type, s * Parse a root path. */ static int parse_root(struct parser_state *state, const char *path) { - char *copy = strdup(path); - if (!copy) { - parse_perror(state, "strdup()"); + struct bfs_ctx *ctx = state->ctx; + const char **root = RESERVE(const char *, &ctx->paths, &ctx->npaths); + if (!root) { + parse_perror(state, "RESERVE()"); return -1; } - struct bfs_ctx *ctx = state->ctx; - if (DARRAY_PUSH(&ctx->paths, ©) != 0) { - parse_perror(state, "DARRAY_PUSH()"); - free(copy); + *root = strdup(path); + if (!*root) { + --ctx->npaths; + parse_perror(state, "strdup()"); return -1; } @@ -3617,7 +3617,7 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { } } - for (size_t i = 0; i < darray_length(ctx->paths); ++i) { + for (size_t i = 0; i < ctx->npaths; ++i) { const char *path = ctx->paths[i]; char c = path[0]; if (c == '-' || c == '(' || c == ')' || c == '!' || c == ',') { @@ -3778,7 +3778,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { goto fail; } - if (darray_length(ctx->paths) == 0 && state.implicit_root) { + if (ctx->npaths == 0 && state.implicit_root) { if (parse_root(&state, ".") != 0) { goto fail; } -- cgit v1.2.3 From 4cdbbed2dccdf4d15eae153e3d1edf589fd24564 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:36:58 -0500 Subject: mtab: Switch from darray to RESERVE() --- src/mtab.c | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/mtab.c b/src/mtab.c index 6b6f9d1..082150c 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -5,7 +5,6 @@ #include "alloc.h" #include "bfstd.h" #include "config.h" -#include "darray.h" #include "stat.h" #include "trie.h" #include @@ -37,7 +36,7 @@ /** * A mount point in the table. */ -struct bfs_mtab_entry { +struct bfs_mount { /** The path to the mount point. */ char *path; /** The filesystem type. */ @@ -46,7 +45,10 @@ struct bfs_mtab_entry { struct bfs_mtab { /** The array of mount points. */ - struct bfs_mtab_entry *entries; + struct bfs_mount *mounts; + /** The number of mount points. */ + size_t nmounts; + /** The basenames of every mount point. */ struct trie names; @@ -61,17 +63,15 @@ struct bfs_mtab { */ attr_maybe_unused static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { - struct bfs_mtab_entry entry = { - .path = strdup(path), - .type = strdup(type), - }; - - if (!entry.path || !entry.type) { - goto fail_entry; + struct bfs_mount *mount = RESERVE(struct bfs_mount, &mtab->mounts, &mtab->nmounts); + if (!mount) { + return -1; } - if (DARRAY_PUSH(&mtab->entries, &entry) != 0) { - goto fail_entry; + mount->path = strdup(path); + mount->type = strdup(type); + if (!mount->path || !mount->type) { + goto fail; } const char *name = path + xbaseoff(path); @@ -81,10 +81,10 @@ static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *typ return 0; -fail_entry: - free(entry.type); - free(entry.path); fail: + free(mount->type); + free(mount->path); + --mtab->nmounts; return -1; } @@ -194,9 +194,9 @@ static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { int parent_ret = -1; struct bfs_stat parent_stat; - for (size_t i = 0; i < darray_length(mtab->entries); ++i) { - struct bfs_mtab_entry *entry = &mtab->entries[i]; - const char *path = entry->path; + for (size_t i = 0; i < mtab->nmounts; ++i) { + struct bfs_mount *mount = &mtab->mounts[i]; + const char *path = mount->path; int fd = AT_FDCWD; char *dir = xdirname(path); @@ -239,7 +239,7 @@ static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { struct trie_leaf *leaf = trie_insert_mem(&mtab->types, &sb.dev, sizeof(sb.dev)); if (leaf) { - leaf->value = entry->type; + leaf->value = mount->type; } else { goto fail; } @@ -281,11 +281,11 @@ void bfs_mtab_free(struct bfs_mtab *mtab) { trie_destroy(&mtab->types); trie_destroy(&mtab->names); - for (size_t i = 0; i < darray_length(mtab->entries); ++i) { - free(mtab->entries[i].type); - free(mtab->entries[i].path); + for (size_t i = 0; i < mtab->nmounts; ++i) { + free(mtab->mounts[i].type); + free(mtab->mounts[i].path); } - darray_free(mtab->entries); + free(mtab->mounts); free(mtab); } -- cgit v1.2.3 From 23793f0805519afb304b6b820c46abeaf545f607 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:38:07 -0500 Subject: printf: Switch from darray to RESERVE() --- src/printf.c | 370 +++++++++++++++++++++++++++++++---------------------------- 1 file changed, 195 insertions(+), 175 deletions(-) diff --git a/src/printf.c b/src/printf.c index 02ca586..1fe8cc3 100644 --- a/src/printf.c +++ b/src/printf.c @@ -7,7 +7,6 @@ #include "color.h" #include "config.h" #include "ctx.h" -#include "darray.h" #include "diag.h" #include "dir.h" #include "dstring.h" @@ -25,16 +24,17 @@ #include #include +struct bfs_fmt; + /** * A function implementing a printf directive. */ -typedef int bfs_printf_fn(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf); +typedef int bfs_printf_fn(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf); /** - * A single printf directive like %f or %#4m. The whole format string is stored - * as a darray of these. + * A single formatting directive like %f or %#4m. */ -struct bfs_printf { +struct bfs_fmt { /** The printing function to invoke. */ bfs_printf_fn *fn; /** String data associated with this directive. */ @@ -47,10 +47,20 @@ struct bfs_printf { void *ptr; }; +/** + * An entire format string. + */ +struct bfs_printf { + /** An array of formatting directives. */ + struct bfs_fmt *fmts; + /** The number of directives. */ + size_t nfmts; +}; + /** Print some text as-is. */ -static int bfs_printf_literal(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { - size_t len = dstrlen(directive->str); - if (fwrite(directive->str, 1, len, cfile->file) == len) { +static int bfs_printf_literal(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { + size_t len = dstrlen(fmt->str); + if (fwrite(fmt->str, 1, len, cfile->file) == len) { return 0; } else { return -1; @@ -58,13 +68,13 @@ static int bfs_printf_literal(CFILE *cfile, const struct bfs_printf *directive, } /** \c: flush */ -static int bfs_printf_flush(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_flush(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { return fflush(cfile->file); } /** Check if we can safely colorize this directive. */ -static bool should_color(CFILE *cfile, const struct bfs_printf *directive) { - return cfile->colors && strcmp(directive->str, "%s") == 0; +static bool should_color(CFILE *cfile, const struct bfs_fmt *fmt) { + return cfile->colors && strcmp(fmt->str, "%s") == 0; } /** @@ -79,15 +89,15 @@ static bool should_color(CFILE *cfile, const struct bfs_printf *directive) { /** * Common entry point for fprintf() with a dynamic format string. */ -static int dyn_fprintf(FILE *file, const struct bfs_printf *directive, ...) { +static int dyn_fprintf(FILE *file, const struct bfs_fmt *fmt, ...) { va_list args; - va_start(args, directive); + va_start(args, fmt); #if __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wformat-nonliteral" #endif - int ret = vfprintf(file, directive->str, args); + int ret = vfprintf(file, fmt->str, args); #if __GNUC__ # pragma GCC diagnostic pop #endif @@ -97,7 +107,7 @@ static int dyn_fprintf(FILE *file, const struct bfs_printf *directive, ...) { } /** %a, %c, %t: ctime() */ -static int bfs_printf_ctime(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_ctime(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { // Not using ctime() itself because GNU find adds nanoseconds static const char *days[] = {"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"}; static const char *months[] = {"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}; @@ -107,7 +117,7 @@ static int bfs_printf_ctime(CFILE *cfile, const struct bfs_printf *directive, co return -1; } - const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field); + const struct timespec *ts = bfs_stat_time(statbuf, fmt->stat_field); if (!ts) { return -1; } @@ -127,17 +137,17 @@ static int bfs_printf_ctime(CFILE *cfile, const struct bfs_printf *directive, co (long)ts->tv_nsec, 1900 + tm.tm_year); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %A, %B/%W, %C, %T: strftime() */ -static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_strftime(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } - const struct timespec *ts = bfs_stat_time(statbuf, directive->stat_field); + const struct timespec *ts = bfs_stat_time(statbuf, fmt->stat_field); if (!ts) { return -1; } @@ -150,7 +160,7 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, int ret; char buf[256]; char format[] = "% "; - switch (directive->c) { + switch (fmt->c) { // Non-POSIX strftime() features case '@': ret = snprintf(buf, sizeof(buf), "%lld.%09ld0", (long long)ts->tv_sec, (long)ts->tv_nsec); @@ -187,7 +197,7 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, // POSIX strftime() features default: - format[1] = directive->c; + format[1] = fmt->c; #if __GNUC__ # pragma GCC diagnostic push # pragma GCC diagnostic ignored "-Wformat-nonliteral" @@ -202,11 +212,11 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_printf *directive, bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); (void)ret; - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %b: blocks */ -static int bfs_printf_b(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_b(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; @@ -214,78 +224,78 @@ static int bfs_printf_b(CFILE *cfile, const struct bfs_printf *directive, const uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + 511) / 512; BFS_PRINTF_BUF(buf, "%ju", blocks); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %d: depth */ -static int bfs_printf_d(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { - return dyn_fprintf(cfile->file, directive, (intmax_t)ftwbuf->depth); +static int bfs_printf_d(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { + return dyn_fprintf(cfile->file, fmt, (intmax_t)ftwbuf->depth); } /** %D: device */ -static int bfs_printf_D(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_D(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->dev); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %f: file name */ -static int bfs_printf_f(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { - if (should_color(cfile, directive)) { +static int bfs_printf_f(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { + if (should_color(cfile, fmt)) { return cfprintf(cfile, "%pF", ftwbuf); } else { - return dyn_fprintf(cfile->file, directive, ftwbuf->path + ftwbuf->nameoff); + return dyn_fprintf(cfile->file, fmt, ftwbuf->path + ftwbuf->nameoff); } } /** %F: file system type */ -static int bfs_printf_F(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_F(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } - const char *type = bfs_fstype(directive->ptr, statbuf); + const char *type = bfs_fstype(fmt->ptr, statbuf); if (!type) { return -1; } - return dyn_fprintf(cfile->file, directive, type); + return dyn_fprintf(cfile->file, fmt, type); } /** %G: gid */ -static int bfs_printf_G(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_G(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->gid); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %g: group name */ -static int bfs_printf_g(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_g(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } - struct bfs_groups *groups = directive->ptr; + struct bfs_groups *groups = fmt->ptr; const struct group *grp = bfs_getgrgid(groups, statbuf->gid); if (!grp) { - return bfs_printf_G(cfile, directive, ftwbuf); + return bfs_printf_G(cfile, fmt, ftwbuf); } - return dyn_fprintf(cfile->file, directive, grp->gr_name); + return dyn_fprintf(cfile->file, fmt, grp->gr_name); } /** %h: leading directories */ -static int bfs_printf_h(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_h(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { char *copy = NULL; const char *buf; @@ -307,10 +317,10 @@ static int bfs_printf_h(CFILE *cfile, const struct bfs_printf *directive, const } int ret; - if (should_color(cfile, directive)) { + if (should_color(cfile, fmt)) { ret = cfprintf(cfile, "${di}%pQ${rs}", buf); } else { - ret = dyn_fprintf(cfile->file, directive, buf); + ret = dyn_fprintf(cfile->file, fmt, buf); } free(copy); @@ -318,31 +328,31 @@ static int bfs_printf_h(CFILE *cfile, const struct bfs_printf *directive, const } /** %H: current root */ -static int bfs_printf_H(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { - if (should_color(cfile, directive)) { +static int bfs_printf_H(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { + if (should_color(cfile, fmt)) { if (ftwbuf->depth == 0) { return cfprintf(cfile, "%pP", ftwbuf); } else { return cfprintf(cfile, "${di}%pQ${rs}", ftwbuf->root); } } else { - return dyn_fprintf(cfile->file, directive, ftwbuf->root); + return dyn_fprintf(cfile->file, fmt, ftwbuf->root); } } /** %i: inode */ -static int bfs_printf_i(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_i(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->ino); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %k: 1K blocks */ -static int bfs_printf_k(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_k(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; @@ -350,16 +360,16 @@ static int bfs_printf_k(CFILE *cfile, const struct bfs_printf *directive, const uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + 1023) / 1024; BFS_PRINTF_BUF(buf, "%ju", blocks); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %l: link target */ -static int bfs_printf_l(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_l(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { char *buf = NULL; const char *target = ""; if (ftwbuf->type == BFS_LNK) { - if (should_color(cfile, directive)) { + if (should_color(cfile, fmt)) { return cfprintf(cfile, "%pL", ftwbuf); } @@ -372,23 +382,23 @@ static int bfs_printf_l(CFILE *cfile, const struct bfs_printf *directive, const } } - int ret = dyn_fprintf(cfile->file, directive, target); + int ret = dyn_fprintf(cfile->file, fmt, target); free(buf); return ret; } /** %m: mode */ -static int bfs_printf_m(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_m(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } - return dyn_fprintf(cfile->file, directive, (unsigned int)(statbuf->mode & 07777)); + return dyn_fprintf(cfile->file, fmt, (unsigned int)(statbuf->mode & 07777)); } /** %M: symbolic mode */ -static int bfs_printf_M(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_M(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; @@ -396,37 +406,37 @@ static int bfs_printf_M(CFILE *cfile, const struct bfs_printf *directive, const char buf[11]; xstrmode(statbuf->mode, buf); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %n: link count */ -static int bfs_printf_n(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_n(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->nlink); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %p: full path */ -static int bfs_printf_p(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { - if (should_color(cfile, directive)) { +static int bfs_printf_p(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { + if (should_color(cfile, fmt)) { return cfprintf(cfile, "%pP", ftwbuf); } else { - return dyn_fprintf(cfile->file, directive, ftwbuf->path); + return dyn_fprintf(cfile->file, fmt, ftwbuf->path); } } /** %P: path after root */ -static int bfs_printf_P(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_P(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { size_t offset = strlen(ftwbuf->root); if (ftwbuf->path[offset] == '/') { ++offset; } - if (should_color(cfile, directive)) { + if (should_color(cfile, fmt)) { if (ftwbuf->depth == 0) { return 0; } @@ -436,23 +446,23 @@ static int bfs_printf_P(CFILE *cfile, const struct bfs_printf *directive, const copybuf.nameoff -= offset; return cfprintf(cfile, "%pP", ©buf); } else { - return dyn_fprintf(cfile->file, directive, ftwbuf->path + offset); + return dyn_fprintf(cfile->file, fmt, ftwbuf->path + offset); } } /** %s: size */ -static int bfs_printf_s(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_s(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->size); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %S: sparseness */ -static int bfs_printf_S(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_S(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; @@ -464,34 +474,34 @@ static int bfs_printf_S(CFILE *cfile, const struct bfs_printf *directive, const } else { sparsity = (double)BFS_STAT_BLKSIZE * statbuf->blocks / statbuf->size; } - return dyn_fprintf(cfile->file, directive, sparsity); + return dyn_fprintf(cfile->file, fmt, sparsity); } /** %U: uid */ -static int bfs_printf_U(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_U(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->uid); - return dyn_fprintf(cfile->file, directive, buf); + return dyn_fprintf(cfile->file, fmt, buf); } /** %u: user name */ -static int bfs_printf_u(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_u(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const struct bfs_stat *statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (!statbuf) { return -1; } - struct bfs_users *users = directive->ptr; + struct bfs_users *users = fmt->ptr; const struct passwd *pwd = bfs_getpwuid(users, statbuf->uid); if (!pwd) { - return bfs_printf_U(cfile, directive, ftwbuf); + return bfs_printf_U(cfile, fmt, ftwbuf); } - return dyn_fprintf(cfile->file, directive, pwd->pw_name); + return dyn_fprintf(cfile->file, fmt, pwd->pw_name); } static const char *bfs_printf_type(enum bfs_type type) { @@ -518,17 +528,17 @@ static const char *bfs_printf_type(enum bfs_type type) { } /** %y: type */ -static int bfs_printf_y(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const char *type = bfs_printf_type(ftwbuf->type); - return dyn_fprintf(cfile->file, directive, type); + return dyn_fprintf(cfile->file, fmt, type); } /** %Y: target type */ -static int bfs_printf_Y(CFILE *cfile, const struct bfs_printf *directive, const struct BFTW *ftwbuf) { +static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { int error = 0; if (ftwbuf->type != BFS_LNK) { - return bfs_printf_y(cfile, directive, ftwbuf); + return bfs_printf_y(cfile, fmt, ftwbuf); } const char *type = "U"; @@ -552,7 +562,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_printf *directive, const } } - int ret = dyn_fprintf(cfile->file, directive, type); + int ret = dyn_fprintf(cfile->file, fmt, type); if (error != 0) { ret = -1; errno = error; @@ -563,21 +573,20 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_printf *directive, const /** * Append a literal string to the chain. */ -static int append_literal(const struct bfs_ctx *ctx, struct bfs_printf **format, char **literal) { +static int append_literal(const struct bfs_ctx *ctx, struct bfs_printf *format, dchar **literal) { if (dstrlen(*literal) == 0) { return 0; } - struct bfs_printf directive = { - .fn = bfs_printf_literal, - .str = *literal, - }; - - if (DARRAY_PUSH(format, &directive) != 0) { - bfs_perror(ctx, "DARRAY_PUSH()"); + struct bfs_fmt *fmt = RESERVE(struct bfs_fmt, &format->fmts, &format->nfmts); + if (!fmt) { + bfs_perror(ctx, "RESERVE()"); return -1; } + fmt->fn = bfs_printf_literal; + fmt->str = *literal; + *literal = dstralloc(0); if (!*literal) { bfs_perror(ctx, "dstralloc()"); @@ -590,21 +599,27 @@ static int append_literal(const struct bfs_ctx *ctx, struct bfs_printf **format, /** * Append a printf directive to the chain. */ -static int append_directive(const struct bfs_ctx *ctx, struct bfs_printf **format, char **literal, struct bfs_printf *directive) { +static int append_directive(const struct bfs_ctx *ctx, struct bfs_printf *format, dchar **literal, struct bfs_fmt *fmt) { if (append_literal(ctx, format, literal) != 0) { return -1; } - if (DARRAY_PUSH(format, directive) != 0) { - bfs_perror(ctx, "DARRAY_PUSH()"); + struct bfs_fmt *dest = RESERVE(struct bfs_fmt, &format->fmts, &format->nfmts); + if (!dest) { + bfs_perror(ctx, "RESERVE()"); return -1; } + *dest = *fmt; return 0; } int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const char *format) { - expr->printf = NULL; + expr->printf = ZALLOC(struct bfs_printf); + if (!expr->printf) { + bfs_perror(ctx, "zalloc()"); + return -1; + } dchar *literal = dstralloc(0); if (!literal) { @@ -640,10 +655,10 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha case 'c': { - struct bfs_printf directive = { + struct bfs_fmt fmt = { .fn = bfs_printf_flush, }; - if (append_directive(ctx, &expr->printf, &literal, &directive) != 0) { + if (append_directive(ctx, expr->printf, &literal, &fmt) != 0) { goto error; } goto done; @@ -665,15 +680,15 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha goto one_char; } - struct bfs_printf directive = { + struct bfs_fmt fmt = { .str = dstralloc(2), }; - if (!directive.str) { - goto directive_error; + if (!fmt.str) { + goto fmt_error; } - if (dstrapp(&directive.str, c) != 0) { + if (dstrapp(&fmt.str, c) != 0) { bfs_perror(ctx, "dstrapp()"); - goto directive_error; + goto fmt_error; } const char *specifier = "s"; @@ -691,14 +706,14 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha fallthru; case ' ': case '-': - if (strchr(directive.str, c)) { + if (strchr(fmt.str, c)) { bfs_expr_error(ctx, expr); bfs_error(ctx, "Duplicate flag '%c'.\n", c); - goto directive_error; + goto fmt_error; } - if (dstrapp(&directive.str, c) != 0) { + if (dstrapp(&fmt.str, c) != 0) { bfs_perror(ctx, "dstrapp()"); - goto directive_error; + goto fmt_error; } continue; } @@ -708,9 +723,9 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha // Parse the field width while (c >= '0' && c <= '9') { - if (dstrapp(&directive.str, c) != 0) { + if (dstrapp(&fmt.str, c) != 0) { bfs_perror(ctx, "dstrapp()"); - goto directive_error; + goto fmt_error; } c = *++i; } @@ -718,9 +733,9 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha // Parse the precision if (c == '.') { do { - if (dstrapp(&directive.str, c) != 0) { + if (dstrapp(&fmt.str, c) != 0) { bfs_perror(ctx, "dstrapp()"); - goto directive_error; + goto fmt_error; } c = *++i; } while (c >= '0' && c <= '9'); @@ -728,163 +743,163 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha switch (c) { case 'a': - directive.fn = bfs_printf_ctime; - directive.stat_field = BFS_STAT_ATIME; + fmt.fn = bfs_printf_ctime; + fmt.stat_field = BFS_STAT_ATIME; break; case 'b': - directive.fn = bfs_printf_b; + fmt.fn = bfs_printf_b; break; case 'c': - directive.fn = bfs_printf_ctime; - directive.stat_field = BFS_STAT_CTIME; + fmt.fn = bfs_printf_ctime; + fmt.stat_field = BFS_STAT_CTIME; break; case 'd': - directive.fn = bfs_printf_d; + fmt.fn = bfs_printf_d; specifier = "jd"; break; case 'D': - directive.fn = bfs_printf_D; + fmt.fn = bfs_printf_D; break; case 'f': - directive.fn = bfs_printf_f; + fmt.fn = bfs_printf_f; break; case 'F': - directive.fn = bfs_printf_F; - directive.ptr = (void *)bfs_ctx_mtab(ctx); - if (!directive.ptr) { + fmt.fn = bfs_printf_F; + fmt.ptr = (void *)bfs_ctx_mtab(ctx); + if (!fmt.ptr) { int error = errno; bfs_expr_error(ctx, expr); bfs_error(ctx, "Couldn't parse the mount table: %s.\n", xstrerror(error)); - goto directive_error; + goto fmt_error; } break; case 'g': - directive.fn = bfs_printf_g; - directive.ptr = ctx->groups; + fmt.fn = bfs_printf_g; + fmt.ptr = ctx->groups; break; case 'G': - directive.fn = bfs_printf_G; + fmt.fn = bfs_printf_G; break; case 'h': - directive.fn = bfs_printf_h; + fmt.fn = bfs_printf_h; break; case 'H': - directive.fn = bfs_printf_H; + fmt.fn = bfs_printf_H; break; case 'i': - directive.fn = bfs_printf_i; + fmt.fn = bfs_printf_i; break; case 'k': - directive.fn = bfs_printf_k; + fmt.fn = bfs_printf_k; break; case 'l': - directive.fn = bfs_printf_l; + fmt.fn = bfs_printf_l; break; case 'm': - directive.fn = bfs_printf_m; + fmt.fn = bfs_printf_m; specifier = "o"; break; case 'M': - directive.fn = bfs_printf_M; + fmt.fn = bfs_printf_M; break; case 'n': - directive.fn = bfs_printf_n; + fmt.fn = bfs_printf_n; break; case 'p': - directive.fn = bfs_printf_p; + fmt.fn = bfs_printf_p; break; case 'P': - directive.fn = bfs_printf_P; + fmt.fn = bfs_printf_P; break; case 's': - directive.fn = bfs_printf_s; + fmt.fn = bfs_printf_s; break; case 'S': - directive.fn = bfs_printf_S; + fmt.fn = bfs_printf_S; specifier = "g"; break; case 't': - directive.fn = bfs_printf_ctime; - directive.stat_field = BFS_STAT_MTIME; + fmt.fn = bfs_printf_ctime; + fmt.stat_field = BFS_STAT_MTIME; break; case 'u': - directive.fn = bfs_printf_u; - directive.ptr = ctx->users; + fmt.fn = bfs_printf_u; + fmt.ptr = ctx->users; break; case 'U': - directive.fn = bfs_printf_U; + fmt.fn = bfs_printf_U; break; case 'w': - directive.fn = bfs_printf_ctime; - directive.stat_field = BFS_STAT_BTIME; + fmt.fn = bfs_printf_ctime; + fmt.stat_field = BFS_STAT_BTIME; break; case 'y': - directive.fn = bfs_printf_y; + fmt.fn = bfs_printf_y; break; case 'Y': - directive.fn = bfs_printf_Y; + fmt.fn = bfs_printf_Y; break; case 'A': - directive.stat_field = BFS_STAT_ATIME; - goto directive_strftime; + fmt.stat_field = BFS_STAT_ATIME; + goto fmt_strftime; case 'B': case 'W': - directive.stat_field = BFS_STAT_BTIME; - goto directive_strftime; + fmt.stat_field = BFS_STAT_BTIME; + goto fmt_strftime; case 'C': - directive.stat_field = BFS_STAT_CTIME; - goto directive_strftime; + fmt.stat_field = BFS_STAT_CTIME; + goto fmt_strftime; case 'T': - directive.stat_field = BFS_STAT_MTIME; - goto directive_strftime; + fmt.stat_field = BFS_STAT_MTIME; + goto fmt_strftime; - directive_strftime: - directive.fn = bfs_printf_strftime; + fmt_strftime: + fmt.fn = bfs_printf_strftime; c = *++i; if (!c) { bfs_expr_error(ctx, expr); - bfs_error(ctx, "Incomplete time specifier '%s%c'.\n", directive.str, i[-1]); - goto directive_error; + bfs_error(ctx, "Incomplete time specifier '%s%c'.\n", fmt.str, i[-1]); + goto fmt_error; } else if (strchr("%+@aAbBcCdDeFgGhHIjklmMnprRsStTuUVwWxXyYzZ", c)) { - directive.c = c; + fmt.c = c; } else { bfs_expr_error(ctx, expr); bfs_error(ctx, "Unrecognized time specifier '%%%c%c'.\n", i[-1], c); - goto directive_error; + goto fmt_error; } break; case '\0': bfs_expr_error(ctx, expr); - bfs_error(ctx, "Incomplete format specifier '%s'.\n", directive.str); - goto directive_error; + bfs_error(ctx, "Incomplete format specifier '%s'.\n", fmt.str); + goto fmt_error; default: bfs_expr_error(ctx, expr); bfs_error(ctx, "Unrecognized format specifier '%%%c'.\n", c); - goto directive_error; + goto fmt_error; } if (must_be_numeric && strcmp(specifier, "s") == 0) { bfs_expr_error(ctx, expr); - bfs_error(ctx, "Invalid flags '%s' for string format '%%%c'.\n", directive.str + 1, c); - goto directive_error; + bfs_error(ctx, "Invalid flags '%s' for string format '%%%c'.\n", fmt.str + 1, c); + goto fmt_error; } - if (dstrcat(&directive.str, specifier) != 0) { + if (dstrcat(&fmt.str, specifier) != 0) { bfs_perror(ctx, "dstrcat()"); - goto directive_error; + goto fmt_error; } - if (append_directive(ctx, &expr->printf, &literal, &directive) != 0) { - goto directive_error; + if (append_directive(ctx, expr->printf, &literal, &fmt) != 0) { + goto fmt_error; } continue; - directive_error: - dstrfree(directive.str); + fmt_error: + dstrfree(fmt.str); goto error; } @@ -896,7 +911,7 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha } done: - if (append_literal(ctx, &expr->printf, &literal) != 0) { + if (append_literal(ctx, expr->printf, &literal) != 0) { goto error; } dstrfree(literal); @@ -912,9 +927,9 @@ error: int bfs_printf(CFILE *cfile, const struct bfs_printf *format, const struct BFTW *ftwbuf) { int ret = 0, error = 0; - for (size_t i = 0; i < darray_length(format); ++i) { - const struct bfs_printf *directive = &format[i]; - if (directive->fn(cfile, directive, ftwbuf) < 0) { + for (size_t i = 0; i < format->nfmts; ++i) { + const struct bfs_fmt *fmt = &format->fmts[i]; + if (fmt->fn(cfile, fmt, ftwbuf) < 0) { ret = -1; error = errno; } @@ -925,8 +940,13 @@ int bfs_printf(CFILE *cfile, const struct bfs_printf *format, const struct BFTW } void bfs_printf_free(struct bfs_printf *format) { - for (size_t i = 0; i < darray_length(format); ++i) { - dstrfree(format[i].str); + if (!format) { + return; + } + + for (size_t i = 0; i < format->nfmts; ++i) { + dstrfree(format->fmts[i].str); } - darray_free(format); + free(format->fmts); + free(format); } -- cgit v1.2.3 From 394cf624ee9ab64090135d99ec4dcec0e0a69882 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 13:39:35 -0500 Subject: darray: Remove The new RESERVE() macro from alloc.h is nicer, and handles alignment properly. --- GNUmakefile | 1 - src/darray.c | 90 ------------------------------------------------------- src/darray.h | 97 ------------------------------------------------------------ src/main.c | 1 - 4 files changed, 189 deletions(-) delete mode 100644 src/darray.c delete mode 100644 src/darray.h diff --git a/GNUmakefile b/GNUmakefile index 2494b16..bcfdf76 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -236,7 +236,6 @@ LIBBFS := \ $(OBJ)/src/bftw.o \ $(OBJ)/src/color.o \ $(OBJ)/src/ctx.o \ - $(OBJ)/src/darray.o \ $(OBJ)/src/diag.o \ $(OBJ)/src/dir.o \ $(OBJ)/src/dstring.o \ diff --git a/src/darray.c b/src/darray.c deleted file mode 100644 index 3e66a55..0000000 --- a/src/darray.c +++ /dev/null @@ -1,90 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include "darray.h" -#include -#include - -/** - * The darray header. - */ -struct darray { - /** The current capacity of the array, as a count of elements. */ - size_t capacity; - /** The current length of the array. */ - size_t length; - - // The array elements are stored after this header in memory. Not using - // a flexible array member to avoid worrying about strict aliasing. We - // assume that 2*sizeof(size_t) keeps any memory allocation suitably - // aligned for the element type. -}; - -/** Get the header for a darray. */ -static struct darray *darray_header(const void *da) { - return (struct darray *)da - 1; -} - -/** Get the array from a darray header. */ -static char *darray_data(struct darray *header) { - return (char *)(header + 1); -} - -size_t darray_length(const void *da) { - if (da) { - return darray_header(da)->length; - } else { - return 0; - } -} - -void *darray_push(void *da, const void *item, size_t size) { - struct darray *header; - if (da) { - header = darray_header(da); - } else { - header = malloc(sizeof(*header) + size); - if (!header) { - return NULL; - } - header->capacity = 1; - header->length = 0; - } - - size_t capacity = header->capacity; - size_t i = header->length++; - if (i >= capacity) { - capacity *= 2; - header = realloc(header, sizeof(*header) + capacity * size); - if (!header) { - // This failure will be detected by darray_check() - return da; - } - header->capacity = capacity; - } - - char *data = darray_data(header); - memcpy(data + i * size, item, size); - return data; -} - -int darray_check(void *da) { - if (!da) { - return -1; - } - - struct darray *header = darray_header(da); - if (header->length <= header->capacity) { - return 0; - } else { - // realloc() failed in darray_push(), so reset the length and report the failure - header->length = header->capacity; - return -1; - } -} - -void darray_free(void *da) { - if (da) { - free(darray_header(da)); - } -} diff --git a/src/darray.h b/src/darray.h deleted file mode 100644 index cc6cc42..0000000 --- a/src/darray.h +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -/** - * A dynamic array library. - * - * darrays are represented by a simple pointer to the array element type, like - * any other array. Behind the scenes, the capacity and current length of the - * array are stored along with it. NULL is a valid way to initialize an empty - * darray: - * - * int *darray = NULL; - * - * To append an element to a darray, use the DARRAY_PUSH macro: - * - * int e = 42; - * if (DARRAY_PUSH(&darray, &e) != 0) { - * // Report the error... - * } - * - * The length can be retrieved by darray_length(). Iterating over the array - * works like normal arrays: - * - * for (size_t i = 0; i < darray_length(darray); ++i) { - * printf("%d\n", darray[i]); - * } - * - * To free a darray, use darray_free(): - * - * darray_free(darray); - */ - -#ifndef BFS_DARRAY_H -#define BFS_DARRAY_H - -#include - -/** - * Get the length of a darray. - * - * @param da - * The array in question. - * @return - * The length of the array. - */ -size_t darray_length(const void *da); - -/** - * @internal Use DARRAY_PUSH(). - * - * Push an element into a darray. - * - * @param da - * The array to append to. - * @param item - * The item to append. - * @param size - * The size of the item. - * @return - * The (new) location of the array. - */ -void *darray_push(void *da, const void *item, size_t size); - -/** - * @internal Use DARRAY_PUSH(). - * - * Check if the last darray_push() call failed. - * - * @param da - * The darray to check. - * @return - * 0 on success, -1 on failure. - */ -int darray_check(void *da); - -/** - * Free a darray. - * - * @param da - * The darray to free. - */ -void darray_free(void *da); - -/** - * Push an item into a darray. - * - * @param da - * The array to append to. - * @param item - * A pointer to the item to append. - * @return - * 0 on success, -1 on failure. - */ -#define DARRAY_PUSH(da, item) \ - (darray_check(*(da) = darray_push(*(da), (item), sizeof(**(da) = *(item))))) - -#endif // BFS_DARRAY_H diff --git a/src/main.c b/src/main.c index b26be85..a05f9c9 100644 --- a/src/main.c +++ b/src/main.c @@ -27,7 +27,6 @@ * - bfstd.[ch] (standard library wrappers/polyfills) * - color.[ch] (for pretty terminal colors) * - config.h (configuration and feature/platform detection) - * - darray.[ch] (a dynamic array library) * - diag.[ch] (formats diagnostic messages) * - dir.[ch] (a directory API facade) * - dstring.[ch] (a dynamic string library) -- cgit v1.2.3 From baa6fc8cd33da879e5f7ac22557fd2ef3e4d8072 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 23 Nov 2023 15:25:34 -0500 Subject: bfstd: Work around more instances of llvm/llvm-project#65532 --- src/bfstd.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 16cd82e..b5006d1 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -284,8 +284,12 @@ const char *xstrerror(int errnum) { const char *ret = NULL; static thread_local char buf[256]; -#if __APPLE__ || __COSMOPOLITAN__ - // No strerror_l() on macOS + // - __APPLE__ + // - __COSMOPOLITAN__ + // - No strerror_l() + // - __FreeBSD__ && SANITIZE_MEMORY + // - duplocale() triggers https://github.com/llvm/llvm-project/issues/65532 +#if __APPLE__ || __COSMOPOLITAN__ || (__FreeBSD__ && SANITIZE_MEMORY) if (strerror_r(errnum, buf, sizeof(buf)) == 0) { ret = buf; } @@ -643,6 +647,16 @@ wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb) { } } +/** + * Work around https://github.com/llvm/llvm-project/issues/65532 by forcing a + * function, not a macro, to be called. + */ +#if __FreeBSD__ && SANITIZE_MEMORY +# define BFS_INTERCEPT(fn) (fn) +#else +# define BFS_INTERCEPT(fn) fn +#endif + size_t xstrwidth(const char *str) { size_t len = strlen(str); size_t ret = 0; @@ -654,7 +668,7 @@ size_t xstrwidth(const char *str) { // Assume a single-width '?' ++ret; } else { - ret += wcwidth(wc); + ret += BFS_INTERCEPT(wcwidth)(wc); } } @@ -674,20 +688,11 @@ static unsigned char ctype_cache[UCHAR_MAX + 1]; /** Initialize the ctype cache. */ static void char_cache_init(void) { -#if __FreeBSD__ && SANITIZE_MEMORY -// Work around https://github.com/llvm/llvm-project/issues/65532 -# define bfs_isprint (isprint) -# define bfs_isspace (isspace) -#else -# define bfs_isprint isprint -# define bfs_isspace isspace -#endif - for (size_t c = 0; c <= UCHAR_MAX; ++c) { - if (bfs_isprint(c)) { + if (BFS_INTERCEPT(isprint)(c)) { ctype_cache[c] |= IS_PRINT; } - if (bfs_isspace(c)) { + if (BFS_INTERCEPT(isspace)(c)) { ctype_cache[c] |= IS_SPACE; } } -- cgit v1.2.3 From 6a093a92f7e744838bf80be9555fc2163c317f75 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 25 Nov 2023 17:25:43 -0500 Subject: color: Make sure errno is non-zero if parse_colors() fails --- src/color.c | 86 ++++++++++++++++++++++++++++++------------------------------- 1 file changed, 43 insertions(+), 43 deletions(-) diff --git a/src/color.c b/src/color.c index 7644ca3..05ba1a3 100644 --- a/src/color.c +++ b/src/color.c @@ -142,12 +142,12 @@ static int init_esc(struct colors *colors, const char *name, const char *value, *field = esc; struct trie_leaf *leaf = trie_insert_str(&colors->names, name); - if (leaf) { - leaf->value = field; - return 0; - } else { + if (!leaf) { return -1; } + + leaf->value = field; + return 0; } /** Check if an escape sequence is equal to a string. */ @@ -623,58 +623,58 @@ struct colors *parse_colors(void) { trie_init(&colors->ext_trie); trie_init(&colors->iext_trie); - int ret = 0; + bool fail = false; // From man console_codes - ret |= init_esc(colors, "rs", "0", &colors->reset); - ret |= init_esc(colors, "lc", "\033[", &colors->leftcode); - ret |= init_esc(colors, "rc", "m", &colors->rightcode); - ret |= init_esc(colors, "ec", NULL, &colors->endcode); - ret |= init_esc(colors, "cl", "\033[K", &colors->clear_to_eol); - - ret |= init_esc(colors, "bld", "01;39", &colors->bold); - ret |= init_esc(colors, "gry", "01;30", &colors->gray); - ret |= init_esc(colors, "red", "01;31", &colors->red); - ret |= init_esc(colors, "grn", "01;32", &colors->green); - ret |= init_esc(colors, "ylw", "01;33", &colors->yellow); - ret |= init_esc(colors, "blu", "01;34", &colors->blue); - ret |= init_esc(colors, "mag", "01;35", &colors->magenta); - ret |= init_esc(colors, "cyn", "01;36", &colors->cyan); - ret |= init_esc(colors, "wht", "01;37", &colors->white); - - ret |= init_esc(colors, "wrn", "01;33", &colors->warning); - ret |= init_esc(colors, "err", "01;31", &colors->error); + fail = fail || init_esc(colors, "rs", "0", &colors->reset); + fail = fail || init_esc(colors, "lc", "\033[", &colors->leftcode); + fail = fail || init_esc(colors, "rc", "m", &colors->rightcode); + fail = fail || init_esc(colors, "ec", NULL, &colors->endcode); + fail = fail || init_esc(colors, "cl", "\033[K", &colors->clear_to_eol); + + fail = fail || init_esc(colors, "bld", "01;39", &colors->bold); + fail = fail || init_esc(colors, "gry", "01;30", &colors->gray); + fail = fail || init_esc(colors, "red", "01;31", &colors->red); + fail = fail || init_esc(colors, "grn", "01;32", &colors->green); + fail = fail || init_esc(colors, "ylw", "01;33", &colors->yellow); + fail = fail || init_esc(colors, "blu", "01;34", &colors->blue); + fail = fail || init_esc(colors, "mag", "01;35", &colors->magenta); + fail = fail || init_esc(colors, "cyn", "01;36", &colors->cyan); + fail = fail || init_esc(colors, "wht", "01;37", &colors->white); + + fail = fail || init_esc(colors, "wrn", "01;33", &colors->warning); + fail = fail || init_esc(colors, "err", "01;31", &colors->error); // Defaults from man dir_colors // "" means fall back to ->normal - ret |= init_esc(colors, "no", NULL, &colors->normal); + fail = fail || init_esc(colors, "no", NULL, &colors->normal); - ret |= init_esc(colors, "fi", "", &colors->file); - ret |= init_esc(colors, "mh", NULL, &colors->multi_hard); - ret |= init_esc(colors, "ex", "01;32", &colors->executable); - ret |= init_esc(colors, "ca", NULL, &colors->capable); - ret |= init_esc(colors, "sg", "30;43", &colors->setgid); - ret |= init_esc(colors, "su", "37;41", &colors->setuid); + fail = fail || init_esc(colors, "fi", "", &colors->file); + fail = fail || init_esc(colors, "mh", NULL, &colors->multi_hard); + fail = fail || init_esc(colors, "ex", "01;32", &colors->executable); + fail = fail || init_esc(colors, "ca", NULL, &colors->capable); + fail = fail || init_esc(colors, "sg", "30;43", &colors->setgid); + fail = fail || init_esc(colors, "su", "37;41", &colors->setuid); - ret |= init_esc(colors, "di", "01;34", &colors->directory); - ret |= init_esc(colors, "st", "37;44", &colors->sticky); - ret |= init_esc(colors, "ow", "34;42", &colors->other_writable); - ret |= init_esc(colors, "tw", "30;42", &colors->sticky_other_writable); + fail = fail || init_esc(colors, "di", "01;34", &colors->directory); + fail = fail || init_esc(colors, "st", "37;44", &colors->sticky); + fail = fail || init_esc(colors, "ow", "34;42", &colors->other_writable); + fail = fail || init_esc(colors, "tw", "30;42", &colors->sticky_other_writable); - ret |= init_esc(colors, "ln", "01;36", &colors->link); - ret |= init_esc(colors, "or", NULL, &colors->orphan); - ret |= init_esc(colors, "mi", NULL, &colors->missing); + fail = fail || init_esc(colors, "ln", "01;36", &colors->link); + fail = fail || init_esc(colors, "or", NULL, &colors->orphan); + fail = fail || init_esc(colors, "mi", NULL, &colors->missing); colors->link_as_target = false; - ret |= init_esc(colors, "bd", "01;33", &colors->blockdev); - ret |= init_esc(colors, "cd", "01;33", &colors->chardev); - ret |= init_esc(colors, "do", "01;35", &colors->door); - ret |= init_esc(colors, "pi", "33", &colors->pipe); - ret |= init_esc(colors, "so", "01;35", &colors->socket); + fail = fail || init_esc(colors, "bd", "01;33", &colors->blockdev); + fail = fail || init_esc(colors, "cd", "01;33", &colors->chardev); + fail = fail || init_esc(colors, "do", "01;35", &colors->door); + fail = fail || init_esc(colors, "pi", "33", &colors->pipe); + fail = fail || init_esc(colors, "so", "01;35", &colors->socket); - if (ret != 0) { + if (fail) { goto fail; } -- cgit v1.2.3 From d693e05c98349a05311bc26967a3088de24efe3b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 28 Nov 2023 10:35:45 -0500 Subject: Revert "README: Realign badges" Looks like GitHub fixed their rendering. This reverts commit 3f15559a82484d46942a68e18ba18c7e1c31aab3. --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 44fbb79..a7ca335 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@
-# `bfs` - +

+bfs +
Version License CI Status Code coverage -
+

**[Features]   •   [Installation]   •   [Usage]   •   [Building]   •   [Hacking]   •   [Changelog]** -- cgit v1.2.3 From deb293c7fb3ac0787ba9de8d4423b0037998fc13 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 9 Dec 2023 17:01:42 -0500 Subject: bfstd: Wrap is[w]{alpha,digit,...}() --- src/bfstd.c | 43 ++++++++++++------------------------------- src/bfstd.h | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 31 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index b5006d1..9ffa8e6 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -647,16 +647,6 @@ wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb) { } } -/** - * Work around https://github.com/llvm/llvm-project/issues/65532 by forcing a - * function, not a macro, to be called. - */ -#if __FreeBSD__ && SANITIZE_MEMORY -# define BFS_INTERCEPT(fn) (fn) -#else -# define BFS_INTERCEPT(fn) fn -#endif - size_t xstrwidth(const char *str) { size_t len = strlen(str); size_t ret = 0; @@ -689,17 +679,17 @@ static unsigned char ctype_cache[UCHAR_MAX + 1]; /** Initialize the ctype cache. */ static void char_cache_init(void) { for (size_t c = 0; c <= UCHAR_MAX; ++c) { - if (BFS_INTERCEPT(isprint)(c)) { + if (xisprint(c)) { ctype_cache[c] |= IS_PRINT; } - if (BFS_INTERCEPT(isspace)(c)) { + if (xisspace(c)) { ctype_cache[c] |= IS_SPACE; } } } /** Check if a character is printable. */ -static bool xisprint(unsigned char c, enum wesc_flags flags) { +static bool wesc_isprint(unsigned char c, enum wesc_flags flags) { if (ctype_cache[c] & IS_PRINT) { return true; } @@ -715,21 +705,12 @@ static bool xisprint(unsigned char c, enum wesc_flags flags) { } /** Check if a wide character is printable. */ -static bool xiswprint(wchar_t c, enum wesc_flags flags) { -#if __FreeBSD__ && SANITIZE_MEMORY -// Work around https://github.com/llvm/llvm-project/issues/65532 -# define bfs_iswprint (iswprint) -# define bfs_iswspace (iswspace) -#else -# define bfs_iswprint iswprint -# define bfs_iswspace iswspace -#endif - - if (bfs_iswprint(c)) { +static bool wesc_iswprint(wchar_t c, enum wesc_flags flags) { + if (xiswprint(c)) { return true; } - if (!(flags & WESC_SHELL) && bfs_iswspace(c)) { + if (!(flags & WESC_SHELL) && xiswspace(c)) { return true; } @@ -753,18 +734,18 @@ static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) } for (size_t j = 0; j < sizeof(word); ++i, ++j) { - if (!xisprint(str[i], flags)) { + if (!wesc_isprint(str[i], flags)) { return i; } } } for (; i < len; ++i) { - unsigned char c = str[i]; - if (!isascii(c)) { + char c = str[i]; + if (!xisascii(c)) { goto multibyte; } - if (!xisprint(c, flags)) { + if (!wesc_isprint(c, flags)) { return i; } } @@ -777,7 +758,7 @@ multibyte:; if (wc == WEOF) { break; } - if (!xiswprint(wc, flags)) { + if (!wesc_iswprint(wc, flags)) { break; } } @@ -825,7 +806,7 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, en wint_t wc = xmbrtowc(str, &i, len, &mb); if (wc != WEOF) { - safe = xiswprint(wc, flags); + safe = wesc_iswprint(wc, flags); } for (size_t j = start; safe && j < i; ++j) { diff --git a/src/bfstd.h b/src/bfstd.h index 6cb2d7b..58e504c 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -9,8 +9,39 @@ #define BFS_BFSTD_H #include "config.h" +#include "sanity.h" #include +#include + +/** + * Work around https://github.com/llvm/llvm-project/issues/65532 by forcing a + * function, not a macro, to be called. + */ +#if __FreeBSD__ && SANITIZE_MEMORY +# define BFS_INTERCEPT(fn) (fn) +#else +# define BFS_INTERCEPT(fn) fn +#endif + +/** + * Wrap isalpha()/isdigit()/etc. + */ +#define BFS_ISCTYPE(fn, c) BFS_INTERCEPT(fn)((unsigned char)(c)) + +#define xisalnum(c) BFS_ISCTYPE(isalnum, c) +#define xisalpha(c) BFS_ISCTYPE(isalpha, c) +#define xisascii(c) BFS_ISCTYPE(isascii, c) +#define xiscntrl(c) BFS_ISCTYPE(iscntrl, c) +#define xisdigit(c) BFS_ISCTYPE(isdigit, c) +#define xislower(c) BFS_ISCTYPE(islower, c) +#define xisgraph(c) BFS_ISCTYPE(isgraph, c) +#define xisprint(c) BFS_ISCTYPE(isprint, c) +#define xispunct(c) BFS_ISCTYPE(ispunct, c) +#define xisspace(c) BFS_ISCTYPE(isspace, c) +#define xisupper(c) BFS_ISCTYPE(isupper, c) +#define xisxdigit(c) BFS_ISCTYPE(isxdigit, c) + // #include /** @@ -359,6 +390,25 @@ wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb); */ size_t xstrwidth(const char *str); +#include + +/** + * Wrap iswalpha()/iswdigit()/etc. + */ +#define BFS_ISWCTYPE(fn, c) BFS_INTERCEPT(fn)(c) + +#define xiswalnum(c) BFS_ISWCTYPE(iswalnum, c) +#define xiswalpha(c) BFS_ISWCTYPE(iswalpha, c) +#define xiswcntrl(c) BFS_ISWCTYPE(iswcntrl, c) +#define xiswdigit(c) BFS_ISWCTYPE(iswdigit, c) +#define xiswlower(c) BFS_ISWCTYPE(iswlower, c) +#define xiswgraph(c) BFS_ISWCTYPE(iswgraph, c) +#define xiswprint(c) BFS_ISWCTYPE(iswprint, c) +#define xiswpunct(c) BFS_ISWCTYPE(iswpunct, c) +#define xiswspace(c) BFS_ISWCTYPE(iswspace, c) +#define xiswupper(c) BFS_ISWCTYPE(iswupper, c) +#define xiswxdigit(c) BFS_ISWCTYPE(iswxdigit, c) + // #include /** -- cgit v1.2.3 From e0d7dc5dfd7bdaa62b6bc18e9c1cce00bbe08577 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 9 Dec 2023 17:02:57 -0500 Subject: parse: Reject integers that start with whitespace --- src/parse.c | 6 +++++- tests/bfs/links_leading_space.sh | 1 + 2 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/bfs/links_leading_space.sh diff --git a/src/parse.c b/src/parse.c index 13e65bc..778fc68 100644 --- a/src/parse.c +++ b/src/parse.c @@ -565,13 +565,17 @@ enum int_flags { * Parse an integer. */ static const char *parse_int(const struct parser_state *state, char **arg, const char *str, void *result, enum int_flags flags) { - char *endptr; + // 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) { diff --git a/tests/bfs/links_leading_space.sh b/tests/bfs/links_leading_space.sh new file mode 100644 index 0000000..15957af --- /dev/null +++ b/tests/bfs/links_leading_space.sh @@ -0,0 +1 @@ +! invoke_bfs links -links ' 1' -- cgit v1.2.3 From 023482caa20c0d3b5ab1641f26d8384829b1e43f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 16 Dec 2023 11:25:33 -0500 Subject: main: Warn if setlocale() fails This should help users understand why issues like #128 happen. --- src/main.c | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main.c b/src/main.c index a05f9c9..1ddbc54 100644 --- a/src/main.c +++ b/src/main.c @@ -116,15 +116,26 @@ int main(int argc, char *argv[]) { } // Use the system locale instead of "C" - setlocale(LC_ALL, ""); + int locale_err = 0; + if (!setlocale(LC_ALL, "")) { + locale_err = errno; + } + // Parse the command line struct bfs_ctx *ctx = bfs_parse_cmdline(argc, argv); if (!ctx) { return EXIT_FAILURE; } + // Warn if setlocale() failed, unless there's no expression to evaluate + if (locale_err && ctx->warn && ctx->expr) { + bfs_warning(ctx, "Failed to set locale: %s\n\n", xstrerror(locale_err)); + } + + // Walk the file system tree, evaluating the expression on each file int ret = bfs_eval(ctx); + // Free the parsed command line, and detect any last-minute errors if (bfs_ctx_free(ctx) != 0 && ret == EXIT_SUCCESS) { ret = EXIT_FAILURE; } -- cgit v1.2.3 From aa8344e04bfe950cc9f5e45352cd8202295e0cdf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 16 Dec 2023 11:54:18 -0500 Subject: bfstd: New xwcwidth() wrapper --- src/bfstd.c | 2 +- src/bfstd.h | 5 +++++ src/eval.c | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 9ffa8e6..a3e22f5 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -658,7 +658,7 @@ size_t xstrwidth(const char *str) { // Assume a single-width '?' ++ret; } else { - ret += BFS_INTERCEPT(wcwidth)(wc); + ret += xwcwidth(wc); } } diff --git a/src/bfstd.h b/src/bfstd.h index 58e504c..0fcb892 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -390,6 +390,11 @@ wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb); */ size_t xstrwidth(const char *str); +/** + * wcwidth() wrapper that works around LLVM bug #65532. + */ +#define xwcwidth BFS_INTERCEPT(wcwidth) + #include /** diff --git a/src/eval.c b/src/eval.c index 55b14f0..a990fd4 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1154,7 +1154,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time // Invalid byte sequence, assume a single-width '?' cwidth = 1; } else { - cwidth = wcwidth(wc); + cwidth = xwcwidth(wc); if (cwidth < 0) { cwidth = 0; } -- cgit v1.2.3 From fd91097055f095fdd827ef8751d9534f59a89404 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 16 Dec 2023 12:47:45 -0500 Subject: docs: Rename HACKING to CONTRIBUTING So that GitHub recognizes it. --- README.md | 4 ++-- docs/CONTRIBUTING.md | 61 ++++++++++++++++++++++++++++++++++++++++++++++++++++ docs/HACKING.md | 61 ---------------------------------------------------- docs/USAGE.md | 8 +++---- 4 files changed, 67 insertions(+), 67 deletions(-) create mode 100644 docs/CONTRIBUTING.md delete mode 100644 docs/HACKING.md diff --git a/README.md b/README.md index a7ca335..79ffe65 100644 --- a/README.md +++ b/README.md @@ -9,13 +9,13 @@ Code coverage -**[Features]   •   [Installation]   •   [Usage]   •   [Building]   •   [Hacking]   •   [Changelog]** +**[Features]   •   [Installation]   •   [Usage]   •   [Building]   •   [Contributing]   •   [Changelog]** [Features]: #features [Installation]: #installation [Usage]: /docs/USAGE.md [Building]: /docs/BUILDING.md -[Hacking]: /docs/HACKING.md +[Contributing]: /docs/CONTRIBUTING.md [Changelog]: /docs/CHANGELOG.md diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100644 index 0000000..099157d --- /dev/null +++ b/docs/CONTRIBUTING.md @@ -0,0 +1,61 @@ +Contributing to `bfs` +===================== + +License +------- + +`bfs` is licensed under the [Zero-Clause BSD License](https://opensource.org/licenses/0BSD), a maximally permissive license. +Contributions must use the same license. + +Individual files contain the following tag instead of the full license text: + + SPDX-License-Identifier: 0BSD + +This enables machine processing of license information based on the SPDX License Identifiers that are available here: https://spdx.org/licenses/ + + +Implementation +-------------- + +`bfs` is written in [C](https://en.wikipedia.org/wiki/C_(programming_language)), specifically [C17](https://en.wikipedia.org/wiki/C17_(C_standard_revision)). +You can get a feel for the coding style by skimming the source code. +[`main.c`](/src/main.c) contains an overview of the rest of source files. +A quick summary: + +- Tabs for indentation, spaces for alignment. +- Most types and functions should be namespaced with `bfs_`. + Exceptions are made for things that could be generally useful outside of `bfs`. +- Error handling follows the C standard library conventions: return a nonzero `int` or a `NULL` pointer, with the error code in `errno`. + All failure cases should be handled, including `malloc()` failures. +- `goto` is not considered harmful for cleaning up in error paths. + + +Tests +----- + +`bfs` includes an extensive test suite. +See the [build documentation](BUILDING.md#testing) for details on running the tests. + +Test cases are grouped by the standard or `find` implementation that supports the tested feature(s): + +| Group | Description | +|---------------------------------|---------------------------------------| +| [`tests/posix`](/tests/posix) | POSIX compatibility tests | +| [`tests/bsd`](/tests/bsd) | BSD `find` features | +| [`tests/gnu`](/tests/gnu) | GNU `find` features | +| [`tests/common`](/tests/common) | Features common to BSD and GNU `find` | +| [`tests/bfs`](/tests/bfs) | `bfs`-specific tests | + +Both new features and bug fixes should have associated tests. +To add a test, create a new `*.sh` file in the appropriate group. +Snapshot tests use the `bfs_diff` function to automatically compare the generated and expected outputs. +For example, + +```bash +# posix/something.sh +bfs_diff basic -name something +``` + +`basic` is one of the directory trees generated for test cases; others include `links`, `loops`, `deep`, and `rainbow`. + +Run `./tests/tests.sh posix/something --update` to generate the reference snapshot (and don't forget to `git add` it). diff --git a/docs/HACKING.md b/docs/HACKING.md deleted file mode 100644 index 0763fda..0000000 --- a/docs/HACKING.md +++ /dev/null @@ -1,61 +0,0 @@ -Hacking on `bfs` -================ - -License -------- - -`bfs` is licensed under the [Zero-Clause BSD License](https://opensource.org/licenses/0BSD), a maximally permissive license. -Contributions must use the same license. - -Individual files contain the following tag instead of the full license text: - - SPDX-License-Identifier: 0BSD - -This enables machine processing of license information based on the SPDX License Identifiers that are available here: https://spdx.org/licenses/ - - -Implementation --------------- - -`bfs` is written in [C](https://en.wikipedia.org/wiki/C_(programming_language)), specifically [C17](https://en.wikipedia.org/wiki/C17_(C_standard_revision)). -You can get a feel for the coding style by skimming the source code. -[`main.c`](/src/main.c) contains an overview of the rest of source files. -A quick summary: - -- Tabs for indentation, spaces for alignment. -- Most types and functions should be namespaced with `bfs_`. - Exceptions are made for things that could be generally useful outside of `bfs`. -- Error handling follows the C standard library conventions: return a nonzero `int` or a `NULL` pointer, with the error code in `errno`. - All failure cases should be handled, including `malloc()` failures. -- `goto` is not considered harmful for cleaning up in error paths. - - -Tests ------ - -`bfs` includes an extensive test suite. -See the [build documentation](BUILDING.md#testing) for details on running the tests. - -Test cases are grouped by the standard or `find` implementation that supports the tested feature(s): - -| Group | Description | -|---------------------------------|---------------------------------------| -| [`tests/posix`](/tests/posix) | POSIX compatibility tests | -| [`tests/bsd`](/tests/bsd) | BSD `find` features | -| [`tests/gnu`](/tests/gnu) | GNU `find` features | -| [`tests/common`](/tests/common) | Features common to BSD and GNU `find` | -| [`tests/bfs`](/tests/bfs) | `bfs`-specific tests | - -Both new features and bug fixes should have associated tests. -To add a test, create a new `*.sh` file in the appropriate group. -Snapshot tests use the `bfs_diff` function to automatically compare the generated and expected outputs. -For example, - -```bash -# posix/something.sh -bfs_diff basic -name something -``` - -`basic` is one of the directory trees generated for test cases; others include `links`, `loops`, `deep`, and `rainbow`. - -Run `./tests/tests.sh posix/something --update` to generate the reference snapshot (and don't forget to `git add` it). diff --git a/docs/USAGE.md b/docs/USAGE.md index 86eef87..3efdee0 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -18,7 +18,7 @@ $ bfs ./completions/bfs.zsh ./docs/BUILDING.md ./docs/CHANGELOG.md -./docs/HACKING.md +./docs/CONTRIBUTING.md ./docs/USAGE.md ./docs/bfs.1 ... @@ -53,7 +53,7 @@ $ bfs -name '*.md' ./README.md ./docs/BUILDING.md ./docs/CHANGELOG.md -./docs/HACKING.md +./docs/CONTRIBUTING.md ./docs/USAGE.md ``` @@ -64,7 +64,7 @@ When you put multiple expressions next to each other, both of them must match: ```console $ bfs -name '*.md' -name '*ING*' ./docs/BUILDING.md -./docs/HACKING.md +./docs/CONTRIBUTING.md ``` This works because the expressions are implicitly combined with *logical and*. @@ -84,7 +84,7 @@ $ bfs -name '*.md' -or -name 'bfs.*' ./completions/bfs.zsh ./docs/BUILDING.md ./docs/CHANGELOG.md -./docs/HACKING.md +./docs/CONTRIBUTING.md ./docs/USAGE.md ./docs/bfs.1 ``` -- cgit v1.2.3 From 5b38f658ee42bef05cecb6cadec65b25d9e94993 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 16 Nov 2023 12:25:09 -0500 Subject: config: New variadic attr(...) macro --- src/alloc.c | 2 +- src/alloc.h | 10 ++++---- src/bar.c | 2 +- src/color.c | 4 ++-- src/color.h | 4 ++-- src/config.h | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/diag.h | 37 ++++++++++++------------------ src/dstring.h | 18 +++++++-------- src/eval.c | 2 +- src/exec.c | 2 +- src/fsade.c | 6 ++--- src/ioq.c | 4 ++-- src/mtab.c | 2 +- src/opt.c | 4 ++-- src/parse.c | 14 +++++------ src/trie.c | 2 +- src/xspawn.c | 2 +- 17 files changed, 127 insertions(+), 62 deletions(-) diff --git a/src/alloc.c b/src/alloc.c index 467f0f0..b65d0c5 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -176,7 +176,7 @@ void arena_init(struct arena *arena, size_t align, size_t size) { } /** Allocate a new slab. */ -attr_cold +attr(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 7132470..f21ddb9 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -114,8 +114,7 @@ static inline size_t flex_size(size_t align, size_t min, size_t offset, size_t s * @return * The allocated memory, or NULL on failure. */ -attr_malloc(free, 1) -attr_aligned_alloc(1, 2) +attr(malloc(free, 1), aligned_alloc(1, 2)) void *alloc(size_t align, size_t size); /** @@ -128,8 +127,7 @@ void *alloc(size_t align, size_t size); * @return * The allocated memory, or NULL on failure. */ -attr_malloc(free, 1) -attr_aligned_alloc(1, 2) +attr(malloc(free, 1), aligned_alloc(1, 2)) void *zalloc(size_t align, size_t size); /** Allocate memory for the given type. */ @@ -252,7 +250,7 @@ void arena_free(struct arena *arena, void *ptr); /** * Allocate an object out of the arena. */ -attr_malloc(arena_free, 2) +attr(malloc(arena_free, 2)) void *arena_alloc(struct arena *arena); /** @@ -334,7 +332,7 @@ void varena_free(struct varena *varena, void *ptr, size_t count); * @return * The allocated struct, or NULL on failure. */ -attr_malloc(varena_free, 2) +attr(malloc(varena_free, 2)) void *varena_alloc(struct varena *varena, size_t count); /** diff --git a/src/bar.c b/src/bar.c index 2e50dbe..babadc3 100644 --- a/src/bar.c +++ b/src/bar.c @@ -127,7 +127,7 @@ static void reset_before_death_by(int sig) { } /** printf() to the status bar with a single write(). */ -attr_format(2, 3) +attr(format(2, 3)) static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { va_list args; va_start(args, format); diff --git a/src/color.c b/src/color.c index 05ba1a3..4c2c8ca 100644 --- a/src/color.c +++ b/src/color.c @@ -1105,7 +1105,7 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { } /** Format some colored output to the buffer. */ -attr_format(2, 3) +attr(format(2, 3)) static int cbuff(CFILE *cfile, const char *format, ...); /** Dump a parsed expression tree, for debugging. */ @@ -1177,7 +1177,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { return 0; } -attr_format(2, 0) +attr(format(2, 0)) static int cvbuff(CFILE *cfile, const char *format, va_list args) { const struct colors *colors = cfile->colors; int error = errno; diff --git a/src/color.h b/src/color.h index 8a81573..81f0e2a 100644 --- a/src/color.h +++ b/src/color.h @@ -100,13 +100,13 @@ int cfclose(CFILE *cfile); * @return * 0 on success, -1 on failure. */ -attr_format(2, 3) +attr(format(2, 3)) int cfprintf(CFILE *cfile, const char *format, ...); /** * cfprintf() variant that takes a va_list. */ -attr_format(2, 0) +attr(format(2, 0)) int cvfprintf(CFILE *cfile, const char *format, va_list args); #endif // BFS_COLOR_H diff --git a/src/config.h b/src/config.h index 9f95674..aa03552 100644 --- a/src/config.h +++ b/src/config.h @@ -301,4 +301,78 @@ typedef long double max_align_t; # define attr_target_clones(...) #endif +/** + * Shorthand for multiple attributes at once. attr(a, b(c), d) is equivalent to + * + * attr_a + * attr_b(c) + * attr_d + */ +#define attr(...) \ + attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none) + +/** + * attr() helper. For exposition, pretend we support only 2 args, instead of 9. + * There are a few cases: + * + * attr() + * => attr__(attr_, none, none) + * => attr_ => + * attr_none => + * attr_too_many_none() => + * + * attr(a) + * => attr__(attr_a, none, none) + * => attr_a => __attribute__((a)) + * attr_none => + * attr_too_many_none() => + * + * attr(a, b(c)) + * => attr__(attr_a, b(c), none, none) + * => attr_a => __attribute__((a)) + * attr_b(c) => __attribute__((b(c))) + * attr_too_many_none(none) => + * + * attr(a, b(c), d) + * => attr__(attr_a, b(c), d, none, none) + * => attr_a => __attribute__((a)) + * attr_b(c) => __attribute__((b(c))) + * attr_too_many_d(none, none) => error + * + * Some attribute names are the same as standard library functions, e.g. printf. + * Standard libraries are permitted to define these functions as macros, like + * + * #define printf(...) __builtin_printf(__VA_ARGS__) + * + * The token paste in + * + * #define attr(...) attr__(attr_##__VA_ARGS__, none, none) + * + * is necessary to prevent macro expansion before evaluating attr__(). + * Otherwise, we could get + * + * attr(printf(1, 2)) + * => attr__(__builtin_printf(1, 2), none, none) + * => attr____builtin_printf(1, 2) + * => error + */ +#define attr__(a1, a2, a3, a4, a5, a6, a7, a8, a9, none, ...) \ + a1 \ + attr_##a2 \ + attr_##a3 \ + attr_##a4 \ + attr_##a5 \ + attr_##a6 \ + attr_##a7 \ + attr_##a8 \ + attr_##a9 \ + attr_too_many_##none(__VA_ARGS__) + +// Ignore `attr_none` from expanding 1-9 argument attr(a1, a2, ...) +#define attr_none +// Ignore `attr_` from expanding 0-argument attr() +#define attr_ +// Only trigger an error on more than 9 arguments +#define attr_too_many_none(...) + #endif // BFS_CONFIG_H diff --git a/src/diag.h b/src/diag.h index 981419e..aa6a44f 100644 --- a/src/diag.h +++ b/src/diag.h @@ -44,8 +44,7 @@ struct bfs_loc { /** * Print a message to standard error and abort. */ -attr_cold -attr_format(2, 3) +attr(cold, format(2, 3)) noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); /** @@ -117,14 +116,13 @@ const char *debug_flag_name(enum debug_flags flag); /** * Like perror(), but decorated like bfs_error(). */ -attr_cold +attr(cold) void bfs_perror(const struct bfs_ctx *ctx, const char *str); /** * Shorthand for printing error messages. */ -attr_cold -attr_format(2, 3) +attr(cold, format(2, 3)) void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -132,8 +130,7 @@ void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a warning was printed. */ -attr_cold -attr_format(2, 3) +attr(cold, format(2, 3)) bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -141,71 +138,67 @@ bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a debug message was printed. */ -attr_cold -attr_format(3, 4) +attr(cold, format(3, 4)) bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, ...); /** * bfs_error() variant that takes a va_list. */ -attr_cold -attr_format(2, 0) +attr(cold, format(2, 0)) void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_warning() variant that takes a va_list. */ -attr_cold -attr_format(2, 0) +attr(cold, format(2, 0)) bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_debug() variant that takes a va_list. */ -attr_cold -attr_format(3, 0) +attr(cold, format(3, 0)) bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args); /** * Print the error message prefix. */ -attr_cold +attr(cold) void bfs_error_prefix(const struct bfs_ctx *ctx); /** * Print the warning message prefix. */ -attr_cold +attr(cold) bool bfs_warning_prefix(const struct bfs_ctx *ctx); /** * Print the debug message prefix. */ -attr_cold +attr(cold) bool bfs_debug_prefix(const struct bfs_ctx *ctx, enum debug_flags flag); /** * Highlight parts of the command line in an error message. */ -attr_cold +attr(cold) void bfs_argv_error(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in an error message. */ -attr_cold +attr(cold) void bfs_expr_error(const struct bfs_ctx *ctx, const struct bfs_expr *expr); /** * Highlight parts of the command line in a warning message. */ -attr_cold +attr(cold) bool bfs_argv_warning(const struct bfs_ctx *ctx, const bool args[]); /** * Highlight parts of an expression in a warning message. */ -attr_cold +attr(cold) bool bfs_expr_warning(const struct bfs_ctx *ctx, const struct bfs_expr *expr); #endif // BFS_DIAG_H diff --git a/src/dstring.h b/src/dstring.h index 2a94438..1be1185 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -41,7 +41,7 @@ void dstrfree(dchar *dstr); * @param cap * The initial capacity of the string. */ -attr_malloc(dstrfree, 1) +attr(malloc(dstrfree, 1)) dchar *dstralloc(size_t cap); /** @@ -50,7 +50,7 @@ dchar *dstralloc(size_t cap); * @param str * The NUL-terminated string to copy. */ -attr_malloc(dstrfree, 1) +attr(malloc(dstrfree, 1)) dchar *dstrdup(const char *str); /** @@ -61,7 +61,7 @@ dchar *dstrdup(const char *str); * @param n * The maximum number of characters to copy from str. */ -attr_malloc(dstrfree, 1) +attr(malloc(dstrfree, 1)) dchar *dstrndup(const char *str, size_t n); /** @@ -70,7 +70,7 @@ dchar *dstrndup(const char *str, size_t n); * @param dstr * The dynamic string to copy. */ -attr_malloc(dstrfree, 1) +attr(malloc(dstrfree, 1)) dchar *dstrddup(const dchar *dstr); /** @@ -81,7 +81,7 @@ dchar *dstrddup(const dchar *dstr); * @param len * The length of the string, which may include internal NUL bytes. */ -attr_malloc(dstrfree, 1) +attr(malloc(dstrfree, 1)) dchar *dstrxdup(const char *str, size_t len); /** @@ -243,7 +243,7 @@ int dstrxcpy(dchar **dest, const char *str, size_t len); * @return * The created string, or NULL on failure. */ -attr_format(1, 2) +attr(format(1, 2)) char *dstrprintf(const char *format, ...); /** @@ -256,7 +256,7 @@ char *dstrprintf(const char *format, ...); * @return * The created string, or NULL on failure. */ -attr_format(1, 0) +attr(format(1, 0)) char *dstrvprintf(const char *format, va_list args); /** @@ -271,7 +271,7 @@ char *dstrvprintf(const char *format, va_list args); * @return * 0 on success, -1 on failure. */ -attr_format(2, 3) +attr(format(2, 3)) int dstrcatf(dchar **str, const char *format, ...); /** @@ -286,7 +286,7 @@ int dstrcatf(dchar **str, const char *format, ...); * @return * 0 on success, -1 on failure. */ -attr_format(2, 0) +attr(format(2, 0)) int dstrvcatf(dchar **str, const char *format, va_list args); /** diff --git a/src/eval.c b/src/eval.c index a990fd4..859ad7e 100644 --- a/src/eval.c +++ b/src/eval.c @@ -57,7 +57,7 @@ struct bfs_eval { /** * Print an error message. */ -attr_format(2, 3) +attr(format(2, 3)) static void eval_error(struct bfs_eval *state, const char *format, ...) { // By POSIX, any errors should be accompanied by a non-zero exit status *state->ret = EXIT_FAILURE; diff --git a/src/exec.c b/src/exec.c index 90b3598..ba2fec8 100644 --- a/src/exec.c +++ b/src/exec.c @@ -22,7 +22,7 @@ #include /** Print some debugging info. */ -attr_format(2, 3) +attr(format(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 4d22d99..a48eeb0 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -32,7 +32,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. */ -attr_maybe_unused +attr(maybe_unused) static const char *fake_at(const struct BFTW *ftwbuf) { static atomic int proc_works = -1; @@ -66,7 +66,7 @@ fail: return ftwbuf->path; } -attr_maybe_unused +attr(maybe_unused) static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { if (path != ftwbuf->path) { dstrfree((dchar *)path); @@ -76,7 +76,7 @@ static void free_fake_at(const struct BFTW *ftwbuf, const char *path) { /** * Check if an error was caused by the absence of support or data for a feature. */ -attr_maybe_unused +attr(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 diff --git a/src/ioq.c b/src/ioq.c index bd8d111..2739338 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -273,7 +273,7 @@ static struct ioq_monitor *ioq_slot_monitor(struct ioqq *ioqq, ioq_slot *slot) { } /** Atomically wait for a slot to change. */ -attr_noinline +attr(noinline) static uintptr_t ioq_slot_wait(struct ioqq *ioqq, ioq_slot *slot, uintptr_t value) { struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); mutex_lock(&monitor->mutex); @@ -303,7 +303,7 @@ done: } /** Wake up any threads waiting on a slot. */ -attr_noinline +attr(noinline) static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { struct ioq_monitor *monitor = ioq_slot_monitor(ioqq, slot); diff --git a/src/mtab.c b/src/mtab.c index 082150c..cc726a2 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -61,7 +61,7 @@ struct bfs_mtab { /** * Add an entry to the mount table. */ -attr_maybe_unused +attr(maybe_unused) static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { struct bfs_mount *mount = RESERVE(struct bfs_mount, &mtab->mounts, &mtab->nmounts); if (!mount) { diff --git a/src/opt.c b/src/opt.c index 5da73d8..3ee5e81 100644 --- a/src/opt.c +++ b/src/opt.c @@ -305,7 +305,7 @@ struct opt_state { }; /** Log an optimization. */ -attr_format(3, 4) +attr(format(3, 4)) static bool opt_debug(const struct opt_state *state, int level, const char *format, ...) { bfs_assert(state->ctx->optlevel >= level); @@ -321,7 +321,7 @@ static bool opt_debug(const struct opt_state *state, int level, const char *form } /** Warn about an expression. */ -attr_format(3, 4) +attr(format(3, 4)) static void opt_warning(const struct opt_state *state, const struct bfs_expr *expr, const char *format, ...) { if (bfs_expr_warning(state->ctx, expr)) { va_list args; diff --git a/src/parse.c b/src/parse.c index 778fc68..35d22fb 100644 --- a/src/parse.c +++ b/src/parse.c @@ -246,7 +246,7 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, /** * Print an error message during parsing. */ -attr_format(2, 3) +attr(format(2, 3)) static void parse_error(const struct parser_state *state, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -266,7 +266,7 @@ static void parse_error(const struct parser_state *state, const char *format, .. /** * Print an error about some command line arguments. */ -attr_format(4, 5) +attr(format(4, 5)) static void parse_argv_error(const struct parser_state *state, char **argv, size_t argc, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -286,7 +286,7 @@ static void parse_argv_error(const struct parser_state *state, char **argv, size /** * Print an error about conflicting command line arguments. */ -attr_format(6, 7) +attr(format(6, 7)) static void parse_conflict_error(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -307,7 +307,7 @@ static void parse_conflict_error(const struct parser_state *state, char **argv1, /** * Print an error about an expression. */ -attr_format(3, 4) +attr(format(3, 4)) static void parse_expr_error(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -324,7 +324,7 @@ static void parse_expr_error(const struct parser_state *state, const struct bfs_ /** * Print a warning message during parsing. */ -attr_format(2, 3) +attr(format(2, 3)) static bool parse_warning(const struct parser_state *state, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -347,7 +347,7 @@ static bool parse_warning(const struct parser_state *state, const char *format, /** * Print a warning about conflicting command line arguments. */ -attr_format(6, 7) +attr(format(6, 7)) static bool parse_conflict_warning(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -371,7 +371,7 @@ static bool parse_conflict_warning(const struct parser_state *state, char **argv /** * Print a warning about an expression. */ -attr_format(3, 4) +attr(format(3, 4)) static bool parse_expr_warning(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; diff --git a/src/trie.c b/src/trie.c index cf55cee..bd5300d 100644 --- a/src/trie.c +++ b/src/trie.c @@ -95,7 +95,7 @@ bfs_static_assert(CHAR_WIDTH == 8); #if __i386__ || __x86_64__ -# define trie_clones attr_target_clones("popcnt", "default") +# define trie_clones attr(target_clones("popcnt", "default")) #else # define trie_clones #endif diff --git a/src/xspawn.c b/src/xspawn.c index 03287c3..40115a1 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -83,7 +83,7 @@ int bfs_spawn_destroy(struct bfs_spawn *ctx) { #if _POSIX_SPAWN > 0 /** Set some posix_spawnattr flags. */ -attr_maybe_unused +attr(maybe_unused) static int bfs_spawn_addflags(struct bfs_spawn *ctx, short flags) { short prev; errno = posix_spawnattr_getflags(&ctx->attr, &prev); -- cgit v1.2.3 From a85fc263a89fb89bc8b2a8166747660a177b1773 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 22 Nov 2023 09:33:49 -0500 Subject: config: s/attr_format/attr_printf/ --- src/bar.c | 2 +- src/color.c | 4 ++-- src/color.h | 4 ++-- src/config.h | 4 ++-- src/diag.h | 14 +++++++------- src/dstring.h | 8 ++++---- src/eval.c | 2 +- src/exec.c | 2 +- src/opt.c | 4 ++-- src/parse.c | 14 +++++++------- 10 files changed, 29 insertions(+), 29 deletions(-) diff --git a/src/bar.c b/src/bar.c index babadc3..8ab4112 100644 --- a/src/bar.c +++ b/src/bar.c @@ -127,7 +127,7 @@ static void reset_before_death_by(int sig) { } /** printf() to the status bar with a single write(). */ -attr(format(2, 3)) +attr(printf(2, 3)) static int bfs_bar_printf(struct bfs_bar *bar, const char *format, ...) { va_list args; va_start(args, format); diff --git a/src/color.c b/src/color.c index 4c2c8ca..5247cbf 100644 --- a/src/color.c +++ b/src/color.c @@ -1105,7 +1105,7 @@ static int print_link_target(CFILE *cfile, const struct BFTW *ftwbuf) { } /** Format some colored output to the buffer. */ -attr(format(2, 3)) +attr(printf(2, 3)) static int cbuff(CFILE *cfile, const char *format, ...); /** Dump a parsed expression tree, for debugging. */ @@ -1177,7 +1177,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { return 0; } -attr(format(2, 0)) +attr(printf(2, 0)) static int cvbuff(CFILE *cfile, const char *format, va_list args) { const struct colors *colors = cfile->colors; int error = errno; diff --git a/src/color.h b/src/color.h index 81f0e2a..760aa9d 100644 --- a/src/color.h +++ b/src/color.h @@ -100,13 +100,13 @@ int cfclose(CFILE *cfile); * @return * 0 on success, -1 on failure. */ -attr(format(2, 3)) +attr(printf(2, 3)) int cfprintf(CFILE *cfile, const char *format, ...); /** * cfprintf() variant that takes a va_list. */ -attr(format(2, 0)) +attr(printf(2, 0)) int cvfprintf(CFILE *cfile, const char *format, va_list args); #endif // BFS_COLOR_H diff --git a/src/config.h b/src/config.h index aa03552..46f1efe 100644 --- a/src/config.h +++ b/src/config.h @@ -240,9 +240,9 @@ typedef long double max_align_t; * Adds compiler warnings for bad printf()-style function calls, if supported. */ #if __has_attribute(format) -# define attr_format(fmt, args) __attribute__((format(printf, fmt, args))) +# define attr_printf(fmt, args) __attribute__((format(printf, fmt, args))) #else -# define attr_format(fmt, args) +# define attr_printf(fmt, args) #endif /** diff --git a/src/diag.h b/src/diag.h index aa6a44f..791c065 100644 --- a/src/diag.h +++ b/src/diag.h @@ -44,7 +44,7 @@ struct bfs_loc { /** * Print a message to standard error and abort. */ -attr(cold, format(2, 3)) +attr(cold, printf(2, 3)) noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...); /** @@ -122,7 +122,7 @@ void bfs_perror(const struct bfs_ctx *ctx, const char *str); /** * Shorthand for printing error messages. */ -attr(cold, format(2, 3)) +attr(cold, printf(2, 3)) void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -130,7 +130,7 @@ void bfs_error(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a warning was printed. */ -attr(cold, format(2, 3)) +attr(cold, printf(2, 3)) bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); /** @@ -138,25 +138,25 @@ bool bfs_warning(const struct bfs_ctx *ctx, const char *format, ...); * * @return Whether a debug message was printed. */ -attr(cold, format(3, 4)) +attr(cold, printf(3, 4)) bool bfs_debug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, ...); /** * bfs_error() variant that takes a va_list. */ -attr(cold, format(2, 0)) +attr(cold, printf(2, 0)) void bfs_verror(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_warning() variant that takes a va_list. */ -attr(cold, format(2, 0)) +attr(cold, printf(2, 0)) bool bfs_vwarning(const struct bfs_ctx *ctx, const char *format, va_list args); /** * bfs_debug() variant that takes a va_list. */ -attr(cold, format(3, 0)) +attr(cold, printf(3, 0)) bool bfs_vdebug(const struct bfs_ctx *ctx, enum debug_flags flag, const char *format, va_list args); /** diff --git a/src/dstring.h b/src/dstring.h index 1be1185..6006199 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -243,7 +243,7 @@ int dstrxcpy(dchar **dest, const char *str, size_t len); * @return * The created string, or NULL on failure. */ -attr(format(1, 2)) +attr(printf(1, 2)) char *dstrprintf(const char *format, ...); /** @@ -256,7 +256,7 @@ char *dstrprintf(const char *format, ...); * @return * The created string, or NULL on failure. */ -attr(format(1, 0)) +attr(printf(1, 0)) char *dstrvprintf(const char *format, va_list args); /** @@ -271,7 +271,7 @@ char *dstrvprintf(const char *format, va_list args); * @return * 0 on success, -1 on failure. */ -attr(format(2, 3)) +attr(printf(2, 3)) int dstrcatf(dchar **str, const char *format, ...); /** @@ -286,7 +286,7 @@ int dstrcatf(dchar **str, const char *format, ...); * @return * 0 on success, -1 on failure. */ -attr(format(2, 0)) +attr(printf(2, 0)) int dstrvcatf(dchar **str, const char *format, va_list args); /** diff --git a/src/eval.c b/src/eval.c index 859ad7e..372fcf5 100644 --- a/src/eval.c +++ b/src/eval.c @@ -57,7 +57,7 @@ struct bfs_eval { /** * Print an error message. */ -attr(format(2, 3)) +attr(printf(2, 3)) static void eval_error(struct bfs_eval *state, const char *format, ...) { // By POSIX, any errors should be accompanied by a non-zero exit status *state->ret = EXIT_FAILURE; diff --git a/src/exec.c b/src/exec.c index ba2fec8..60bfd28 100644 --- a/src/exec.c +++ b/src/exec.c @@ -22,7 +22,7 @@ #include /** Print some debugging info. */ -attr(format(2, 3)) +attr(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/opt.c b/src/opt.c index 3ee5e81..ddcd1ab 100644 --- a/src/opt.c +++ b/src/opt.c @@ -305,7 +305,7 @@ struct opt_state { }; /** Log an optimization. */ -attr(format(3, 4)) +attr(printf(3, 4)) static bool opt_debug(const struct opt_state *state, int level, const char *format, ...) { bfs_assert(state->ctx->optlevel >= level); @@ -321,7 +321,7 @@ static bool opt_debug(const struct opt_state *state, int level, const char *form } /** Warn about an expression. */ -attr(format(3, 4)) +attr(printf(3, 4)) static void opt_warning(const struct opt_state *state, const struct bfs_expr *expr, const char *format, ...) { if (bfs_expr_warning(state->ctx, expr)) { va_list args; diff --git a/src/parse.c b/src/parse.c index 35d22fb..8d9c3f0 100644 --- a/src/parse.c +++ b/src/parse.c @@ -246,7 +246,7 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, /** * Print an error message during parsing. */ -attr(format(2, 3)) +attr(printf(2, 3)) static void parse_error(const struct parser_state *state, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -266,7 +266,7 @@ static void parse_error(const struct parser_state *state, const char *format, .. /** * Print an error about some command line arguments. */ -attr(format(4, 5)) +attr(printf(4, 5)) static void parse_argv_error(const struct parser_state *state, char **argv, size_t argc, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -286,7 +286,7 @@ static void parse_argv_error(const struct parser_state *state, char **argv, size /** * Print an error about conflicting command line arguments. */ -attr(format(6, 7)) +attr(printf(6, 7)) static void parse_conflict_error(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -307,7 +307,7 @@ static void parse_conflict_error(const struct parser_state *state, char **argv1, /** * Print an error about an expression. */ -attr(format(3, 4)) +attr(printf(3, 4)) static void parse_expr_error(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -324,7 +324,7 @@ static void parse_expr_error(const struct parser_state *state, const struct bfs_ /** * Print a warning message during parsing. */ -attr(format(2, 3)) +attr(printf(2, 3)) static bool parse_warning(const struct parser_state *state, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -347,7 +347,7 @@ static bool parse_warning(const struct parser_state *state, const char *format, /** * Print a warning about conflicting command line arguments. */ -attr(format(6, 7)) +attr(printf(6, 7)) static bool parse_conflict_warning(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; @@ -371,7 +371,7 @@ static bool parse_conflict_warning(const struct parser_state *state, char **argv /** * Print a warning about an expression. */ -attr(format(3, 4)) +attr(printf(3, 4)) static bool parse_expr_warning(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { int error = errno; const struct bfs_ctx *ctx = state->ctx; -- cgit v1.2.3 From 2f3653bf522ce079501254bfd34abbf884dc528e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 20 Dec 2023 18:34:49 -0500 Subject: config: Fix attr_nodiscard to use warn_unused_result --- src/config.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.h b/src/config.h index 46f1efe..91cef71 100644 --- a/src/config.h +++ b/src/config.h @@ -212,8 +212,8 @@ typedef long double max_align_t; */ #if __has_c_attribute(nodiscard) # define attr_nodiscard [[nodiscard]] -#elif __has_attribute(nodiscard) -# define attr_nodiscard __attribute__((nodiscard)) +#elif __has_attribute(warn_unused_result) +# define attr_nodiscard __attribute__((warn_unused_result)) #else # define attr_nodiscard #endif -- cgit v1.2.3 From cbbcb340c974fca7312713d2515e3b32bacb176f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 20 Dec 2023 18:35:58 -0500 Subject: alloc: Add attr(nodiscard) to a few functions --- src/alloc.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/alloc.h b/src/alloc.h index f21ddb9..60dd738 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -168,6 +168,7 @@ void *zalloc(size_t align, size_t size); * @return * The reallocated memory, or NULL on failure. */ +attr(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. */ @@ -193,6 +194,7 @@ void *xrealloc(void *ptr, size_t align, size_t old_size, size_t new_size); * for (count + 1) elements. On failure, errno will be non-zero, and * ptr will returned unchanged. */ +attr(nodiscard) void *reserve(void *ptr, size_t align, size_t size, size_t count); /** @@ -349,6 +351,7 @@ void *varena_alloc(struct varena *varena, size_t count); * @return * The resized struct, or NULL on failure. */ +attr(nodiscard) void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t new_count); /** @@ -363,6 +366,7 @@ void *varena_realloc(struct varena *varena, void *ptr, size_t old_count, size_t * @return * The resized struct, or NULL on failure. */ +attr(nodiscard) void *varena_grow(struct varena *varena, void *ptr, size_t *count); /** -- cgit v1.2.3 From 8f004e73238c5ee4be40c044827138eb5895ce88 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 21 Jul 2023 10:11:20 -0400 Subject: expr: Move some implementation into expr.c --- GNUmakefile | 1 + src/expr.c | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/parse.c | 45 --------------------------------------------- 3 files changed, 57 insertions(+), 45 deletions(-) create mode 100644 src/expr.c diff --git a/GNUmakefile b/GNUmakefile index bcfdf76..a4d1c2d 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -241,6 +241,7 @@ LIBBFS := \ $(OBJ)/src/dstring.o \ $(OBJ)/src/eval.o \ $(OBJ)/src/exec.o \ + $(OBJ)/src/expr.o \ $(OBJ)/src/fsade.o \ $(OBJ)/src/ioq.o \ $(OBJ)/src/mtab.o \ diff --git a/src/expr.c b/src/expr.c new file mode 100644 index 0000000..380038c --- /dev/null +++ b/src/expr.c @@ -0,0 +1,56 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "expr.h" +#include "alloc.h" +#include "eval.h" +#include "exec.h" +#include "printf.h" +#include "xregex.h" +#include +#include + +struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { + struct bfs_expr *expr = ZALLOC(struct bfs_expr); + if (!expr) { + perror("zalloc()"); + return NULL; + } + + expr->eval_fn = eval_fn; + expr->argc = argc; + expr->argv = argv; + expr->probability = 0.5; + return expr; +} + +bool bfs_expr_is_parent(const struct bfs_expr *expr) { + return expr->eval_fn == eval_and + || expr->eval_fn == eval_or + || expr->eval_fn == eval_not + || expr->eval_fn == eval_comma; +} + +bool bfs_expr_never_returns(const struct bfs_expr *expr) { + // Expressions that never return are vacuously both always true and always false + return expr->always_true && expr->always_false; +} + +void bfs_expr_free(struct bfs_expr *expr) { + if (!expr) { + return; + } + + if (bfs_expr_is_parent(expr)) { + bfs_expr_free(expr->rhs); + bfs_expr_free(expr->lhs); + } else if (expr->eval_fn == eval_exec) { + bfs_exec_free(expr->exec); + } else if (expr->eval_fn == eval_fprintf) { + bfs_printf_free(expr->printf); + } else if (expr->eval_fn == eval_regex) { + bfs_regfree(expr->regex); + } + + free(expr); +} diff --git a/src/parse.c b/src/parse.c index 8d9c3f0..2d7b3a9 100644 --- a/src/parse.c +++ b/src/parse.c @@ -54,51 +54,6 @@ static char *fake_or_arg = "-o"; static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; -struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { - struct bfs_expr *expr = ZALLOC(struct bfs_expr); - if (!expr) { - perror("zalloc()"); - return NULL; - } - - expr->eval_fn = eval_fn; - expr->argc = argc; - expr->argv = argv; - expr->probability = 0.5; - return expr; -} - -bool bfs_expr_is_parent(const struct bfs_expr *expr) { - return expr->eval_fn == eval_and - || expr->eval_fn == eval_or - || expr->eval_fn == eval_not - || expr->eval_fn == eval_comma; -} - -bool bfs_expr_never_returns(const struct bfs_expr *expr) { - // Expressions that never return are vacuously both always true and always false - return expr->always_true && expr->always_false; -} - -void bfs_expr_free(struct bfs_expr *expr) { - if (!expr) { - return; - } - - if (bfs_expr_is_parent(expr)) { - bfs_expr_free(expr->rhs); - bfs_expr_free(expr->lhs); - } else if (expr->eval_fn == eval_exec) { - bfs_exec_free(expr->exec); - } else if (expr->eval_fn == eval_fprintf) { - bfs_printf_free(expr->printf); - } else if (expr->eval_fn == eval_regex) { - bfs_regfree(expr->regex); - } - - free(expr); -} - /** * Create a new unary expression. */ -- cgit v1.2.3 From c60d93493f958cde1769a90058dac719ae1efa8c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 29 Jul 2023 14:44:26 -0400 Subject: opt: Use more standard terminology for data flow domains --- src/opt.c | 592 +++++++++++++++++++++++++++++++------------------------------- 1 file changed, 294 insertions(+), 298 deletions(-) diff --git a/src/opt.c b/src/opt.c index ddcd1ab..a989670 100644 --- a/src/opt.c +++ b/src/opt.c @@ -6,14 +6,13 @@ * * -O1: basic logical simplifications, like folding (-true -and -foo) to -foo. * - * -O2: dead code elimination and data flow analysis. struct opt_facts is used + * -O2: dead code elimination and data flow analysis. struct df_domain is used * to record data flow facts that are true at various points of evaluation. - * Specifically, struct opt_facts records the facts that must be true before an - * expression is evaluated (state->facts), and those that must be true after the - * expression is evaluated, given that it returns true (state->facts_when_true) - * or false (state->facts_when_true). Additionally, state->facts_when_impure - * records the possible data flow facts before any expressions with side effects - * are evaluated. + * Specifically, struct df_domain records the state before an expression is + * evaluated (opt->before), and after an expression returns true + * (opt->after_true) or false (opt->after_false). Additionally, opt->impure + * records the possible state before any expression with side effects is + * evaluated. * * -O3: expression re-ordering to reduce expected cost. In an expression like * (-foo -and -bar), if both -foo and -bar are pure (no side effects), they can @@ -22,8 +21,8 @@ * -bar is likely to return false. * * -O4/-Ofast: aggressive optimizations that may affect correctness in corner - * cases. The main effect is to use facts_when_impure to determine if any side- - * effects are reachable at all, and skipping the traversal if not. + * cases. The main effect is to use impure to determine if any side-effects are + * reachable at all, and skipping the traversal if not. */ #include "opt.h" @@ -46,16 +45,58 @@ static char *fake_and_arg = "-a"; static char *fake_or_arg = "-o"; static char *fake_not_arg = "!"; +/** + * The data flow domain for predicates. + */ +enum df_pred { + /** The bottom state (unreachable). */ + PRED_BOTTOM = 0, + /** The predicate is known to be false. */ + PRED_FALSE = 1 << false, + /** The predicate is known to be true. */ + PRED_TRUE = 1 << true, + /** The top state (unknown). */ + PRED_TOP = PRED_FALSE | PRED_TRUE, +}; + +/** Make a predicate known. */ +static void constrain_pred(enum df_pred *pred, bool value) { + *pred &= 1 << value; +} + +/** Compute the join (union) of two predicates. */ +static void pred_join(enum df_pred *dest, enum df_pred src) { + *dest |= src; +} + /** * A contrained integer range. */ -struct range { +struct df_range { /** The (inclusive) minimum value. */ long long min; /** The (inclusive) maximum value. */ long long max; }; +/** Initialize an empty range. */ +static void range_init_bottom(struct df_range *range) { + range->min = LLONG_MAX; + range->max = LLONG_MIN; +} + +/** Check if a range is empty. */ +static bool range_is_bottom(const struct df_range *range) { + return range->min > range->max; +} + +/** Initialize a full range. */ +static void range_init_top(struct df_range *range) { + // All ranges we currently track are non-negative + range->min = 0; + range->max = LLONG_MAX; +} + /** Compute the minimum of two values. */ static long long min_value(long long a, long long b) { if (a < b) { @@ -75,17 +116,17 @@ static long long max_value(long long a, long long b) { } /** Constrain the minimum of a range. */ -static void constrain_min(struct range *range, long long value) { +static void constrain_min(struct df_range *range, long long value) { range->min = max_value(range->min, value); } /** Contrain the maximum of a range. */ -static void constrain_max(struct range *range, long long value) { +static void constrain_max(struct df_range *range, long long value) { range->max = min_value(range->max, value); } /** Remove a single value from a range. */ -static void range_remove(struct range *range, long long value) { +static void range_remove(struct df_range *range, long long value) { if (range->min == value) { if (range->min == LLONG_MAX) { range->max = LLONG_MIN; @@ -104,20 +145,9 @@ static void range_remove(struct range *range, long long value) { } /** Compute the union of two ranges. */ -static void range_union(struct range *result, const struct range *lhs, const struct range *rhs) { - result->min = min_value(lhs->min, rhs->min); - result->max = max_value(lhs->max, rhs->max); -} - -/** Check if a range contains no values. */ -static bool range_is_impossible(const struct range *range) { - return range->min > range->max; -} - -/** Set a range to contain no values. */ -static void set_range_impossible(struct range *range) { - range->min = LLONG_MAX; - range->max = LLONG_MIN; +static void range_join(struct df_range *dest, const struct df_range *src) { + dest->min = min_value(dest->min, src->min); + dest->max = max_value(dest->max, src->max); } /** @@ -140,42 +170,6 @@ enum range_type { RANGE_TYPES, }; -/** - * A possibly-known value of a predicate. - */ -enum known_pred { - /** The state is impossible to reach. */ - PRED_IMPOSSIBLE = -2, - /** The value of the predicate is not known. */ - PRED_UNKNOWN = -1, - /** The predicate is known to be false. */ - PRED_FALSE = false, - /** The predicate is known to be true. */ - PRED_TRUE = true, -}; - -/** Make a predicate known. */ -static void constrain_pred(enum known_pred *pred, bool value) { - if (*pred == PRED_UNKNOWN) { - *pred = value; - } else if (*pred == !value) { - *pred = PRED_IMPOSSIBLE; - } -} - -/** Compute the union of two known predicates. */ -static enum known_pred pred_union(enum known_pred lhs, enum known_pred rhs) { - if (lhs == PRED_IMPOSSIBLE) { - return rhs; - } else if (rhs == PRED_IMPOSSIBLE) { - return lhs; - } else if (lhs == rhs) { - return lhs; - } else { - return PRED_UNKNOWN; - } -} - /** * Types of predicates we track. */ @@ -207,14 +201,14 @@ enum pred_type { }; /** - * Data flow facts about an evaluation point. + * The data flow analysis domain. */ -struct opt_facts { - /** The value ranges we track. */ - struct range ranges[RANGE_TYPES]; - +struct df_domain { /** The predicates we track. */ - enum known_pred preds[PRED_TYPES]; + enum df_pred preds[PRED_TYPES]; + + /** The value ranges we track. */ + struct df_range ranges[RANGE_TYPES]; /** Bitmask of possible file types. */ unsigned int types; @@ -222,97 +216,101 @@ struct opt_facts { unsigned int xtypes; }; -/** Initialize some data flow facts. */ -static void facts_init(struct opt_facts *facts) { - for (int i = 0; i < RANGE_TYPES; ++i) { - struct range *range = &facts->ranges[i]; - range->min = 0; // All ranges we currently track are non-negative - range->max = LLONG_MAX; - } - +/** Set a data flow value to bottom. */ +static void df_init_bottom(struct df_domain *value) { for (int i = 0; i < PRED_TYPES; ++i) { - facts->preds[i] = PRED_UNKNOWN; + value->preds[i] = PRED_BOTTOM; } - facts->types = ~0; - facts->xtypes = ~0; -} - -/** Compute the union of two fact sets. */ -static void facts_union(struct opt_facts *result, const struct opt_facts *lhs, const struct opt_facts *rhs) { for (int i = 0; i < RANGE_TYPES; ++i) { - range_union(&result->ranges[i], &lhs->ranges[i], &rhs->ranges[i]); + range_init_bottom(&value->ranges[i]); } - for (int i = 0; i < PRED_TYPES; ++i) { - result->preds[i] = pred_union(lhs->preds[i], rhs->preds[i]); - } - - result->types = lhs->types | rhs->types; - result->xtypes = lhs->xtypes | rhs->xtypes; + value->types = 0; + value->xtypes = 0; } /** Determine whether a fact set is impossible. */ -static bool facts_are_impossible(const struct opt_facts *facts) { +static bool df_is_bottom(const struct df_domain *value) { for (int i = 0; i < RANGE_TYPES; ++i) { - if (range_is_impossible(&facts->ranges[i])) { + if (range_is_bottom(&value->ranges[i])) { return true; } } for (int i = 0; i < PRED_TYPES; ++i) { - if (facts->preds[i] == PRED_IMPOSSIBLE) { + if (value->preds[i] == PRED_BOTTOM) { return true; } } - if (!facts->types || !facts->xtypes) { + if (!value->types || !value->xtypes) { return true; } return false; } -/** Set some facts to be impossible. */ -static void set_facts_impossible(struct opt_facts *facts) { +/** Initialize some data flow value. */ +static void df_init_top(struct df_domain *value) { + for (int i = 0; i < PRED_TYPES; ++i) { + value->preds[i] = PRED_TOP; + } + for (int i = 0; i < RANGE_TYPES; ++i) { - set_range_impossible(&facts->ranges[i]); + range_init_top(&value->ranges[i]); } + value->types = ~0; + value->xtypes = ~0; +} + +/** Compute the union of two fact sets. */ +static void df_join(struct df_domain *dest, const struct df_domain *src) { for (int i = 0; i < PRED_TYPES; ++i) { - facts->preds[i] = PRED_IMPOSSIBLE; + pred_join(&dest->preds[i], src->preds[i]); } - facts->types = 0; - facts->xtypes = 0; + for (int i = 0; i < RANGE_TYPES; ++i) { + range_join(&dest->ranges[i], &src->ranges[i]); + } + + dest->types |= src->types; + dest->xtypes |= src->xtypes; } /** * Optimizer state. */ -struct opt_state { +struct bfs_opt { /** The context we're optimizing. */ const struct bfs_ctx *ctx; - /** Data flow facts before this expression is evaluated. */ - struct opt_facts facts; - /** Data flow facts after this expression returns true. */ - struct opt_facts facts_when_true; - /** Data flow facts after this expression returns false. */ - struct opt_facts facts_when_false; - /** Data flow facts before any side-effecting expressions are evaluated. */ - struct opt_facts *facts_when_impure; + /** Data flow state before this expression is evaluated. */ + struct df_domain before; + /** Data flow state after this expression returns true. */ + struct df_domain after_true; + /** Data flow state after this expression returns false. */ + struct df_domain after_false; + /** Data flow state before any side-effecting expressions are evaluated. */ + struct df_domain *impure; }; +/** Constrain the value of a predicate. */ +static void opt_constrain_pred(struct bfs_opt *opt, enum pred_type type, bool value) { + constrain_pred(&opt->after_true.preds[type], value); + constrain_pred(&opt->after_false.preds[type], !value); +} + /** Log an optimization. */ attr(printf(3, 4)) -static bool opt_debug(const struct opt_state *state, int level, const char *format, ...) { - bfs_assert(state->ctx->optlevel >= level); +static bool opt_debug(const struct bfs_opt *opt, int level, const char *format, ...) { + bfs_assert(opt->ctx->optlevel >= level); - if (bfs_debug(state->ctx, DEBUG_OPT, "${cyn}-O%d${rs}: ", level)) { + if (bfs_debug(opt->ctx, DEBUG_OPT, "${cyn}-O%d${rs}: ", level)) { va_list args; va_start(args, format); - cvfprintf(state->ctx->cerr, format, args); + cvfprintf(opt->ctx->cerr, format, args); va_end(args); return true; } else { @@ -322,11 +320,11 @@ static bool opt_debug(const struct opt_state *state, int level, const char *form /** Warn about an expression. */ attr(printf(3, 4)) -static void opt_warning(const struct opt_state *state, const struct bfs_expr *expr, const char *format, ...) { - if (bfs_expr_warning(state->ctx, expr)) { +static void opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, const char *format, ...) { + if (bfs_expr_warning(opt->ctx, expr)) { va_list args; va_start(args, format); - bfs_vwarning(state->ctx, format, args); + bfs_vwarning(opt->ctx, format, args); va_end(args); } } @@ -365,15 +363,15 @@ static struct bfs_expr *negate_expr(struct bfs_expr *rhs, char **argv) { return expr; } -static struct bfs_expr *optimize_not_expr(const struct opt_state *state, struct bfs_expr *expr); -static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct bfs_expr *expr); -static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct bfs_expr *expr); +static struct bfs_expr *optimize_not_expr(const struct bfs_opt *opt, struct bfs_expr *expr); +static struct bfs_expr *optimize_and_expr(const struct bfs_opt *opt, struct bfs_expr *expr); +static struct bfs_expr *optimize_or_expr(const struct bfs_opt *opt, struct bfs_expr *expr); /** * Apply De Morgan's laws. */ -static struct bfs_expr *de_morgan(const struct opt_state *state, struct bfs_expr *expr, char **argv) { - bool debug = opt_debug(state, 1, "De Morgan's laws: %pe ", expr); +static struct bfs_expr *de_morgan(const struct bfs_opt *opt, struct bfs_expr *expr, char **argv) { + bool debug = opt_debug(opt, 1, "De Morgan's laws: %pe ", expr); struct bfs_expr *parent = negate_expr(expr, argv); if (!parent) { @@ -403,14 +401,14 @@ static struct bfs_expr *de_morgan(const struct opt_state *state, struct bfs_expr } if (debug) { - cfprintf(state->ctx->cerr, "<==> %pe\n", parent); + cfprintf(opt->ctx->cerr, "<==> %pe\n", parent); } if (expr->lhs->eval_fn == eval_not) { - expr->lhs = optimize_not_expr(state, expr->lhs); + expr->lhs = optimize_not_expr(opt, expr->lhs); } if (expr->rhs->eval_fn == eval_not) { - expr->rhs = optimize_not_expr(state, expr->rhs); + expr->rhs = optimize_not_expr(opt, expr->rhs); } if (!expr->lhs || !expr->rhs) { bfs_expr_free(parent); @@ -418,9 +416,9 @@ static struct bfs_expr *de_morgan(const struct opt_state *state, struct bfs_expr } if (expr->eval_fn == eval_and) { - expr = optimize_and_expr(state, expr); + expr = optimize_and_expr(opt, expr); } else { - expr = optimize_or_expr(state, expr); + expr = optimize_or_expr(opt, expr); } if (has_parent) { parent->rhs = expr; @@ -433,38 +431,38 @@ static struct bfs_expr *de_morgan(const struct opt_state *state, struct bfs_expr } if (has_parent) { - parent = optimize_not_expr(state, parent); + parent = optimize_not_expr(opt, parent); } return parent; } /** Optimize an expression recursively. */ -static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct bfs_expr *expr); +static struct bfs_expr *optimize_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr); /** * Optimize a negation. */ -static struct bfs_expr *optimize_not_expr(const struct opt_state *state, struct bfs_expr *expr) { +static struct bfs_expr *optimize_not_expr(const struct bfs_opt *opt, struct bfs_expr *expr) { bfs_assert(expr->eval_fn == eval_not); struct bfs_expr *rhs = expr->rhs; - int optlevel = state->ctx->optlevel; + int optlevel = opt->ctx->optlevel; if (optlevel >= 1) { if (rhs->eval_fn == eval_true || rhs->eval_fn == eval_false) { struct bfs_expr *ret = opt_const(rhs->eval_fn == eval_false); - opt_debug(state, 1, "constant propagation: %pe <==> %pe\n", expr, ret); + opt_debug(opt, 1, "constant propagation: %pe <==> %pe\n", expr, ret); bfs_expr_free(expr); return ret; } else if (rhs->eval_fn == eval_not) { - opt_debug(state, 1, "double negation: %pe <==> %pe\n", expr, rhs->rhs); + opt_debug(opt, 1, "double negation: %pe <==> %pe\n", expr, rhs->rhs); return extract_child_expr(expr, &rhs->rhs); } else if (bfs_expr_never_returns(rhs)) { - opt_debug(state, 1, "reachability: %pe <==> %pe\n", expr, rhs); + opt_debug(opt, 1, "reachability: %pe <==> %pe\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if ((rhs->eval_fn == eval_and || rhs->eval_fn == eval_or) && (rhs->lhs->eval_fn == eval_not || rhs->rhs->eval_fn == eval_not)) { - return de_morgan(state, expr, expr->argv); + return de_morgan(opt, expr, expr->argv); } } @@ -478,17 +476,17 @@ static struct bfs_expr *optimize_not_expr(const struct opt_state *state, struct } /** Optimize a negation recursively. */ -static struct bfs_expr *optimize_not_expr_recursive(struct opt_state *state, struct bfs_expr *expr) { - struct opt_state rhs_state = *state; +static struct bfs_expr *optimize_not_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { + struct bfs_opt rhs_state = *opt; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } - state->facts_when_true = rhs_state.facts_when_false; - state->facts_when_false = rhs_state.facts_when_true; + opt->after_true = rhs_state.after_false; + opt->after_false = rhs_state.after_true; - return optimize_not_expr(state, expr); + return optimize_not_expr(opt, expr); fail: bfs_expr_free(expr); @@ -496,27 +494,27 @@ fail: } /** Optimize a conjunction. */ -static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct bfs_expr *expr) { +static struct bfs_expr *optimize_and_expr(const struct bfs_opt *opt, struct bfs_expr *expr) { bfs_assert(expr->eval_fn == eval_and); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; - const struct bfs_ctx *ctx = state->ctx; + const struct bfs_ctx *ctx = opt->ctx; int optlevel = ctx->optlevel; if (optlevel >= 1) { if (lhs->eval_fn == eval_true) { - opt_debug(state, 1, "conjunction elimination: %pe <==> %pe\n", expr, rhs); + opt_debug(opt, 1, "conjunction elimination: %pe <==> %pe\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (rhs->eval_fn == eval_true) { - opt_debug(state, 1, "conjunction elimination: %pe <==> %pe\n", expr, lhs); + opt_debug(opt, 1, "conjunction elimination: %pe <==> %pe\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_false) { - opt_debug(state, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); - opt_warning(state, expr->rhs, "This expression is unreachable.\n\n"); + opt_debug(opt, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); + opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_true && rhs->eval_fn == eval_false) { - bool debug = opt_debug(state, 1, "strength reduction: %pe <==> ", expr); + bool debug = opt_debug(opt, 1, "strength reduction: %pe <==> ", expr); struct bfs_expr *ret = extract_child_expr(expr, &expr->lhs); ret = negate_expr(ret, &fake_not_arg); if (debug && ret) { @@ -524,11 +522,11 @@ static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct } return ret; } else if (optlevel >= 2 && lhs->pure && rhs->eval_fn == eval_false) { - opt_debug(state, 2, "purity: %pe <==> %pe\n", expr, rhs); - opt_warning(state, expr->lhs, "The result of this expression is ignored.\n\n"); + opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); + opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); return extract_child_expr(expr, &expr->rhs); } else if (lhs->eval_fn == eval_not && rhs->eval_fn == eval_not) { - return de_morgan(state, expr, expr->lhs->argv); + return de_morgan(opt, expr, expr->lhs->argv); } } @@ -542,24 +540,25 @@ static struct bfs_expr *optimize_and_expr(const struct opt_state *state, struct } /** Optimize a conjunction recursively. */ -static struct bfs_expr *optimize_and_expr_recursive(struct opt_state *state, struct bfs_expr *expr) { - struct opt_state lhs_state = *state; +static struct bfs_expr *optimize_and_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { + struct bfs_opt lhs_state = *opt; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } - struct opt_state rhs_state = *state; - rhs_state.facts = lhs_state.facts_when_true; + struct bfs_opt rhs_state = *opt; + rhs_state.before = lhs_state.after_true; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } - state->facts_when_true = rhs_state.facts_when_true; - facts_union(&state->facts_when_false, &lhs_state.facts_when_false, &rhs_state.facts_when_false); + opt->after_true = rhs_state.after_true; + opt->after_false = lhs_state.after_false; + df_join(&opt->after_false, &rhs_state.after_false); - return optimize_and_expr(state, expr); + return optimize_and_expr(opt, expr); fail: bfs_expr_free(expr); @@ -567,27 +566,27 @@ fail: } /** Optimize a disjunction. */ -static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct bfs_expr *expr) { +static struct bfs_expr *optimize_or_expr(const struct bfs_opt *opt, struct bfs_expr *expr) { bfs_assert(expr->eval_fn == eval_or); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; - const struct bfs_ctx *ctx = state->ctx; + const struct bfs_ctx *ctx = opt->ctx; int optlevel = ctx->optlevel; if (optlevel >= 1) { if (lhs->always_true) { - opt_debug(state, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); - opt_warning(state, expr->rhs, "This expression is unreachable.\n\n"); + opt_debug(opt, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); + opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); return extract_child_expr(expr, &expr->lhs); } else if (lhs->eval_fn == eval_false) { - opt_debug(state, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, rhs); + opt_debug(opt, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, rhs); return extract_child_expr(expr, &expr->rhs); } else if (rhs->eval_fn == eval_false) { - opt_debug(state, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, lhs); + opt_debug(opt, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (lhs->always_false && rhs->eval_fn == eval_true) { - bool debug = opt_debug(state, 1, "strength reduction: %pe <==> ", expr); + bool debug = opt_debug(opt, 1, "strength reduction: %pe <==> ", expr); struct bfs_expr *ret = extract_child_expr(expr, &expr->lhs); ret = negate_expr(ret, &fake_not_arg); if (debug && ret) { @@ -595,11 +594,11 @@ static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct b } return ret; } else if (optlevel >= 2 && lhs->pure && rhs->eval_fn == eval_true) { - opt_debug(state, 2, "purity: %pe <==> %pe\n", expr, rhs); - opt_warning(state, expr->lhs, "The result of this expression is ignored.\n\n"); + opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); + opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); return extract_child_expr(expr, &expr->rhs); } else if (lhs->eval_fn == eval_not && rhs->eval_fn == eval_not) { - return de_morgan(state, expr, expr->lhs->argv); + return de_morgan(opt, expr, expr->lhs->argv); } } @@ -613,24 +612,25 @@ static struct bfs_expr *optimize_or_expr(const struct opt_state *state, struct b } /** Optimize a disjunction recursively. */ -static struct bfs_expr *optimize_or_expr_recursive(struct opt_state *state, struct bfs_expr *expr) { - struct opt_state lhs_state = *state; +static struct bfs_expr *optimize_or_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { + struct bfs_opt lhs_state = *opt; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } - struct opt_state rhs_state = *state; - rhs_state.facts = lhs_state.facts_when_false; + struct bfs_opt rhs_state = *opt; + rhs_state.before = lhs_state.after_false; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } - facts_union(&state->facts_when_true, &lhs_state.facts_when_true, &rhs_state.facts_when_true); - state->facts_when_false = rhs_state.facts_when_false; + opt->after_false = rhs_state.after_false; + opt->after_true = lhs_state.after_true; + df_join(&opt->after_true, &rhs_state.after_true); - return optimize_or_expr(state, expr); + return optimize_or_expr(opt, expr); fail: bfs_expr_free(expr); @@ -638,20 +638,20 @@ fail: } /** Optimize an expression in an ignored-result context. */ -static struct bfs_expr *ignore_result(const struct opt_state *state, struct bfs_expr *expr) { - int optlevel = state->ctx->optlevel; +static struct bfs_expr *ignore_result(const struct bfs_opt *opt, struct bfs_expr *expr) { + int optlevel = opt->ctx->optlevel; if (optlevel >= 1) { while (true) { if (expr->eval_fn == eval_not) { - opt_debug(state, 1, "ignored result: %pe --> %pe\n", expr, expr->rhs); - opt_warning(state, expr, "The result of this expression is ignored.\n\n"); + opt_debug(opt, 1, "ignored result: %pe --> %pe\n", expr, expr->rhs); + opt_warning(opt, expr, "The result of this expression is ignored.\n\n"); expr = extract_child_expr(expr, &expr->rhs); } else if (optlevel >= 2 && (expr->eval_fn == eval_and || expr->eval_fn == eval_or || expr->eval_fn == eval_comma) && expr->rhs->pure) { - opt_debug(state, 2, "ignored result: %pe --> %pe\n", expr, expr->lhs); - opt_warning(state, expr->rhs, "The result of this expression is ignored.\n\n"); + opt_debug(opt, 2, "ignored result: %pe --> %pe\n", expr, expr->lhs); + opt_warning(opt, expr->rhs, "The result of this expression is ignored.\n\n"); expr = extract_child_expr(expr, &expr->lhs); } else { break; @@ -660,8 +660,8 @@ static struct bfs_expr *ignore_result(const struct opt_state *state, struct bfs_ if (optlevel >= 2 && expr->pure && expr->eval_fn != eval_false) { struct bfs_expr *ret = opt_const(false); - opt_debug(state, 2, "ignored result: %pe --> %pe\n", expr, ret); - opt_warning(state, expr, "The result of this expression is ignored.\n\n"); + opt_debug(opt, 2, "ignored result: %pe --> %pe\n", expr, ret); + opt_warning(opt, expr, "The result of this expression is ignored.\n\n"); bfs_expr_free(expr); return ret; } @@ -671,27 +671,27 @@ static struct bfs_expr *ignore_result(const struct opt_state *state, struct bfs_ } /** Optimize a comma expression. */ -static struct bfs_expr *optimize_comma_expr(const struct opt_state *state, struct bfs_expr *expr) { +static struct bfs_expr *optimize_comma_expr(const struct bfs_opt *opt, struct bfs_expr *expr) { bfs_assert(expr->eval_fn == eval_comma); struct bfs_expr *lhs = expr->lhs; struct bfs_expr *rhs = expr->rhs; - int optlevel = state->ctx->optlevel; + int optlevel = opt->ctx->optlevel; if (optlevel >= 1) { - lhs = expr->lhs = ignore_result(state, lhs); + lhs = expr->lhs = ignore_result(opt, lhs); if (bfs_expr_never_returns(lhs)) { - opt_debug(state, 1, "reachability: %pe <==> %pe\n", expr, lhs); - opt_warning(state, expr->rhs, "This expression is unreachable.\n\n"); + opt_debug(opt, 1, "reachability: %pe <==> %pe\n", expr, lhs); + opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); return extract_child_expr(expr, &expr->lhs); } else if ((lhs->always_true && rhs->eval_fn == eval_true) || (lhs->always_false && rhs->eval_fn == eval_false)) { - opt_debug(state, 1, "redundancy elimination: %pe <==> %pe\n", expr, lhs); + opt_debug(opt, 1, "redundancy elimination: %pe <==> %pe\n", expr, lhs); return extract_child_expr(expr, &expr->lhs); } else if (optlevel >= 2 && lhs->pure) { - opt_debug(state, 2, "purity: %pe <==> %pe\n", expr, rhs); - opt_warning(state, expr->lhs, "The result of this expression is ignored.\n\n"); + opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); + opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); return extract_child_expr(expr, &expr->rhs); } } @@ -706,76 +706,72 @@ static struct bfs_expr *optimize_comma_expr(const struct opt_state *state, struc } /** Optimize a comma expression recursively. */ -static struct bfs_expr *optimize_comma_expr_recursive(struct opt_state *state, struct bfs_expr *expr) { - struct opt_state lhs_state = *state; +static struct bfs_expr *optimize_comma_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { + struct bfs_opt lhs_state = *opt; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { goto fail; } - struct opt_state rhs_state = *state; - facts_union(&rhs_state.facts, &lhs_state.facts_when_true, &lhs_state.facts_when_false); + struct bfs_opt rhs_state = *opt; + rhs_state.before = lhs_state.after_true; + df_join(&rhs_state.before, &lhs_state.after_false); + expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { goto fail; } - return optimize_comma_expr(state, expr); + return optimize_comma_expr(opt, expr); fail: bfs_expr_free(expr); return NULL; } -/** Infer data flow facts about a predicate. */ -static void infer_pred_facts(struct opt_state *state, enum pred_type pred) { - constrain_pred(&state->facts_when_true.preds[pred], true); - constrain_pred(&state->facts_when_false.preds[pred], false); -} - -/** Infer data flow facts about an icmp-style ([+-]N) expression. */ -static void infer_icmp_facts(struct opt_state *state, const struct bfs_expr *expr, enum range_type type) { - struct range *range_when_true = &state->facts_when_true.ranges[type]; - struct range *range_when_false = &state->facts_when_false.ranges[type]; +/** Optimize an icmp-style ([+-]N) expression. */ +static void optimize_icmp(struct bfs_opt *opt, const struct bfs_expr *expr, enum range_type type) { + struct df_range *true_range = &opt->after_true.ranges[type]; + struct df_range *false_range = &opt->after_false.ranges[type]; long long value = expr->num; switch (expr->int_cmp) { case BFS_INT_EQUAL: - constrain_min(range_when_true, value); - constrain_max(range_when_true, value); - range_remove(range_when_false, value); + constrain_min(true_range, value); + constrain_max(true_range, value); + range_remove(false_range, value); break; case BFS_INT_LESS: - constrain_min(range_when_false, value); - constrain_max(range_when_true, value); - range_remove(range_when_true, value); + constrain_min(false_range, value); + constrain_max(true_range, value); + range_remove(true_range, value); break; case BFS_INT_GREATER: - constrain_max(range_when_false, value); - constrain_min(range_when_true, value); - range_remove(range_when_true, value); + constrain_max(false_range, value); + constrain_min(true_range, value); + range_remove(true_range, value); break; } } /** Optimize -{execut,read,writ}able. */ -static struct bfs_expr *optimize_access(struct opt_state *state, struct bfs_expr *expr) { +static struct bfs_expr *optimize_access(struct bfs_opt *opt, struct bfs_expr *expr) { expr->probability = 1.0; if (expr->num & R_OK) { - infer_pred_facts(state, READABLE_PRED); + opt_constrain_pred(opt, READABLE_PRED, true); expr->probability *= 0.99; } if (expr->num & W_OK) { - infer_pred_facts(state, WRITABLE_PRED); + opt_constrain_pred(opt, WRITABLE_PRED, true); expr->probability *= 0.8; } if (expr->num & X_OK) { - infer_pred_facts(state, EXECUTABLE_PRED); + opt_constrain_pred(opt, EXECUTABLE_PRED, true); expr->probability *= 0.2; } @@ -783,8 +779,8 @@ static struct bfs_expr *optimize_access(struct opt_state *state, struct bfs_expr } /** Optimize -empty. */ -static struct bfs_expr *optimize_empty(struct opt_state *state, struct bfs_expr *expr) { - if (state->ctx->optlevel >= 4) { +static struct bfs_expr *optimize_empty(struct bfs_opt *opt, struct bfs_expr *expr) { + if (opt->ctx->optlevel >= 4) { // Since -empty attempts to open and read directories, it may // have side effects such as reporting permission errors, and // thus shouldn't be re-ordered without aggressive optimizations @@ -795,7 +791,7 @@ static struct bfs_expr *optimize_empty(struct opt_state *state, struct bfs_expr } /** Optimize -{exec,ok}{,dir}. */ -static struct bfs_expr *optimize_exec(struct opt_state *state, struct bfs_expr *expr) { +static struct bfs_expr *optimize_exec(struct bfs_opt *opt, struct bfs_expr *expr) { if (expr->exec->flags & BFS_EXEC_MULTI) { expr->always_true = true; } else { @@ -806,7 +802,7 @@ static struct bfs_expr *optimize_exec(struct opt_state *state, struct bfs_expr * } /** Optimize -name/-lname/-path. */ -static struct bfs_expr *optimize_fnmatch(struct opt_state *state, struct bfs_expr *expr) { +static struct bfs_expr *optimize_fnmatch(struct bfs_opt *opt, struct bfs_expr *expr) { if (strchr(expr->argv[1], '*')) { expr->probability = 0.5; } else { @@ -817,13 +813,13 @@ static struct bfs_expr *optimize_fnmatch(struct opt_state *state, struct bfs_exp } /** Optimize -gid. */ -static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *expr) { - struct range *range = &state->facts_when_true.ranges[GID_RANGE]; +static struct bfs_expr *optimize_gid(struct bfs_opt *opt, struct bfs_expr *expr) { + struct df_range *range = &opt->after_true.ranges[GID_RANGE]; if (range->min == range->max) { gid_t gid = range->min; - bool nogroup = !bfs_getgrgid(state->ctx->groups, gid); + bool nogroup = !bfs_getgrgid(opt->ctx->groups, gid); if (errno == 0) { - constrain_pred(&state->facts_when_true.preds[NOGROUP_PRED], nogroup); + opt_constrain_pred(opt, NOGROUP_PRED, nogroup); } } @@ -831,8 +827,8 @@ static struct bfs_expr *optimize_gid(struct opt_state *state, struct bfs_expr *e } /** Optimize -inum. */ -static struct bfs_expr *optimize_inum(struct opt_state *state, struct bfs_expr *expr) { - struct range *range = &state->facts_when_true.ranges[INUM_RANGE]; +static struct bfs_expr *optimize_inum(struct bfs_opt *opt, struct bfs_expr *expr) { + struct df_range *range = &opt->after_true.ranges[INUM_RANGE]; if (range->min == range->max) { expr->probability = 0.01; } else { @@ -843,8 +839,8 @@ static struct bfs_expr *optimize_inum(struct opt_state *state, struct bfs_expr * } /** Optimize -links. */ -static struct bfs_expr *optimize_links(struct opt_state *state, struct bfs_expr *expr) { - struct range *range = &state->facts_when_true.ranges[LINKS_RANGE]; +static struct bfs_expr *optimize_links(struct bfs_opt *opt, struct bfs_expr *expr) { + struct df_range *range = &opt->after_true.ranges[LINKS_RANGE]; if (1 >= range->min && 1 <= range->max) { expr->probability = 0.99; } else { @@ -855,13 +851,13 @@ static struct bfs_expr *optimize_links(struct opt_state *state, struct bfs_expr } /** Optimize -uid. */ -static struct bfs_expr *optimize_uid(struct opt_state *state, struct bfs_expr *expr) { - struct range *range = &state->facts_when_true.ranges[UID_RANGE]; +static struct bfs_expr *optimize_uid(struct bfs_opt *opt, struct bfs_expr *expr) { + struct df_range *range = &opt->after_true.ranges[UID_RANGE]; if (range->min == range->max) { uid_t uid = range->min; - bool nouser = !bfs_getpwuid(state->ctx->users, uid); + bool nouser = !bfs_getpwuid(opt->ctx->users, uid); if (errno == 0) { - constrain_pred(&state->facts_when_true.preds[NOUSER_PRED], nouser); + opt_constrain_pred(opt, NOUSER_PRED, nouser); } } @@ -869,16 +865,16 @@ static struct bfs_expr *optimize_uid(struct opt_state *state, struct bfs_expr *e } /** Optimize -samefile. */ -static struct bfs_expr *optimize_samefile(struct opt_state *state, struct bfs_expr *expr) { - struct range *range_when_true = &state->facts_when_true.ranges[INUM_RANGE]; - constrain_min(range_when_true, expr->ino); - constrain_max(range_when_true, expr->ino); +static struct bfs_expr *optimize_samefile(struct bfs_opt *opt, struct bfs_expr *expr) { + struct df_range *range = &opt->after_true.ranges[INUM_RANGE]; + constrain_min(range, expr->ino); + constrain_max(range, expr->ino); return expr; } /** Optimize -size. */ -static struct bfs_expr *optimize_size(struct opt_state *state, struct bfs_expr *expr) { - struct range *range = &state->facts_when_true.ranges[SIZE_RANGE]; +static struct bfs_expr *optimize_size(struct bfs_opt *opt, struct bfs_expr *expr) { + struct df_range *range = &opt->after_true.ranges[SIZE_RANGE]; if (range->min == range->max) { expr->probability = 0.01; } else { @@ -923,9 +919,9 @@ static void estimate_type_probability(struct bfs_expr *expr) { } /** Optimize -type. */ -static struct bfs_expr *optimize_type(struct opt_state *state, struct bfs_expr *expr) { - state->facts_when_true.types &= expr->num; - state->facts_when_false.types &= ~expr->num; +static struct bfs_expr *optimize_type(struct bfs_opt *opt, struct bfs_expr *expr) { + opt->after_true.types &= expr->num; + opt->after_false.types &= ~expr->num; estimate_type_probability(expr); @@ -933,16 +929,16 @@ static struct bfs_expr *optimize_type(struct opt_state *state, struct bfs_expr * } /** Optimize -xtype. */ -static struct bfs_expr *optimize_xtype(struct opt_state *state, struct bfs_expr *expr) { - if (state->ctx->optlevel >= 4) { +static struct bfs_expr *optimize_xtype(struct bfs_opt *opt, struct bfs_expr *expr) { + if (opt->ctx->optlevel >= 4) { // Since -xtype dereferences symbolic links, it may have side // effects such as reporting permission errors, and thus // shouldn't be re-ordered without aggressive optimizations expr->pure = true; } - state->facts_when_true.xtypes &= expr->num; - state->facts_when_false.xtypes &= ~expr->num; + opt->after_true.xtypes &= expr->num; + opt->after_false.xtypes &= ~expr->num; estimate_type_probability(expr); @@ -1115,7 +1111,7 @@ static const struct { }; /** Signature for custom optimizer functions. */ -typedef struct bfs_expr *bfs_opt_fn(struct opt_state *state, struct bfs_expr *expr); +typedef struct bfs_expr *bfs_opt_fn(struct bfs_opt *opt, struct bfs_expr *expr); /** Table of custom optimizer functions. */ static const struct { @@ -1150,7 +1146,7 @@ static const struct { /** * Look up the appropriate optimizer for an expression and call it. */ -static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs_expr *expr) { +static struct bfs_expr *optimize_expr_lookup(struct bfs_opt *opt, struct bfs_expr *expr) { for (size_t i = 0; i < countof(opt_pure); ++i) { if (opt_pure[i] == expr->eval_fn) { expr->pure = true; @@ -1189,42 +1185,42 @@ static struct bfs_expr *optimize_expr_lookup(struct opt_state *state, struct bfs for (size_t i = 0; i < countof(opt_preds); ++i) { if (opt_preds[i].eval_fn == expr->eval_fn) { - infer_pred_facts(state, opt_preds[i].pred); + opt_constrain_pred(opt, opt_preds[i].pred, true); break; } } for (size_t i = 0; i < countof(opt_ranges); ++i) { if (opt_ranges[i].eval_fn == expr->eval_fn) { - infer_icmp_facts(state, expr, opt_ranges[i].range); + optimize_icmp(opt, expr, opt_ranges[i].range); break; } } for (size_t i = 0; i < countof(opt_fns); ++i) { if (opt_fns[i].eval_fn == expr->eval_fn) { - return opt_fns[i].opt_fn(state, expr); + return opt_fns[i].opt_fn(opt, expr); } } return expr; } -static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct bfs_expr *expr) { - int optlevel = state->ctx->optlevel; +static struct bfs_expr *optimize_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { + int optlevel = opt->ctx->optlevel; - state->facts_when_true = state->facts; - state->facts_when_false = state->facts; + opt->after_true = opt->before; + opt->after_false = opt->before; - if (optlevel >= 2 && facts_are_impossible(&state->facts)) { + if (optlevel >= 2 && df_is_bottom(&opt->before)) { struct bfs_expr *ret = opt_const(false); - opt_debug(state, 2, "reachability: %pe --> %pe\n", expr, ret); - opt_warning(state, expr, "This expression is unreachable.\n\n"); + opt_debug(opt, 2, "reachability: %pe --> %pe\n", expr, ret); + opt_warning(opt, expr, "This expression is unreachable.\n\n"); bfs_expr_free(expr); return ret; } - expr = optimize_expr_lookup(state, expr); + expr = optimize_expr_lookup(opt, expr); if (!expr) { return NULL; } @@ -1243,38 +1239,38 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct } } } else if (!expr->pure) { - facts_union(state->facts_when_impure, state->facts_when_impure, &state->facts); + df_join(opt->impure, &opt->before); } if (expr->always_true) { expr->probability = 1.0; - set_facts_impossible(&state->facts_when_false); + df_init_bottom(&opt->after_false); } if (expr->always_false) { expr->probability = 0.0; - set_facts_impossible(&state->facts_when_true); + df_init_bottom(&opt->after_true); } if (optlevel < 2 || expr->eval_fn == eval_true || expr->eval_fn == eval_false) { return expr; } - if (facts_are_impossible(&state->facts_when_true)) { + if (df_is_bottom(&opt->after_true)) { if (expr->pure) { struct bfs_expr *ret = opt_const(false); - opt_warning(state, expr, "This expression is always false.\n\n"); - opt_debug(state, 2, "data flow: %pe --> %pe\n", expr, ret); + opt_warning(opt, expr, "This expression is always false.\n\n"); + opt_debug(opt, 2, "data flow: %pe --> %pe\n", expr, ret); bfs_expr_free(expr); return ret; } else { expr->always_false = true; expr->probability = 0.0; } - } else if (facts_are_impossible(&state->facts_when_false)) { + } else if (df_is_bottom(&opt->after_false)) { if (expr->pure) { struct bfs_expr *ret = opt_const(true); - opt_debug(state, 2, "data flow: %pe --> %pe\n", expr, ret); - opt_warning(state, expr, "This expression is always true.\n\n"); + opt_debug(opt, 2, "data flow: %pe --> %pe\n", expr, ret); + opt_warning(opt, expr, "This expression is always true.\n\n"); bfs_expr_free(expr); return ret; } else { @@ -1287,14 +1283,14 @@ static struct bfs_expr *optimize_expr_recursive(struct opt_state *state, struct } /** Swap the children of a binary expression if it would reduce the cost. */ -static bool reorder_expr(const struct opt_state *state, struct bfs_expr *expr, float swapped_cost) { +static bool reorder_expr(const struct bfs_opt *opt, struct bfs_expr *expr, float swapped_cost) { if (swapped_cost < expr->cost) { - bool debug = opt_debug(state, 3, "cost: %pe <==> ", expr); + bool debug = opt_debug(opt, 3, "cost: %pe <==> ", expr); struct bfs_expr *lhs = expr->lhs; expr->lhs = expr->rhs; expr->rhs = lhs; if (debug) { - cfprintf(state->ctx->cerr, "%pe (~${ylw}%g${rs} --> ~${ylw}%g${rs})\n", expr, expr->cost, swapped_cost); + cfprintf(opt->ctx->cerr, "%pe (~${ylw}%g${rs} --> ~${ylw}%g${rs})\n", expr, expr->cost, swapped_cost); } expr->cost = swapped_cost; return true; @@ -1311,7 +1307,7 @@ static bool reorder_expr(const struct opt_state *state, struct bfs_expr *expr, f * @return * Whether any subexpression was reordered. */ -static bool reorder_expr_recursive(const struct opt_state *state, struct bfs_expr *expr) { +static bool reorder_expr_recursive(const struct bfs_opt *opt, struct bfs_expr *expr) { if (!bfs_expr_is_parent(expr)) { return false; } @@ -1321,17 +1317,17 @@ static bool reorder_expr_recursive(const struct opt_state *state, struct bfs_exp bool ret = false; if (lhs) { - ret |= reorder_expr_recursive(state, lhs); + ret |= reorder_expr_recursive(opt, lhs); } if (rhs) { - ret |= reorder_expr_recursive(state, rhs); + ret |= reorder_expr_recursive(opt, rhs); } if (expr->eval_fn == eval_and || expr->eval_fn == eval_or) { if (lhs->pure && rhs->pure) { float rhs_prob = expr->eval_fn == eval_and ? rhs->probability : 1.0 - rhs->probability; float swapped_cost = rhs->cost + rhs_prob * lhs->cost; - ret |= reorder_expr(state, expr, swapped_cost); + ret |= reorder_expr(opt, expr, swapped_cost); } } @@ -1341,18 +1337,18 @@ static bool reorder_expr_recursive(const struct opt_state *state, struct bfs_exp /** * Optimize a top-level expression. */ -static struct bfs_expr *optimize_expr(struct opt_state *state, struct bfs_expr *expr) { - struct opt_facts saved_impure = *state->facts_when_impure; +static struct bfs_expr *optimize_expr(struct bfs_opt *opt, struct bfs_expr *expr) { + struct df_domain saved_impure = *opt->impure; - expr = optimize_expr_recursive(state, expr); + expr = optimize_expr_recursive(opt, expr); if (!expr) { return NULL; } - if (state->ctx->optlevel >= 3 && reorder_expr_recursive(state, expr)) { + if (opt->ctx->optlevel >= 3 && reorder_expr_recursive(opt, expr)) { // Re-do optimizations to account for the new ordering - *state->facts_when_impure = saved_impure; - expr = optimize_expr_recursive(state, expr); + *opt->impure = saved_impure; + expr = optimize_expr_recursive(opt, expr); if (!expr) { return NULL; } @@ -1364,41 +1360,41 @@ static struct bfs_expr *optimize_expr(struct opt_state *state, struct bfs_expr * int bfs_optimize(struct bfs_ctx *ctx) { bfs_ctx_dump(ctx, DEBUG_OPT); - struct opt_facts facts_when_impure; - set_facts_impossible(&facts_when_impure); + struct df_domain impure; + df_init_bottom(&impure); - struct opt_state state = { + struct bfs_opt opt = { .ctx = ctx, - .facts_when_impure = &facts_when_impure, + .impure = &impure, }; - facts_init(&state.facts); + df_init_top(&opt.before); - ctx->exclude = optimize_expr(&state, ctx->exclude); + ctx->exclude = optimize_expr(&opt, ctx->exclude); if (!ctx->exclude) { return -1; } // Only non-excluded files are evaluated - state.facts = state.facts_when_false; + opt.before = opt.after_false; - struct range *depth = &state.facts.ranges[DEPTH_RANGE]; + struct df_range *depth = &opt.before.ranges[DEPTH_RANGE]; constrain_min(depth, ctx->mindepth); constrain_max(depth, ctx->maxdepth); - ctx->expr = optimize_expr(&state, ctx->expr); + ctx->expr = optimize_expr(&opt, ctx->expr); if (!ctx->expr) { return -1; } - ctx->expr = ignore_result(&state, ctx->expr); + ctx->expr = ignore_result(&opt, ctx->expr); - if (facts_are_impossible(&facts_when_impure)) { + if (df_is_bottom(&impure)) { bfs_warning(ctx, "This command won't do anything.\n\n"); } - const struct range *depth_when_impure = &facts_when_impure.ranges[DEPTH_RANGE]; - long long mindepth = depth_when_impure->min; - long long maxdepth = depth_when_impure->max; + const struct df_range *impure_depth = &impure.ranges[DEPTH_RANGE]; + long long mindepth = impure_depth->min; + long long maxdepth = impure_depth->max; int optlevel = ctx->optlevel; @@ -1407,7 +1403,7 @@ int bfs_optimize(struct bfs_ctx *ctx) { mindepth = INT_MAX; } ctx->mindepth = mindepth; - opt_debug(&state, 2, "data flow: mindepth --> %d\n", ctx->mindepth); + opt_debug(&opt, 2, "data flow: mindepth --> %d\n", ctx->mindepth); } if (optlevel >= 4 && maxdepth < ctx->maxdepth) { @@ -1415,7 +1411,7 @@ int bfs_optimize(struct bfs_ctx *ctx) { maxdepth = INT_MIN; } ctx->maxdepth = maxdepth; - opt_debug(&state, 4, "data flow: maxdepth --> %d\n", ctx->maxdepth); + opt_debug(&opt, 4, "data flow: maxdepth --> %d\n", ctx->maxdepth); } return 0; -- cgit v1.2.3 From 9c6e4ce18304c395338c7c5b2bac9eb89583a568 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 20 Dec 2023 18:56:50 -0500 Subject: parse: s/parser_state/bfs_parser/ --- src/parse.c | 952 ++++++++++++++++++++++++++++++------------------------------ 1 file changed, 476 insertions(+), 476 deletions(-) diff --git a/src/parse.c b/src/parse.c index 2d7b3a9..18a1064 100644 --- a/src/parse.c +++ b/src/parse.c @@ -108,9 +108,9 @@ enum use_color { }; /** - * Ephemeral state for parsing the command line. + * Command line parser state. */ -struct parser_state { +struct bfs_parser { /** The command line being constructed. */ struct bfs_ctx *ctx; /** The command line arguments being parsed. */ @@ -178,8 +178,8 @@ enum token_type { /** * Print a low-level error message during parsing. */ -static void parse_perror(const struct parser_state *state, const char *str) { - bfs_perror(state->ctx, str); +static void parse_perror(const struct bfs_parser *parser, const char *str) { + bfs_perror(parser->ctx, str); } /** Initialize an empty highlighted range. */ @@ -202,19 +202,19 @@ static void highlight_args(const struct bfs_ctx *ctx, char **argv, size_t argc, * Print an error message during parsing. */ attr(printf(2, 3)) -static void parse_error(const struct parser_state *state, const char *format, ...) { +static void parse_error(const struct bfs_parser *parser, const char *format, ...) { int error = errno; - const struct bfs_ctx *ctx = state->ctx; + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); - highlight_args(ctx, state->argv, 1, highlight); + highlight_args(ctx, parser->argv, 1, highlight); bfs_argv_error(ctx, highlight); va_list args; va_start(args, format); errno = error; - bfs_verror(state->ctx, format, args); + bfs_verror(parser->ctx, format, args); va_end(args); } @@ -222,9 +222,9 @@ static void parse_error(const struct parser_state *state, const char *format, .. * Print an error about some command line arguments. */ attr(printf(4, 5)) -static void parse_argv_error(const struct parser_state *state, char **argv, size_t argc, const char *format, ...) { +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 = state->ctx; + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); @@ -242,9 +242,9 @@ static void parse_argv_error(const struct parser_state *state, char **argv, size * Print an error about conflicting command line arguments. */ attr(printf(6, 7)) -static void parse_conflict_error(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { +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 = state->ctx; + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); @@ -263,9 +263,9 @@ static void parse_conflict_error(const struct parser_state *state, char **argv1, * Print an error about an expression. */ attr(printf(3, 4)) -static void parse_expr_error(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { +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 = state->ctx; + const struct bfs_ctx *ctx = parser->ctx; bfs_expr_error(ctx, expr); @@ -280,13 +280,13 @@ static void parse_expr_error(const struct parser_state *state, const struct bfs_ * Print a warning message during parsing. */ attr(printf(2, 3)) -static bool parse_warning(const struct parser_state *state, const char *format, ...) { +static bool parse_warning(const struct bfs_parser *parser, const char *format, ...) { int error = errno; - const struct bfs_ctx *ctx = state->ctx; + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); - highlight_args(ctx, state->argv, 1, highlight); + highlight_args(ctx, parser->argv, 1, highlight); if (!bfs_argv_warning(ctx, highlight)) { return false; } @@ -294,7 +294,7 @@ static bool parse_warning(const struct parser_state *state, const char *format, va_list args; va_start(args, format); errno = error; - bool ret = bfs_vwarning(state->ctx, format, args); + bool ret = bfs_vwarning(parser->ctx, format, args); va_end(args); return ret; } @@ -303,9 +303,9 @@ static bool parse_warning(const struct parser_state *state, const char *format, * Print a warning about conflicting command line arguments. */ attr(printf(6, 7)) -static bool parse_conflict_warning(const struct parser_state *state, char **argv1, size_t argc1, char **argv2, size_t argc2, const char *format, ...) { +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 = state->ctx; + const struct bfs_ctx *ctx = parser->ctx; bool highlight[ctx->argc]; init_highlight(ctx, highlight); @@ -327,9 +327,9 @@ static bool parse_conflict_warning(const struct parser_state *state, char **argv * Print a warning about an expression. */ attr(printf(3, 4)) -static bool parse_expr_warning(const struct parser_state *state, const struct bfs_expr *expr, const char *format, ...) { +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 = state->ctx; + const struct bfs_ctx *ctx = parser->ctx; if (!bfs_expr_warning(ctx, expr)) { return false; @@ -346,16 +346,16 @@ static bool parse_expr_warning(const struct parser_state *state, const struct bf /** * Fill in a "-print"-type expression. */ -static void init_print_expr(struct parser_state *state, struct bfs_expr *expr) { - expr->cfile = state->ctx->cout; +static void init_print_expr(struct bfs_parser *parser, struct bfs_expr *expr) { + expr->cfile = parser->ctx->cout; expr->path = NULL; } /** * Open a file for an expression. */ -static int expr_open(struct parser_state *state, struct bfs_expr *expr, const char *path) { - struct bfs_ctx *ctx = state->ctx; +static int expr_open(struct bfs_parser *parser, struct bfs_expr *expr, const char *path) { + struct bfs_ctx *ctx = parser->ctx; FILE *file = NULL; CFILE *cfile = NULL; @@ -365,7 +365,7 @@ static int expr_open(struct parser_state *state, struct bfs_expr *expr, const ch goto fail; } - cfile = cfwrap(file, state->use_color ? ctx->colors : NULL, true); + cfile = cfwrap(file, parser->use_color ? ctx->colors : NULL, true); if (!cfile) { goto fail; } @@ -384,7 +384,7 @@ static int expr_open(struct parser_state *state, struct bfs_expr *expr, const ch return 0; fail: - parse_expr_error(state, expr, "%m.\n"); + parse_expr_error(parser, expr, "%m.\n"); if (cfile) { cfclose(cfile); } else if (file) { @@ -396,15 +396,15 @@ fail: /** * Invoke bfs_stat() on an argument. */ -static int stat_arg(const struct parser_state *state, char **arg, struct bfs_stat *sb) { - const struct bfs_ctx *ctx = state->ctx; +static int stat_arg(const struct bfs_parser *parser, char **arg, struct bfs_stat *sb) { + const struct bfs_ctx *ctx = parser->ctx; bool follow = ctx->flags & (BFTW_FOLLOW_ROOTS | BFTW_FOLLOW_ALL); enum bfs_stat_flags flags = follow ? BFS_STAT_TRYFOLLOW : BFS_STAT_NOFOLLOW; int ret = bfs_stat(AT_FDCWD, *arg, flags, sb); if (ret != 0) { - parse_argv_error(state, arg, 1, "%m.\n"); + parse_argv_error(parser, arg, 1, "%m.\n"); } return ret; } @@ -412,53 +412,53 @@ static int stat_arg(const struct parser_state *state, char **arg, struct bfs_sta /** * Parse the expression specified on the command line. */ -static struct bfs_expr *parse_expr(struct parser_state *state); +static struct bfs_expr *parse_expr(struct bfs_parser *parser); /** * Advance by a single token. */ -static char **parser_advance(struct parser_state *state, enum token_type type, size_t argc) { +static char **parser_advance(struct bfs_parser *parser, enum token_type type, size_t argc) { if (type != T_FLAG && type != T_PATH) { - state->expr_started = true; + parser->expr_started = true; } if (type != T_PATH) { - state->last_arg = state->argv; + parser->last_arg = parser->argv; } - char **argv = state->argv; - state->argv += argc; + char **argv = parser->argv; + parser->argv += argc; return argv; } /** * Parse a root path. */ -static int parse_root(struct parser_state *state, const char *path) { - struct bfs_ctx *ctx = state->ctx; +static int parse_root(struct bfs_parser *parser, const char *path) { + struct bfs_ctx *ctx = parser->ctx; const char **root = RESERVE(const char *, &ctx->paths, &ctx->npaths); if (!root) { - parse_perror(state, "RESERVE()"); + parse_perror(parser, "RESERVE()"); return -1; } *root = strdup(path); if (!*root) { --ctx->npaths; - parse_perror(state, "strdup()"); + parse_perror(parser, "strdup()"); return -1; } - state->implicit_root = false; + parser->implicit_root = false; return 0; } /** * While parsing an expression, skip any paths and add them to ctx->paths. */ -static int skip_paths(struct parser_state *state) { +static int skip_paths(struct bfs_parser *parser) { while (true) { - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { return 0; } @@ -468,7 +468,7 @@ static int skip_paths(struct parser_state *state) { // find uses -- to separate flags from the rest // of the command line. We allow mixing flags // and paths/predicates, so we just ignore --. - parser_advance(state, T_FLAG, 1); + parser_advance(parser, T_FLAG, 1); continue; } if (strcmp(arg, "-") != 0) { @@ -483,7 +483,7 @@ static int skip_paths(struct parser_state *state) { return 0; } - if (state->expr_started) { + if (parser->expr_started) { // By POSIX, these can be paths. We only treat them as // such at the beginning of the command line. if (strcmp(arg, ")") == 0 || strcmp(arg, ",") == 0) { @@ -491,16 +491,16 @@ static int skip_paths(struct parser_state *state) { } } - if (state->excluding) { - parse_warning(state, "This path will not be excluded. Use a test like ${blu}-name${rs} or ${blu}-path${rs}\n"); - bfs_warning(state->ctx, "within ${red}-exclude${rs} to exclude matching files.\n\n"); + if (parser->excluding) { + parse_warning(parser, "This path will not be excluded. Use a test like ${blu}-name${rs} or ${blu}-path${rs}\n"); + bfs_warning(parser->ctx, "within ${red}-exclude${rs} to exclude matching files.\n\n"); } - if (parse_root(state, arg) != 0) { + if (parse_root(parser, arg) != 0) { return -1; } - parser_advance(state, T_PATH, 1); + parser_advance(parser, T_PATH, 1); } } @@ -519,7 +519,7 @@ enum int_flags { /** * Parse an integer. */ -static const char *parse_int(const struct parser_state *state, char **arg, const char *str, void *result, enum int_flags flags) { +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; @@ -584,19 +584,19 @@ static const char *parse_int(const struct parser_state *state, char **arg, const bad: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "${bld}%pq${rs} is not a valid integer.\n", str); + parse_argv_error(parser, arg, 1, "${bld}%pq${rs} is not a valid integer.\n", str); } return NULL; negative: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "Negative integer ${bld}%pq${rs} is not allowed here.\n", str); + parse_argv_error(parser, arg, 1, "Negative integer ${bld}%pq${rs} is not allowed here.\n", str); } return NULL; range: if (!(flags & IF_QUIET)) { - parse_argv_error(state, arg, 1, "${bld}%pq${rs} is too large an integer.\n", str); + parse_argv_error(parser, arg, 1, "${bld}%pq${rs} is too large an integer.\n", str); } return NULL; } @@ -604,7 +604,7 @@ range: /** * Parse an integer and a comparison flag. */ -static const char *parse_icmp(const struct parser_state *state, struct bfs_expr *expr, enum int_flags flags) { +static const char *parse_icmp(const struct bfs_parser *parser, struct bfs_expr *expr, enum int_flags flags) { char **arg = &expr->argv[1]; const char *str = *arg; switch (str[0]) { @@ -621,7 +621,7 @@ static const char *parse_icmp(const struct parser_state *state, struct bfs_expr break; } - return parse_int(state, arg, str, &expr->num, flags | IF_LONG_LONG | IF_UNSIGNED); + return parse_int(parser, arg, str, &expr->num, flags | IF_LONG_LONG | IF_UNSIGNED); } /** @@ -643,103 +643,103 @@ static bool looks_like_icmp(const char *str) { /** * Parse a single flag. */ -static struct bfs_expr *parse_flag(struct parser_state *state, size_t argc) { - char **argv = parser_advance(state, T_FLAG, argc); +static struct bfs_expr *parse_flag(struct bfs_parser *parser, size_t argc) { + char **argv = parser_advance(parser, T_FLAG, argc); return bfs_expr_new(eval_true, argc, argv); } /** * Parse a flag that doesn't take a value. */ -static struct bfs_expr *parse_nullary_flag(struct parser_state *state) { - return parse_flag(state, 1); +static struct bfs_expr *parse_nullary_flag(struct bfs_parser *parser) { + return parse_flag(parser, 1); } /** * Parse a flag that takes a value. */ -static struct bfs_expr *parse_unary_flag(struct parser_state *state) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; +static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) { + const char *arg = parser->argv[0]; + const char *value = parser->argv[1]; if (!value) { - parse_error(state, "${cyn}%s${rs} needs a value.\n", arg); + parse_error(parser, "${cyn}%s${rs} needs a value.\n", arg); return NULL; } - return parse_flag(state, 2); + return parse_flag(parser, 2); } /** * Parse a single option. */ -static struct bfs_expr *parse_option(struct parser_state *state, size_t argc) { - char **argv = parser_advance(state, T_OPTION, argc); +static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) { + char **argv = parser_advance(parser, T_OPTION, argc); return bfs_expr_new(eval_true, argc, argv); } /** * Parse an option that doesn't take a value. */ -static struct bfs_expr *parse_nullary_option(struct parser_state *state) { - return parse_option(state, 1); +static struct bfs_expr *parse_nullary_option(struct bfs_parser *parser) { + return parse_option(parser, 1); } /** * Parse an option that takes a value. */ -static struct bfs_expr *parse_unary_option(struct parser_state *state) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; +static struct bfs_expr *parse_unary_option(struct bfs_parser *parser) { + const char *arg = parser->argv[0]; + const char *value = parser->argv[1]; if (!value) { - parse_error(state, "${blu}%s${rs} needs a value.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; } - return parse_option(state, 2); + return parse_option(parser, 2); } /** * Parse a single test. */ -static struct bfs_expr *parse_test(struct parser_state *state, bfs_eval_fn *eval_fn, size_t argc) { - char **argv = parser_advance(state, T_TEST, argc); +static struct bfs_expr *parse_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { + char **argv = parser_advance(parser, T_TEST, argc); return bfs_expr_new(eval_fn, argc, argv); } /** * Parse a test that doesn't take a value. */ -static struct bfs_expr *parse_nullary_test(struct parser_state *state, bfs_eval_fn *eval_fn) { - return parse_test(state, eval_fn, 1); +static struct bfs_expr *parse_nullary_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + return parse_test(parser, eval_fn, 1); } /** * Parse a test that takes a value. */ -static struct bfs_expr *parse_unary_test(struct parser_state *state, bfs_eval_fn *eval_fn) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; +static struct bfs_expr *parse_unary_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + const char *arg = parser->argv[0]; + const char *value = parser->argv[1]; if (!value) { - parse_error(state, "${blu}%s${rs} needs a value.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; } - return parse_test(state, eval_fn, 2); + return parse_test(parser, eval_fn, 2); } /** * Parse a single action. */ -static struct bfs_expr *parse_action(struct parser_state *state, bfs_eval_fn *eval_fn, size_t argc) { - char **argv = parser_advance(state, T_ACTION, argc); +static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { + char **argv = parser_advance(parser, T_ACTION, argc); - if (state->excluding) { - parse_argv_error(state, argv, argc, "This action is not supported within ${red}-exclude${rs}.\n"); + if (parser->excluding) { + parse_argv_error(parser, argv, argc, "This action is not supported within ${red}-exclude${rs}.\n"); return NULL; } if (eval_fn != eval_prune && eval_fn != eval_quit) { - state->implicit_print = false; + parser->implicit_print = false; } return bfs_expr_new(eval_fn, argc, argv); @@ -748,29 +748,29 @@ static struct bfs_expr *parse_action(struct parser_state *state, bfs_eval_fn *ev /** * Parse an action that takes no arguments. */ -static struct bfs_expr *parse_nullary_action(struct parser_state *state, bfs_eval_fn *eval_fn) { - return parse_action(state, eval_fn, 1); +static struct bfs_expr *parse_nullary_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + return parse_action(parser, eval_fn, 1); } /** * Parse an action that takes one argument. */ -static struct bfs_expr *parse_unary_action(struct parser_state *state, bfs_eval_fn *eval_fn) { - const char *arg = state->argv[0]; - const char *value = state->argv[1]; +static struct bfs_expr *parse_unary_action(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + const char *arg = parser->argv[0]; + const char *value = parser->argv[1]; if (!value) { - parse_error(state, "${blu}%s${rs} needs a value.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a value.\n", arg); return NULL; } - return parse_action(state, eval_fn, 2); + return parse_action(parser, eval_fn, 2); } /** * Add an expression to the exclusions. */ -static int parse_exclude(struct parser_state *state, struct bfs_expr *expr) { - struct bfs_ctx *ctx = state->ctx; +static int parse_exclude(struct bfs_parser *parser, struct bfs_expr *expr) { + struct bfs_ctx *ctx = parser->ctx; ctx->exclude = new_binary_expr(eval_or, ctx->exclude, expr, &fake_or_arg); if (ctx->exclude) { return 0; @@ -782,13 +782,13 @@ static int parse_exclude(struct parser_state *state, struct bfs_expr *expr) { /** * Parse a test expression with integer data and a comparison flag. */ -static struct bfs_expr *parse_test_icmp(struct parser_state *state, bfs_eval_fn *eval_fn) { - struct bfs_expr *expr = parse_unary_test(state, eval_fn); +static struct bfs_expr *parse_test_icmp(struct bfs_parser *parser, bfs_eval_fn *eval_fn) { + struct bfs_expr *expr = parse_unary_test(parser, eval_fn); if (!expr) { return NULL; } - if (!parse_icmp(state, expr, 0)) { + if (!parse_icmp(parser, expr, 0)) { bfs_expr_free(expr); return NULL; } @@ -825,10 +825,10 @@ static bool parse_debug_flag(const char *flag, size_t len, const char *expected) /** * Parse -D FLAG. */ -static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int arg2) { - struct bfs_ctx *ctx = state->ctx; +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(state); + struct bfs_expr *expr = parse_unary_flag(parser); if (!expr) { cfprintf(ctx->cerr, "\n"); debug_help(ctx->cerr); @@ -847,7 +847,7 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar if (parse_debug_flag(flag, len, "help")) { debug_help(ctx->cout); - state->just_info = true; + parser->just_info = true; bfs_expr_free(expr); return NULL; } else if (parse_debug_flag(flag, len, "all")) { @@ -866,7 +866,7 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar if (DEBUG_ALL & i) { ctx->debug |= i; } else { - if (parse_expr_warning(state, expr, "Unrecognized debug flag ${bld}")) { + if (parse_expr_warning(parser, expr, "Unrecognized debug flag ${bld}")) { fwrite(flag, 1, len, stderr); cfprintf(ctx->cerr, "${rs}.\n\n"); unrecognized = true; @@ -885,23 +885,23 @@ static struct bfs_expr *parse_debug(struct parser_state *state, int arg1, int ar /** * Parse -On. */ -static struct bfs_expr *parse_optlevel(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_flag(state); +static struct bfs_expr *parse_optlevel(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_flag(parser); if (!expr) { return NULL; } - int *optlevel = &state->ctx->optlevel; + int *optlevel = &parser->ctx->optlevel; if (strcmp(expr->argv[0], "-Ofast") == 0) { *optlevel = 4; - } else if (!parse_int(state, expr->argv, expr->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { + } else if (!parse_int(parser, expr->argv, expr->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { bfs_expr_free(expr); return NULL; } if (*optlevel > 4) { - parse_expr_warning(state, 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", expr->argv[0] + 2); } return expr; @@ -910,30 +910,30 @@ static struct bfs_expr *parse_optlevel(struct parser_state *state, int arg1, int /** * Parse -[PHL], -follow. */ -static struct bfs_expr *parse_follow(struct parser_state *state, int flags, int option) { - struct bfs_ctx *ctx = state->ctx; +static struct bfs_expr *parse_follow(struct bfs_parser *parser, int flags, int option) { + struct bfs_ctx *ctx = parser->ctx; ctx->flags &= ~(BFTW_FOLLOW_ROOTS | BFTW_FOLLOW_ALL); ctx->flags |= flags; if (option) { - return parse_nullary_option(state); + return parse_nullary_option(parser); } else { - return parse_nullary_flag(state); + return parse_nullary_flag(parser); } } /** * Parse -X. */ -static struct bfs_expr *parse_xargs_safe(struct parser_state *state, int arg1, int arg2) { - state->ctx->xargs_safe = true; - return parse_nullary_flag(state); +static struct bfs_expr *parse_xargs_safe(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->xargs_safe = true; + return parse_nullary_flag(parser); } /** * Parse -executable, -readable, -writable */ -static struct bfs_expr *parse_access(struct parser_state *state, int flag, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_access); +static struct bfs_expr *parse_access(struct bfs_parser *parser, int flag, int arg2) { + struct bfs_expr *expr = parse_nullary_test(parser, eval_access); if (expr) { expr->num = flag; } @@ -943,11 +943,11 @@ static struct bfs_expr *parse_access(struct parser_state *state, int flag, int a /** * Parse -acl. */ -static struct bfs_expr *parse_acl(struct parser_state *state, int flag, int arg2) { +static struct bfs_expr *parse_acl(struct bfs_parser *parser, int flag, int arg2) { #if BFS_CAN_CHECK_ACL - return parse_nullary_test(state, eval_acl); + return parse_nullary_test(parser, eval_acl); #else - parse_error(state, "Missing platform support.\n"); + parse_error(parser, "Missing platform support.\n"); return NULL; #endif } @@ -955,14 +955,14 @@ static struct bfs_expr *parse_acl(struct parser_state *state, int flag, int arg2 /** * Parse -[aBcm]?newer. */ -static struct bfs_expr *parse_newer(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_newer); +static struct bfs_expr *parse_newer(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { return NULL; } struct bfs_stat sb; - if (stat_arg(state, &expr->argv[1], &sb) != 0) { + if (stat_arg(parser, &expr->argv[1], &sb) != 0) { goto fail; } @@ -978,13 +978,13 @@ fail: /** * Parse -[aBcm]min. */ -static struct bfs_expr *parse_min(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_test_icmp(state, eval_time); +static struct bfs_expr *parse_min(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_test_icmp(parser, eval_time); if (!expr) { return NULL; } - expr->reftime = state->now; + expr->reftime = parser->now; expr->stat_field = field; expr->time_unit = BFS_MINUTES; return expr; @@ -993,16 +993,16 @@ static struct bfs_expr *parse_min(struct parser_state *state, int field, int arg /** * Parse -[aBcm]time. */ -static struct bfs_expr *parse_time(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_time); +static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_time); if (!expr) { return NULL; } - expr->reftime = state->now; + expr->reftime = parser->now; expr->stat_field = field; - const char *tail = parse_icmp(state, expr, IF_PARTIAL_OK); + const char *tail = parse_icmp(parser, expr, IF_PARTIAL_OK); if (!tail) { goto fail; } @@ -1032,7 +1032,7 @@ static struct bfs_expr *parse_time(struct parser_state *state, int field, int ar case 's': break; default: - parse_expr_error(state, expr, "Unknown time unit ${bld}%c${rs}.\n", *tail); + parse_expr_error(parser, expr, "Unknown time unit ${bld}%c${rs}.\n", *tail); goto fail; } @@ -1042,12 +1042,12 @@ static struct bfs_expr *parse_time(struct parser_state *state, int field, int ar break; } - tail = parse_int(state, &expr->argv[1], tail, &time, IF_PARTIAL_OK | IF_LONG_LONG | IF_UNSIGNED); + tail = parse_int(parser, &expr->argv[1], tail, &time, IF_PARTIAL_OK | IF_LONG_LONG | IF_UNSIGNED); if (!tail) { goto fail; } if (!*tail) { - parse_expr_error(state, expr, "Missing time unit.\n"); + parse_expr_error(parser, expr, "Missing time unit.\n"); goto fail; } } @@ -1063,11 +1063,11 @@ fail: /** * Parse -capable. */ -static struct bfs_expr *parse_capable(struct parser_state *state, int flag, int arg2) { +static struct bfs_expr *parse_capable(struct bfs_parser *parser, int flag, int arg2) { #if BFS_CAN_CHECK_CAPABILITIES - return parse_nullary_test(state, eval_capable); + return parse_nullary_test(parser, eval_capable); #else - parse_error(state, "Missing platform support.\n"); + parse_error(parser, "Missing platform support.\n"); return NULL; #endif } @@ -1075,27 +1075,27 @@ static struct bfs_expr *parse_capable(struct parser_state *state, int flag, int /** * Parse -(no)?color. */ -static struct bfs_expr *parse_color(struct parser_state *state, int color, int arg2) { - struct bfs_expr *expr = parse_nullary_option(state); +static struct bfs_expr *parse_color(struct bfs_parser *parser, int color, int arg2) { + struct bfs_expr *expr = parse_nullary_option(parser); if (!expr) { return NULL; } - struct bfs_ctx *ctx = state->ctx; + struct bfs_ctx *ctx = parser->ctx; struct colors *colors = ctx->colors; if (color) { if (!colors) { - parse_expr_error(state, expr, "Error parsing $$LS_COLORS: %s.\n", xstrerror(ctx->colors_error)); + parse_expr_error(parser, expr, "Error parsing $$LS_COLORS: %s.\n", xstrerror(ctx->colors_error)); bfs_expr_free(expr); return NULL; } - state->use_color = COLOR_ALWAYS; + parser->use_color = COLOR_ALWAYS; ctx->cout->colors = colors; ctx->cerr->colors = colors; } else { - state->use_color = COLOR_NEVER; + parser->use_color = COLOR_NEVER; ctx->cout->colors = NULL; ctx->cerr->colors = NULL; } @@ -1106,21 +1106,21 @@ static struct bfs_expr *parse_color(struct parser_state *state, int color, int a /** * Parse -{false,true}. */ -static struct bfs_expr *parse_const(struct parser_state *state, int value, int arg2) { - return parse_nullary_test(state, value ? eval_true : eval_false); +static struct bfs_expr *parse_const(struct bfs_parser *parser, int value, int arg2) { + return parse_nullary_test(parser, value ? eval_true : eval_false); } /** * Parse -daystart. */ -static struct bfs_expr *parse_daystart(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_daystart(struct bfs_parser *parser, int arg1, int arg2) { struct tm tm; - if (xlocaltime(&state->now.tv_sec, &tm) != 0) { - parse_perror(state, "xlocaltime()"); + if (xlocaltime(&parser->now.tv_sec, &tm) != 0) { + parse_perror(parser, "xlocaltime()"); return NULL; } - if (tm.tm_hour || tm.tm_min || tm.tm_sec || state->now.tv_nsec) { + if (tm.tm_hour || tm.tm_min || tm.tm_sec || parser->now.tv_nsec) { ++tm.tm_mday; } tm.tm_hour = 0; @@ -1129,59 +1129,59 @@ static struct bfs_expr *parse_daystart(struct parser_state *state, int arg1, int time_t time; if (xmktime(&tm, &time) != 0) { - parse_perror(state, "xmktime()"); + parse_perror(parser, "xmktime()"); return NULL; } - state->now.tv_sec = time; - state->now.tv_nsec = 0; + parser->now.tv_sec = time; + parser->now.tv_nsec = 0; - return parse_nullary_option(state); + return parse_nullary_option(parser); } /** * Parse -delete. */ -static struct bfs_expr *parse_delete(struct parser_state *state, int arg1, int arg2) { - state->ctx->flags |= BFTW_POST_ORDER; - state->depth_arg = state->argv; - return parse_nullary_action(state, eval_delete); +static struct bfs_expr *parse_delete(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->flags |= BFTW_POST_ORDER; + parser->depth_arg = parser->argv; + return parse_nullary_action(parser, eval_delete); } /** * Parse -d. */ -static struct bfs_expr *parse_depth(struct parser_state *state, int arg1, int arg2) { - state->ctx->flags |= BFTW_POST_ORDER; - state->depth_arg = state->argv; - return parse_nullary_flag(state); +static struct bfs_expr *parse_depth(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->flags |= BFTW_POST_ORDER; + parser->depth_arg = parser->argv; + return parse_nullary_flag(parser); } /** * Parse -depth [N]. */ -static struct bfs_expr *parse_depth_n(struct parser_state *state, int arg1, int arg2) { - const char *arg = state->argv[1]; +static struct bfs_expr *parse_depth_n(struct bfs_parser *parser, int arg1, int arg2) { + const char *arg = parser->argv[1]; if (arg && looks_like_icmp(arg)) { - return parse_test_icmp(state, eval_depth); + return parse_test_icmp(parser, eval_depth); } else { - return parse_depth(state, arg1, arg2); + return parse_depth(parser, arg1, arg2); } } /** * Parse -{min,max}depth N. */ -static struct bfs_expr *parse_depth_limit(struct parser_state *state, int is_min, int arg2) { - struct bfs_expr *expr = parse_unary_option(state); +static struct bfs_expr *parse_depth_limit(struct bfs_parser *parser, int is_min, int arg2) { + struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { return NULL; } - struct bfs_ctx *ctx = state->ctx; + struct bfs_ctx *ctx = parser->ctx; int *depth = is_min ? &ctx->mindepth : &ctx->maxdepth; char **arg = &expr->argv[1]; - if (!parse_int(state, arg, *arg, depth, IF_INT | IF_UNSIGNED)) { + if (!parse_int(parser, arg, *arg, depth, IF_INT | IF_UNSIGNED)) { bfs_expr_free(expr); return NULL; } @@ -1192,8 +1192,8 @@ static struct bfs_expr *parse_depth_limit(struct parser_state *state, int is_min /** * Parse -empty. */ -static struct bfs_expr *parse_empty(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_empty); +static struct bfs_expr *parse_empty(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_test(parser, eval_empty); if (expr) { // For opendir() expr->ephemeral_fds = 1; @@ -1204,13 +1204,13 @@ static struct bfs_expr *parse_empty(struct parser_state *state, int arg1, int ar /** * Parse -exec(dir)?/-ok(dir)?. */ -static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int arg2) { - struct bfs_exec *execbuf = bfs_exec_parse(state->ctx, state->argv, flags); +static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg2) { + struct bfs_exec *execbuf = bfs_exec_parse(parser->ctx, parser->argv, flags); if (!execbuf) { return NULL; } - struct bfs_expr *expr = parse_action(state, eval_exec, execbuf->tmpl_argc + 2); + struct bfs_expr *expr = parse_action(parser, eval_exec, execbuf->tmpl_argc + 2); if (!expr) { bfs_exec_free(execbuf); return NULL; @@ -1229,11 +1229,11 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar size_t len = strcspn(path, ":"); char *comp = strndup(path, len); if (comp) { - parse_expr_error(state, expr, + 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(state, "strndup()"); + parse_perror(parser, "strndup()"); } goto fail; } @@ -1253,7 +1253,7 @@ static struct bfs_expr *parse_exec(struct parser_state *state, int flags, int ar } if (execbuf->flags & BFS_EXEC_CONFIRM) { - state->ok_expr = expr; + parser->ok_expr = expr; } return expr; @@ -1266,16 +1266,16 @@ fail: /** * Parse -exit [STATUS]. */ -static struct bfs_expr *parse_exit(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_exit(struct bfs_parser *parser, int arg1, int arg2) { size_t argc = 1; - const char *value = state->argv[1]; + const char *value = parser->argv[1]; int status = EXIT_SUCCESS; - if (value && parse_int(state, NULL, value, &status, IF_INT | IF_UNSIGNED | IF_QUIET)) { + if (value && parse_int(parser, NULL, value, &status, IF_INT | IF_UNSIGNED | IF_QUIET)) { argc = 2; } - struct bfs_expr *expr = parse_action(state, eval_exit, argc); + struct bfs_expr *expr = parse_action(parser, eval_exit, argc); if (expr) { expr->num = status; } @@ -1285,13 +1285,13 @@ static struct bfs_expr *parse_exit(struct parser_state *state, int arg1, int arg /** * Parse -f PATH. */ -static struct bfs_expr *parse_f(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_flag(state); +static struct bfs_expr *parse_f(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_flag(parser); if (!expr) { return NULL; } - if (parse_root(state, expr->argv[1]) != 0) { + if (parse_root(parser, expr->argv[1]) != 0) { bfs_expr_free(expr); return NULL; } @@ -1302,8 +1302,8 @@ static struct bfs_expr *parse_f(struct parser_state *state, int arg1, int arg2) /** * Parse -files0-from PATH. */ -static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_option(state); +static struct bfs_expr *parse_files0_from(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { return NULL; } @@ -1317,7 +1317,7 @@ static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, file = xfopen(from, O_RDONLY | O_CLOEXEC); } if (!file) { - parse_expr_error(state, expr, "%m.\n"); + parse_expr_error(parser, expr, "%m.\n"); goto fail; } @@ -1331,7 +1331,7 @@ static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, } } - int ret = parse_root(state, path); + int ret = parse_root(parser, path); free(path); if (ret != 0) { goto fail; @@ -1339,12 +1339,12 @@ static struct bfs_expr *parse_files0_from(struct parser_state *state, int arg1, } if (file == stdin) { - state->files0_stdin_arg = expr->argv; + parser->files0_stdin_arg = expr->argv; } else { fclose(file); } - state->implicit_root = false; + parser->implicit_root = false; return expr; fail: @@ -1358,8 +1358,8 @@ fail: /** * Parse -flags FLAGS. */ -static struct bfs_expr *parse_flags(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_flags); +static struct bfs_expr *parse_flags(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_flags); if (!expr) { return NULL; } @@ -1381,9 +1381,9 @@ static struct bfs_expr *parse_flags(struct parser_state *state, int arg1, int ar if (xstrtofflags(&flags, &expr->set_flags, &expr->clear_flags) != 0) { if (errno == ENOTSUP) { - parse_expr_error(state, expr, "Missing platform support.\n"); + parse_expr_error(parser, expr, "Missing platform support.\n"); } else { - parse_expr_error(state, expr, "Invalid flags.\n"); + parse_expr_error(parser, expr, "Invalid flags.\n"); } bfs_expr_free(expr); return NULL; @@ -1395,13 +1395,13 @@ static struct bfs_expr *parse_flags(struct parser_state *state, int arg1, int ar /** * Parse -fls FILE. */ -static struct bfs_expr *parse_fls(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_action(state, eval_fls); +static struct bfs_expr *parse_fls(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_fls); if (!expr) { goto fail; } - if (expr_open(state, expr, expr->argv[1]) != 0) { + if (expr_open(parser, expr, expr->argv[1]) != 0) { goto fail; } @@ -1415,10 +1415,10 @@ fail: /** * Parse -fprint FILE. */ -static struct bfs_expr *parse_fprint(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_action(state, eval_fprint); +static struct bfs_expr *parse_fprint(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_fprint); if (expr) { - if (expr_open(state, expr, expr->argv[1]) != 0) { + if (expr_open(parser, expr, expr->argv[1]) != 0) { goto fail; } } @@ -1432,10 +1432,10 @@ fail: /** * Parse -fprint0 FILE. */ -static struct bfs_expr *parse_fprint0(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_action(state, eval_fprint0); +static struct bfs_expr *parse_fprint0(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_fprint0); if (expr) { - if (expr_open(state, expr, expr->argv[1]) != 0) { + if (expr_open(parser, expr, expr->argv[1]) != 0) { goto fail; } } @@ -1449,31 +1449,31 @@ fail: /** * Parse -fprintf FILE FORMAT. */ -static struct bfs_expr *parse_fprintf(struct parser_state *state, int arg1, int arg2) { - const char *arg = state->argv[0]; +static struct bfs_expr *parse_fprintf(struct bfs_parser *parser, int arg1, int arg2) { + const char *arg = parser->argv[0]; - const char *file = state->argv[1]; + const char *file = parser->argv[1]; if (!file) { - parse_error(state, "${blu}%s${rs} needs a file.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a file.\n", arg); return NULL; } - const char *format = state->argv[2]; + const char *format = parser->argv[2]; if (!format) { - parse_error(state, "${blu}%s${rs} needs a format string.\n", arg); + parse_error(parser, "${blu}%s${rs} needs a format string.\n", arg); return NULL; } - struct bfs_expr *expr = parse_action(state, eval_fprintf, 3); + struct bfs_expr *expr = parse_action(parser, eval_fprintf, 3); if (!expr) { return NULL; } - if (expr_open(state, expr, file) != 0) { + if (expr_open(parser, expr, file) != 0) { goto fail; } - if (bfs_printf_parse(state->ctx, expr, format) != 0) { + if (bfs_printf_parse(parser->ctx, expr, format) != 0) { goto fail; } @@ -1487,14 +1487,14 @@ fail: /** * Parse -fstype TYPE. */ -static struct bfs_expr *parse_fstype(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_fstype); +static struct bfs_expr *parse_fstype(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_fstype); if (!expr) { return NULL; } - if (!bfs_ctx_mtab(state->ctx)) { - parse_expr_error(state, expr, "Couldn't parse the mount table: %m.\n"); + if (!bfs_ctx_mtab(parser->ctx)) { + parse_expr_error(parser, expr, "Couldn't parse the mount table: %m.\n"); bfs_expr_free(expr); return NULL; } @@ -1505,25 +1505,25 @@ static struct bfs_expr *parse_fstype(struct parser_state *state, int arg1, int a /** * Parse -gid/-group. */ -static struct bfs_expr *parse_group(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_gid); +static struct bfs_expr *parse_group(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_gid); if (!expr) { return NULL; } - const struct group *grp = bfs_getgrnam(state->ctx->groups, expr->argv[1]); + const struct group *grp = bfs_getgrnam(parser->ctx->groups, expr->argv[1]); if (grp) { expr->num = grp->gr_gid; expr->int_cmp = BFS_INT_EQUAL; } else if (looks_like_icmp(expr->argv[1])) { - if (!parse_icmp(state, expr, 0)) { + if (!parse_icmp(parser, expr, 0)) { goto fail; } } else if (errno) { - parse_expr_error(state, expr, "%m.\n"); + parse_expr_error(parser, expr, "%m.\n"); goto fail; } else { - parse_expr_error(state, expr, "No such group.\n"); + parse_expr_error(parser, expr, "No such group.\n"); goto fail; } @@ -1537,40 +1537,40 @@ fail: /** * Parse -unique. */ -static struct bfs_expr *parse_unique(struct parser_state *state, int arg1, int arg2) { - state->ctx->unique = true; - return parse_nullary_option(state); +static struct bfs_expr *parse_unique(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->unique = true; + return parse_nullary_option(parser); } /** * Parse -used N. */ -static struct bfs_expr *parse_used(struct parser_state *state, int arg1, int arg2) { - return parse_test_icmp(state, eval_used); +static struct bfs_expr *parse_used(struct bfs_parser *parser, int arg1, int arg2) { + return parse_test_icmp(parser, eval_used); } /** * Parse -uid/-user. */ -static struct bfs_expr *parse_user(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_uid); +static struct bfs_expr *parse_user(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_uid); if (!expr) { return NULL; } - const struct passwd *pwd = bfs_getpwnam(state->ctx->users, expr->argv[1]); + const struct passwd *pwd = bfs_getpwnam(parser->ctx->users, expr->argv[1]); if (pwd) { expr->num = pwd->pw_uid; expr->int_cmp = BFS_INT_EQUAL; } else if (looks_like_icmp(expr->argv[1])) { - if (!parse_icmp(state, expr, 0)) { + if (!parse_icmp(parser, expr, 0)) { goto fail; } } else if (errno) { - parse_expr_error(state, expr, "%m.\n"); + parse_expr_error(parser, expr, "%m.\n"); goto fail; } else { - parse_expr_error(state, expr, "No such user.\n"); + parse_expr_error(parser, expr, "No such user.\n"); goto fail; } @@ -1584,91 +1584,91 @@ fail: /** * Parse -hidden. */ -static struct bfs_expr *parse_hidden(struct parser_state *state, int arg1, int arg2) { - return parse_nullary_test(state, eval_hidden); +static struct bfs_expr *parse_hidden(struct bfs_parser *parser, int arg1, int arg2) { + return parse_nullary_test(parser, eval_hidden); } /** * Parse -(no)?ignore_readdir_race. */ -static struct bfs_expr *parse_ignore_races(struct parser_state *state, int ignore, int arg2) { - state->ctx->ignore_races = ignore; - return parse_nullary_option(state); +static struct bfs_expr *parse_ignore_races(struct bfs_parser *parser, int ignore, int arg2) { + parser->ctx->ignore_races = ignore; + return parse_nullary_option(parser); } /** * Parse -inum N. */ -static struct bfs_expr *parse_inum(struct parser_state *state, int arg1, int arg2) { - return parse_test_icmp(state, eval_inum); +static struct bfs_expr *parse_inum(struct bfs_parser *parser, int arg1, int arg2) { + return parse_test_icmp(parser, eval_inum); } /** * Parse -j. */ -static struct bfs_expr *parse_jobs(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_flag(state); +static struct bfs_expr *parse_jobs(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_flag(parser); if (!expr) { return NULL; } unsigned int n; - if (!parse_int(state, expr->argv, expr->argv[0] + 2, &n, IF_INT | IF_UNSIGNED)) { + if (!parse_int(parser, expr->argv, expr->argv[0] + 2, &n, IF_INT | IF_UNSIGNED)) { bfs_expr_free(expr); return NULL; } if (n == 0) { - parse_expr_error(state, expr, "${bld}0${rs} is not enough threads.\n"); + parse_expr_error(parser, expr, "${bld}0${rs} is not enough threads.\n"); bfs_expr_free(expr); return NULL; } - state->ctx->threads = n; + parser->ctx->threads = n; return expr; } /** * Parse -links N. */ -static struct bfs_expr *parse_links(struct parser_state *state, int arg1, int arg2) { - return parse_test_icmp(state, eval_links); +static struct bfs_expr *parse_links(struct bfs_parser *parser, int arg1, int arg2) { + return parse_test_icmp(parser, eval_links); } /** * Parse -ls. */ -static struct bfs_expr *parse_ls(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_fls); +static struct bfs_expr *parse_ls(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_fls); if (!expr) { return NULL; } - init_print_expr(state, expr); + init_print_expr(parser, expr); return expr; } /** * Parse -mount. */ -static struct bfs_expr *parse_mount(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_option(state); +static struct bfs_expr *parse_mount(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_option(parser); if (!expr) { return NULL; } - parse_expr_warning(state, expr, "In the future, ${blu}%s${rs} will skip mount points entirely, unlike\n", expr->argv[0]); - bfs_warning(state->ctx, "${blu}-xdev${rs}, due to http://austingroupbugs.net/view.php?id=1133.\n\n"); + parse_expr_warning(parser, expr, "In the future, ${blu}%s${rs} will skip mount points entirely, unlike\n", expr->argv[0]); + bfs_warning(parser->ctx, "${blu}-xdev${rs}, due to http://austingroupbugs.net/view.php?id=1133.\n\n"); - state->ctx->flags |= BFTW_PRUNE_MOUNTS; - state->mount_arg = expr->argv; + parser->ctx->flags |= BFTW_PRUNE_MOUNTS; + parser->mount_arg = expr->argv; return expr; } /** * Common code for fnmatch() tests. */ -static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct bfs_expr *expr, bool casefold) { +static struct bfs_expr *parse_fnmatch(const struct bfs_parser *parser, struct bfs_expr *expr, bool casefold) { if (!expr) { return NULL; } @@ -1679,7 +1679,7 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b #ifdef FNM_CASEFOLD expr->fnm_flags = FNM_CASEFOLD; #else - parse_expr_error(state, expr, "Missing platform support.\n"); + parse_expr_error(parser, expr, "Missing platform support.\n"); bfs_expr_free(expr); return NULL; #endif @@ -1700,7 +1700,7 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b } } if (i % 2 != 0) { - parse_expr_warning(state, expr, "Unescaped trailing backslash.\n\n"); + parse_expr_warning(parser, expr, "Unescaped trailing backslash.\n\n"); expr->eval_fn = eval_false; return expr; } @@ -1717,25 +1717,25 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b /** * Parse -i?name. */ -static struct bfs_expr *parse_name(struct parser_state *state, int casefold, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_name); - return parse_fnmatch(state, expr, casefold); +static struct bfs_expr *parse_name(struct bfs_parser *parser, int casefold, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_name); + return parse_fnmatch(parser, expr, casefold); } /** * Parse -i?path, -i?wholename. */ -static struct bfs_expr *parse_path(struct parser_state *state, int casefold, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_path); - return parse_fnmatch(state, expr, casefold); +static struct bfs_expr *parse_path(struct bfs_parser *parser, int casefold, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_path); + return parse_fnmatch(parser, expr, casefold); } /** * Parse -i?lname. */ -static struct bfs_expr *parse_lname(struct parser_state *state, int casefold, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_lname); - return parse_fnmatch(state, expr, casefold); +static struct bfs_expr *parse_lname(struct bfs_parser *parser, int casefold, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_lname); + return parse_fnmatch(parser, expr, casefold); } /** Get the bfs_stat_field for X/Y in -newerXY. */ @@ -1755,20 +1755,20 @@ static enum bfs_stat_field parse_newerxy_field(char c) { } /** Parse an explicit reference timestamp for -newerXt and -*since. */ -static int parse_reftime(const struct parser_state *state, struct bfs_expr *expr) { +static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr) { if (xgetdate(expr->argv[1], &expr->reftime) == 0) { return 0; } else if (errno != EINVAL) { - parse_expr_error(state, expr, "%m.\n"); + parse_expr_error(parser, expr, "%m.\n"); return -1; } - parse_expr_error(state, expr, "Invalid timestamp.\n\n"); + parse_expr_error(parser, expr, "Invalid timestamp.\n\n"); fprintf(stderr, "Supported timestamp formats are ISO 8601-like, e.g.\n\n"); struct tm tm; - if (xlocaltime(&state->now.tv_sec, &tm) != 0) { - parse_perror(state, "xlocaltime()"); + if (xlocaltime(&parser->now.tv_sec, &tm) != 0) { + parse_perror(parser, "xlocaltime()"); return -1; } @@ -1787,8 +1787,8 @@ static int parse_reftime(const struct parser_state *state, struct bfs_expr *expr fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d%+03d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz_hour, tz_min); - if (xgmtime(&state->now.tv_sec, &tm) != 0) { - parse_perror(state, "xgmtime()"); + if (xgmtime(&parser->now.tv_sec, &tm) != 0) { + parse_perror(parser, "xgmtime()"); return -1; } @@ -1802,47 +1802,47 @@ static int parse_reftime(const struct parser_state *state, struct bfs_expr *expr /** * Parse -newerXY. */ -static struct bfs_expr *parse_newerxy(struct parser_state *state, int arg1, int arg2) { - const char *arg = state->argv[0]; +static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int arg2) { + const char *arg = parser->argv[0]; if (strlen(arg) != 8) { - parse_error(state, "Expected ${blu}-newer${bld}XY${rs}; found ${blu}-newer${bld}%pq${rs}.\n", arg + 6); + parse_error(parser, "Expected ${blu}-newer${bld}XY${rs}; found ${blu}-newer${bld}%pq${rs}.\n", arg + 6); return NULL; } - struct bfs_expr *expr = parse_unary_test(state, eval_newer); + struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { goto fail; } expr->stat_field = parse_newerxy_field(arg[6]); if (!expr->stat_field) { - parse_expr_error(state, expr, + parse_expr_error(parser, expr, "For ${blu}-newer${bld}XY${rs}, ${bld}X${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, or ${bld}B${rs}, not ${err}%c${rs}.\n", arg[6]); goto fail; } if (arg[7] == 't') { - if (parse_reftime(state, expr) != 0) { + if (parse_reftime(parser, expr) != 0) { goto fail; } } else { enum bfs_stat_field field = parse_newerxy_field(arg[7]); if (!field) { - parse_expr_error(state, expr, + parse_expr_error(parser, expr, "For ${blu}-newer${bld}XY${rs}, ${bld}Y${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, ${bld}B${rs}, or ${bld}t${rs}, not ${err}%c${rs}.\n", arg[7]); goto fail; } struct bfs_stat sb; - if (stat_arg(state, &expr->argv[1], &sb) != 0) { + if (stat_arg(parser, &expr->argv[1], &sb) != 0) { goto fail; } const struct timespec *reftime = bfs_stat_time(&sb, field); if (!reftime) { - parse_expr_error(state, expr, "Couldn't get file %s.\n", bfs_stat_field_name(field)); + parse_expr_error(parser, expr, "Couldn't get file %s.\n", bfs_stat_field_name(field)); goto fail; } @@ -1859,8 +1859,8 @@ fail: /** * Parse -nogroup. */ -static struct bfs_expr *parse_nogroup(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_nogroup); +static struct bfs_expr *parse_nogroup(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_test(parser, eval_nogroup); if (expr) { // Who knows how many FDs getgrgid_r() needs? expr->ephemeral_fds = 3; @@ -1871,33 +1871,33 @@ static struct bfs_expr *parse_nogroup(struct parser_state *state, int arg1, int /** * Parse -nohidden. */ -static struct bfs_expr *parse_nohidden(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_nohidden(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *hidden = bfs_expr_new(eval_hidden, 1, &fake_hidden_arg); if (!hidden) { return NULL; } - if (parse_exclude(state, hidden) != 0) { + if (parse_exclude(parser, hidden) != 0) { return NULL; } - return parse_nullary_option(state); + return parse_nullary_option(parser); } /** * Parse -noleaf. */ -static struct bfs_expr *parse_noleaf(struct parser_state *state, int arg1, int arg2) { - parse_warning(state, "${ex}%s${rs} does not apply the optimization that ${blu}%s${rs} inhibits.\n\n", - BFS_COMMAND, state->argv[0]); - return parse_nullary_option(state); +static struct bfs_expr *parse_noleaf(struct bfs_parser *parser, int arg1, int arg2) { + parse_warning(parser, "${ex}%s${rs} does not apply the optimization that ${blu}%s${rs} inhibits.\n\n", + BFS_COMMAND, parser->argv[0]); + return parse_nullary_option(parser); } /** * Parse -nouser. */ -static struct bfs_expr *parse_nouser(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_test(state, eval_nouser); +static struct bfs_expr *parse_nouser(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_test(parser, eval_nouser); if (expr) { // Who knows how many FDs getpwuid_r() needs? expr->ephemeral_fds = 3; @@ -1908,10 +1908,10 @@ static struct bfs_expr *parse_nouser(struct parser_state *state, int arg1, int a /** * Parse a permission mode like chmod(1). */ -static int parse_mode(const struct parser_state *state, const char *mode, struct bfs_expr *expr) { +static int parse_mode(const struct bfs_parser *parser, const char *mode, struct bfs_expr *expr) { if (mode[0] >= '0' && mode[0] <= '9') { unsigned int parsed; - if (!parse_int(state, NULL, mode, &parsed, 8 | IF_INT | IF_UNSIGNED | IF_QUIET)) { + if (!parse_int(parser, NULL, mode, &parsed, 8 | IF_INT | IF_UNSIGNED | IF_QUIET)) { goto fail; } if (parsed > 07777) { @@ -1943,7 +1943,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct // // PERMCOPY : "u" | "g" | "o" - // State machine state + // Parser machine parser enum { MODE_CLAUSE, MODE_WHO, @@ -1951,7 +1951,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct MODE_ACTION_APPLY, MODE_OP, MODE_PERM, - } mstate = MODE_CLAUSE; + } mparser = MODE_CLAUSE; enum { MODE_PLUS, @@ -1965,10 +1965,10 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct const char *i = mode; while (true) { - switch (mstate) { + switch (mparser) { case MODE_CLAUSE: who = 0; - mstate = MODE_WHO; + mparser = MODE_WHO; fallthru; case MODE_WHO: @@ -1986,7 +1986,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct who |= 0777; break; default: - mstate = MODE_ACTION; + mparser = MODE_ACTION; continue; } break; @@ -2016,27 +2016,27 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct switch (*i) { case '+': op = MODE_PLUS; - mstate = MODE_OP; + mparser = MODE_OP; break; case '-': op = MODE_MINUS; - mstate = MODE_OP; + mparser = MODE_OP; break; case '=': op = MODE_EQUALS; - mstate = MODE_OP; + mparser = MODE_OP; break; case ',': - if (mstate == MODE_ACTION_APPLY) { - mstate = MODE_CLAUSE; + if (mparser == MODE_ACTION_APPLY) { + mparser = MODE_CLAUSE; } else { goto fail; } break; case '\0': - if (mstate == MODE_ACTION_APPLY) { + if (mparser == MODE_ACTION_APPLY) { goto done; } else { goto fail; @@ -2065,7 +2065,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct default: file_change = 0; dir_change = 0; - mstate = MODE_PERM; + mparser = MODE_PERM; continue; } @@ -2073,7 +2073,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct file_change &= who; dir_change |= (dir_change << 6) | (dir_change << 3); dir_change &= who; - mstate = MODE_ACTION_APPLY; + mparser = MODE_ACTION_APPLY; break; case MODE_PERM: @@ -2109,7 +2109,7 @@ static int parse_mode(const struct parser_state *state, const char *mode, struct } break; default: - mstate = MODE_ACTION_APPLY; + mparser = MODE_ACTION_APPLY; continue; } break; @@ -2122,15 +2122,15 @@ done: return 0; fail: - parse_expr_error(state, expr, "Invalid mode.\n"); + parse_expr_error(parser, expr, "Invalid mode.\n"); return -1; } /** * Parse -perm MODE. */ -static struct bfs_expr *parse_perm(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_perm); +static struct bfs_expr *parse_perm(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_perm); if (!expr) { return NULL; } @@ -2157,7 +2157,7 @@ static struct bfs_expr *parse_perm(struct parser_state *state, int field, int ar break; } - if (parse_mode(state, mode, expr) != 0) { + if (parse_mode(parser, mode, expr) != 0) { goto fail; } @@ -2171,10 +2171,10 @@ fail: /** * Parse -print. */ -static struct bfs_expr *parse_print(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_fprint); +static struct bfs_expr *parse_print(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_fprint); if (expr) { - init_print_expr(state, expr); + init_print_expr(parser, expr); } return expr; } @@ -2182,10 +2182,10 @@ static struct bfs_expr *parse_print(struct parser_state *state, int arg1, int ar /** * Parse -print0. */ -static struct bfs_expr *parse_print0(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_fprint0); +static struct bfs_expr *parse_print0(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_fprint0); if (expr) { - init_print_expr(state, expr); + init_print_expr(parser, expr); } return expr; } @@ -2193,15 +2193,15 @@ static struct bfs_expr *parse_print0(struct parser_state *state, int arg1, int a /** * Parse -printf FORMAT. */ -static struct bfs_expr *parse_printf(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_action(state, eval_fprintf); +static struct bfs_expr *parse_printf(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_fprintf); if (!expr) { return NULL; } - init_print_expr(state, expr); + init_print_expr(parser, expr); - if (bfs_printf_parse(state->ctx, expr, expr->argv[1]) != 0) { + if (bfs_printf_parse(parser->ctx, expr, expr->argv[1]) != 0) { bfs_expr_free(expr); return NULL; } @@ -2212,10 +2212,10 @@ static struct bfs_expr *parse_printf(struct parser_state *state, int arg1, int a /** * Parse -printx. */ -static struct bfs_expr *parse_printx(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_nullary_action(state, eval_fprintx); +static struct bfs_expr *parse_printx(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_nullary_action(parser, eval_fprintx); if (expr) { - init_print_expr(state, expr); + init_print_expr(parser, expr); } return expr; } @@ -2223,40 +2223,40 @@ static struct bfs_expr *parse_printx(struct parser_state *state, int arg1, int a /** * Parse -prune. */ -static struct bfs_expr *parse_prune(struct parser_state *state, int arg1, int arg2) { - state->prune_arg = state->argv; - return parse_nullary_action(state, eval_prune); +static struct bfs_expr *parse_prune(struct bfs_parser *parser, int arg1, int arg2) { + parser->prune_arg = parser->argv; + return parse_nullary_action(parser, eval_prune); } /** * Parse -quit. */ -static struct bfs_expr *parse_quit(struct parser_state *state, int arg1, int arg2) { - return parse_nullary_action(state, eval_quit); +static struct bfs_expr *parse_quit(struct bfs_parser *parser, int arg1, int arg2) { + return parse_nullary_action(parser, eval_quit); } /** * Parse -i?regex. */ -static struct bfs_expr *parse_regex(struct parser_state *state, int flags, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_regex); +static struct bfs_expr *parse_regex(struct bfs_parser *parser, int flags, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_regex); if (!expr) { goto fail; } - if (bfs_regcomp(&expr->regex, expr->argv[1], state->regex_type, flags) != 0) { + if (bfs_regcomp(&expr->regex, expr->argv[1], parser->regex_type, flags) != 0) { if (!expr->regex) { - parse_perror(state, "bfs_regcomp()"); + parse_perror(parser, "bfs_regcomp()"); goto fail; } char *str = bfs_regerror(expr->regex); if (!str) { - parse_perror(state, "bfs_regerror()"); + parse_perror(parser, "bfs_regerror()"); goto fail; } - parse_expr_error(state, expr, "%s.\n", str); + parse_expr_error(parser, expr, "%s.\n", str); free(str); goto fail; } @@ -2271,19 +2271,19 @@ fail: /** * Parse -E. */ -static struct bfs_expr *parse_regex_extended(struct parser_state *state, int arg1, int arg2) { - state->regex_type = BFS_REGEX_POSIX_EXTENDED; - return parse_nullary_flag(state); +static struct bfs_expr *parse_regex_extended(struct bfs_parser *parser, int arg1, int arg2) { + parser->regex_type = BFS_REGEX_POSIX_EXTENDED; + return parse_nullary_flag(parser); } /** * Parse -regextype TYPE. */ -static struct bfs_expr *parse_regextype(struct parser_state *state, int arg1, int arg2) { - struct bfs_ctx *ctx = state->ctx; +static struct bfs_expr *parse_regextype(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_ctx *ctx = parser->ctx; CFILE *cfile = ctx->cerr; - struct bfs_expr *expr = parse_unary_option(state); + struct bfs_expr *expr = parse_unary_option(parser); if (!expr) { cfprintf(cfile, "\n"); goto list_types; @@ -2294,21 +2294,21 @@ static struct bfs_expr *parse_regextype(struct parser_state *state, int arg1, in if (strcmp(type, "posix-basic") == 0 || strcmp(type, "ed") == 0 || strcmp(type, "sed") == 0) { - state->regex_type = BFS_REGEX_POSIX_BASIC; + parser->regex_type = BFS_REGEX_POSIX_BASIC; } else if (strcmp(type, "posix-extended") == 0) { - state->regex_type = BFS_REGEX_POSIX_EXTENDED; + parser->regex_type = BFS_REGEX_POSIX_EXTENDED; #if BFS_USE_ONIGURUMA } else if (strcmp(type, "emacs") == 0) { - state->regex_type = BFS_REGEX_EMACS; + parser->regex_type = BFS_REGEX_EMACS; } else if (strcmp(type, "grep") == 0) { - state->regex_type = BFS_REGEX_GREP; + parser->regex_type = BFS_REGEX_GREP; #endif } else if (strcmp(type, "help") == 0) { - state->just_info = true; + parser->just_info = true; cfile = ctx->cout; goto list_types; } else { - parse_expr_error(state, expr, "Unsupported regex type.\n\n"); + parse_expr_error(parser, expr, "Unsupported regex type.\n\n"); goto list_types; } @@ -2332,22 +2332,22 @@ list_types: /** * Parse -s. */ -static struct bfs_expr *parse_s(struct parser_state *state, int arg1, int arg2) { - state->ctx->flags |= BFTW_SORT; - return parse_nullary_flag(state); +static struct bfs_expr *parse_s(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->flags |= BFTW_SORT; + return parse_nullary_flag(parser); } /** * Parse -samefile FILE. */ -static struct bfs_expr *parse_samefile(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_samefile); +static struct bfs_expr *parse_samefile(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_samefile); if (!expr) { return NULL; } struct bfs_stat sb; - if (stat_arg(state, &expr->argv[1], &sb) != 0) { + if (stat_arg(parser, &expr->argv[1], &sb) != 0) { bfs_expr_free(expr); return NULL; } @@ -2360,11 +2360,11 @@ static struct bfs_expr *parse_samefile(struct parser_state *state, int arg1, int /** * Parse -S STRATEGY. */ -static struct bfs_expr *parse_search_strategy(struct parser_state *state, int arg1, int arg2) { - struct bfs_ctx *ctx = state->ctx; +static struct bfs_expr *parse_search_strategy(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_ctx *ctx = parser->ctx; CFILE *cfile = ctx->cerr; - struct bfs_expr *expr = parse_unary_flag(state); + struct bfs_expr *expr = parse_unary_flag(parser); if (!expr) { cfprintf(cfile, "\n"); goto list_strategies; @@ -2380,11 +2380,11 @@ static struct bfs_expr *parse_search_strategy(struct parser_state *state, int ar } else if (strcmp(arg, "eds") == 0) { ctx->strategy = BFTW_EDS; } else if (strcmp(arg, "help") == 0) { - state->just_info = true; + parser->just_info = true; cfile = ctx->cout; goto list_strategies; } else { - parse_expr_error(state, expr, "Unrecognized search strategy.\n\n"); + parse_expr_error(parser, expr, "Unrecognized search strategy.\n\n"); goto list_strategies; } @@ -2404,13 +2404,13 @@ list_strategies: /** * Parse -[aBcm]?since. */ -static struct bfs_expr *parse_since(struct parser_state *state, int field, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_newer); +static struct bfs_expr *parse_since(struct bfs_parser *parser, int field, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { return NULL; } - if (parse_reftime(state, expr) != 0) { + if (parse_reftime(parser, expr) != 0) { goto fail; } @@ -2425,13 +2425,13 @@ fail: /** * Parse -size N[cwbkMGTP]?. */ -static struct bfs_expr *parse_size(struct parser_state *state, int arg1, int arg2) { - struct bfs_expr *expr = parse_unary_test(state, eval_size); +static struct bfs_expr *parse_size(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_test(parser, eval_size); if (!expr) { return NULL; } - const char *unit = parse_icmp(state, expr, IF_PARTIAL_OK); + const char *unit = parse_icmp(parser, expr, IF_PARTIAL_OK); if (!unit) { goto fail; } @@ -2474,7 +2474,7 @@ static struct bfs_expr *parse_size(struct parser_state *state, int arg1, int arg return expr; bad_unit: - parse_expr_error(state, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%pq${rs}.\n", unit); + parse_expr_error(parser, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%pq${rs}.\n", unit); fail: bfs_expr_free(expr); return NULL; @@ -2483,26 +2483,26 @@ fail: /** * Parse -sparse. */ -static struct bfs_expr *parse_sparse(struct parser_state *state, int arg1, int arg2) { - return parse_nullary_test(state, eval_sparse); +static struct bfs_expr *parse_sparse(struct bfs_parser *parser, int arg1, int arg2) { + return parse_nullary_test(parser, eval_sparse); } /** * Parse -status. */ -static struct bfs_expr *parse_status(struct parser_state *state, int arg1, int arg2) { - state->ctx->status = true; - return parse_nullary_option(state); +static struct bfs_expr *parse_status(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->status = true; + return parse_nullary_option(parser); } /** * Parse -x?type [bcdpflsD]. */ -static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) { - struct bfs_ctx *ctx = state->ctx; +static struct bfs_expr *parse_type(struct bfs_parser *parser, int x, int arg2) { + struct bfs_ctx *ctx = parser->ctx; bfs_eval_fn *eval = x ? eval_xtype : eval_type; - struct bfs_expr *expr = parse_unary_test(state, eval); + struct bfs_expr *expr = parse_unary_test(parser, eval); if (!expr) { return NULL; } @@ -2542,11 +2542,11 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) break; case '\0': - parse_expr_error(state, expr, "Expected a type flag.\n"); + parse_expr_error(parser, expr, "Expected a type flag.\n"); goto fail; default: - parse_expr_error(state, expr, "Unknown type flag ${err}%c${rs}; expected one of [${bld}bcdpflsD${rs}].\n", *c); + parse_expr_error(parser, expr, "Unknown type flag ${err}%c${rs}; expected one of [${bld}bcdpflsD${rs}].\n", *c); goto fail; } @@ -2557,7 +2557,7 @@ static struct bfs_expr *parse_type(struct parser_state *state, int x, int arg2) ++c; continue; } else { - parse_expr_error(state, expr, "Types must be comma-separated.\n"); + parse_expr_error(parser, expr, "Types must be comma-separated.\n"); goto fail; } } @@ -2572,19 +2572,19 @@ fail: /** * Parse -(no)?warn. */ -static struct bfs_expr *parse_warn(struct parser_state *state, int warn, int arg2) { - state->ctx->warn = warn; - return parse_nullary_option(state); +static struct bfs_expr *parse_warn(struct bfs_parser *parser, int warn, int arg2) { + parser->ctx->warn = warn; + return parse_nullary_option(parser); } /** * Parse -xattr. */ -static struct bfs_expr *parse_xattr(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_xattr(struct bfs_parser *parser, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS - return parse_nullary_test(state, eval_xattr); + return parse_nullary_test(parser, eval_xattr); #else - parse_error(state, "Missing platform support.\n"); + parse_error(parser, "Missing platform support.\n"); return NULL; #endif } @@ -2592,11 +2592,11 @@ static struct bfs_expr *parse_xattr(struct parser_state *state, int arg1, int ar /** * Parse -xattrname. */ -static struct bfs_expr *parse_xattrname(struct parser_state *state, int arg1, int arg2) { +static struct bfs_expr *parse_xattrname(struct bfs_parser *parser, int arg1, int arg2) { #if BFS_CAN_CHECK_XATTRS - return parse_unary_test(state, eval_xattrname); + return parse_unary_test(parser, eval_xattrname); #else - parse_error(state, "Missing platform support.\n"); + parse_error(parser, "Missing platform support.\n"); return NULL; #endif } @@ -2604,10 +2604,10 @@ static struct bfs_expr *parse_xattrname(struct parser_state *state, int arg1, in /** * Parse -xdev. */ -static struct bfs_expr *parse_xdev(struct parser_state *state, int arg1, int arg2) { - state->ctx->flags |= BFTW_PRUNE_MOUNTS; - state->xdev_arg = state->argv; - return parse_nullary_option(state); +static struct bfs_expr *parse_xdev(struct bfs_parser *parser, int arg1, int arg2) { + parser->ctx->flags |= BFTW_PRUNE_MOUNTS; + parser->xdev_arg = parser->argv; + return parse_nullary_option(parser); } /** @@ -2708,16 +2708,16 @@ fail: /** * "Parse" -help. */ -static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg2) { - CFILE *cout = state->ctx->cout; +static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2) { + CFILE *cout = parser->ctx->cout; pid_t pager = -1; - if (state->stdout_tty) { + if (parser->stdout_tty) { cout = launch_pager(&pager, cout); } cfprintf(cout, "Usage: ${ex}%s${rs} [${cyn}flags${rs}...] [${mag}paths${rs}...] [${blu}expression${rs}...]\n\n", - state->command); + parser->command); cfprintf(cout, "${ex}%s${rs} is compatible with ${ex}find${rs}, with some extensions. " "${cyn}Flags${rs} (${cyn}-H${rs}/${cyn}-L${rs}/${cyn}-P${rs} etc.), ${mag}paths${rs},\n" @@ -2968,23 +2968,23 @@ static struct bfs_expr *parse_help(struct parser_state *state, int arg1, int arg xwaitpid(pager, NULL, 0); } - state->just_info = true; + parser->just_info = true; return NULL; } /** * "Parse" -version. */ -static struct bfs_expr *parse_version(struct parser_state *state, int arg1, int arg2) { - cfprintf(state->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, BFS_VERSION); +static struct bfs_expr *parse_version(struct bfs_parser *parser, int arg1, int arg2) { + cfprintf(parser->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, BFS_VERSION); printf("%s\n", BFS_HOMEPAGE); - state->just_info = true; + parser->just_info = true; return NULL; } -typedef struct bfs_expr *parse_fn(struct parser_state *state, int arg1, int arg2); +typedef struct bfs_expr *parse_fn(struct bfs_parser *parser, int arg1, int arg2); /** * An entry in the parse table for primary expressions. @@ -3161,9 +3161,9 @@ static const struct table_entry *table_lookup_fuzzy(const char *arg) { * | TEST * | ACTION */ -static struct bfs_expr *parse_primary(struct parser_state *state) { +static struct bfs_expr *parse_primary(struct bfs_parser *parser) { // Paths are already skipped at this point - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (arg[0] != '-') { goto unexpected; @@ -3180,8 +3180,8 @@ static struct bfs_expr *parse_primary(struct parser_state *state) { match = table_lookup_fuzzy(arg); - CFILE *cerr = state->ctx->cerr; - parse_error(state, "Unknown argument; did you mean "); + CFILE *cerr = parser->ctx->cerr; + parse_error(parser, "Unknown argument; did you mean "); switch (match->type) { case T_FLAG: cfprintf(cerr, "${cyn}%s${rs}?", match->arg); @@ -3194,7 +3194,7 @@ static struct bfs_expr *parse_primary(struct parser_state *state) { break; } - if (!state->interactive || !match->parse) { + if (!parser->interactive || !match->parse) { fprintf(stderr, "\n"); goto unmatched; } @@ -3205,16 +3205,16 @@ static struct bfs_expr *parse_primary(struct parser_state *state) { } fprintf(stderr, "\n"); - state->argv[0] = match->arg; + parser->argv[0] = match->arg; matched: - return match->parse(state, match->arg1, match->arg2); + return match->parse(parser, match->arg1, match->arg2); unmatched: return NULL; unexpected: - parse_error(state, "Expected a predicate.\n"); + parse_error(parser, "Expected a predicate.\n"); return NULL; } @@ -3224,71 +3224,71 @@ unexpected: * | "-exclude" FACTOR * | PRIMARY */ -static struct bfs_expr *parse_factor(struct parser_state *state) { - if (skip_paths(state) != 0) { +static struct bfs_expr *parse_factor(struct bfs_parser *parser) { + if (skip_paths(parser) != 0) { return NULL; } - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { - parse_argv_error(state, state->last_arg, 1, "Expression terminated prematurely here.\n"); + parse_argv_error(parser, parser->last_arg, 1, "Expression terminated prematurely here.\n"); return NULL; } if (strcmp(arg, "(") == 0) { - parser_advance(state, T_OPERATOR, 1); + parser_advance(parser, T_OPERATOR, 1); - struct bfs_expr *expr = parse_expr(state); + struct bfs_expr *expr = parse_expr(parser); if (!expr) { return NULL; } - if (skip_paths(state) != 0) { + if (skip_paths(parser) != 0) { bfs_expr_free(expr); return NULL; } - arg = state->argv[0]; + arg = parser->argv[0]; if (!arg || strcmp(arg, ")") != 0) { - parse_argv_error(state, state->last_arg, 1, "Expected a ${red})${rs}.\n"); + parse_argv_error(parser, parser->last_arg, 1, "Expected a ${red})${rs}.\n"); bfs_expr_free(expr); return NULL; } - parser_advance(state, T_OPERATOR, 1); + parser_advance(parser, T_OPERATOR, 1); return expr; } else if (strcmp(arg, "-exclude") == 0) { - if (state->excluding) { - parse_error(state, "${err}%s${rs} is not supported within ${red}-exclude${rs}.\n", arg); + if (parser->excluding) { + parse_error(parser, "${err}%s${rs} is not supported within ${red}-exclude${rs}.\n", arg); return NULL; } - char **argv = parser_advance(state, T_OPERATOR, 1); - state->excluding = true; + char **argv = parser_advance(parser, T_OPERATOR, 1); + parser->excluding = true; - struct bfs_expr *factor = parse_factor(state); + struct bfs_expr *factor = parse_factor(parser); if (!factor) { return NULL; } - state->excluding = false; + parser->excluding = false; - if (parse_exclude(state, factor) != 0) { + if (parse_exclude(parser, factor) != 0) { return NULL; } - return bfs_expr_new(eval_true, state->argv - argv, argv); + return bfs_expr_new(eval_true, parser->argv - argv, argv); } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { - char **argv = parser_advance(state, T_OPERATOR, 1); + char **argv = parser_advance(parser, T_OPERATOR, 1); - struct bfs_expr *factor = parse_factor(state); + struct bfs_expr *factor = parse_factor(parser); if (!factor) { return NULL; } return new_unary_expr(eval_not, factor, argv); } else { - return parse_primary(state); + return parse_primary(parser); } } @@ -3298,16 +3298,16 @@ static struct bfs_expr *parse_factor(struct parser_state *state) { * | TERM "-a" FACTOR * | TERM "-and" FACTOR */ -static struct bfs_expr *parse_term(struct parser_state *state) { - struct bfs_expr *term = parse_factor(state); +static struct bfs_expr *parse_term(struct bfs_parser *parser) { + struct bfs_expr *term = parse_factor(parser); while (term) { - if (skip_paths(state) != 0) { + if (skip_paths(parser) != 0) { bfs_expr_free(term); return NULL; } - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { break; } @@ -3320,11 +3320,11 @@ static struct bfs_expr *parse_term(struct parser_state *state) { char **argv = &fake_and_arg; if (strcmp(arg, "-a") == 0 || strcmp(arg, "-and") == 0) { - argv = parser_advance(state, T_OPERATOR, 1); + argv = parser_advance(parser, T_OPERATOR, 1); } struct bfs_expr *lhs = term; - struct bfs_expr *rhs = parse_factor(state); + struct bfs_expr *rhs = parse_factor(parser); if (!rhs) { bfs_expr_free(lhs); return NULL; @@ -3341,16 +3341,16 @@ static struct bfs_expr *parse_term(struct parser_state *state) { * | CLAUSE "-o" TERM * | CLAUSE "-or" TERM */ -static struct bfs_expr *parse_clause(struct parser_state *state) { - struct bfs_expr *clause = parse_term(state); +static struct bfs_expr *parse_clause(struct bfs_parser *parser) { + struct bfs_expr *clause = parse_term(parser); while (clause) { - if (skip_paths(state) != 0) { + if (skip_paths(parser) != 0) { bfs_expr_free(clause); return NULL; } - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { break; } @@ -3359,10 +3359,10 @@ static struct bfs_expr *parse_clause(struct parser_state *state) { break; } - char **argv = parser_advance(state, T_OPERATOR, 1); + char **argv = parser_advance(parser, T_OPERATOR, 1); struct bfs_expr *lhs = clause; - struct bfs_expr *rhs = parse_term(state); + struct bfs_expr *rhs = parse_term(parser); if (!rhs) { bfs_expr_free(lhs); return NULL; @@ -3378,16 +3378,16 @@ static struct bfs_expr *parse_clause(struct parser_state *state) { * EXPR : CLAUSE * | EXPR "," CLAUSE */ -static struct bfs_expr *parse_expr(struct parser_state *state) { - struct bfs_expr *expr = parse_clause(state); +static struct bfs_expr *parse_expr(struct bfs_parser *parser) { + struct bfs_expr *expr = parse_clause(parser); while (expr) { - if (skip_paths(state) != 0) { + if (skip_paths(parser) != 0) { bfs_expr_free(expr); return NULL; } - const char *arg = state->argv[0]; + const char *arg = parser->argv[0]; if (!arg) { break; } @@ -3396,10 +3396,10 @@ static struct bfs_expr *parse_expr(struct parser_state *state) { break; } - char **argv = parser_advance(state, T_OPERATOR, 1); + char **argv = parser_advance(parser, T_OPERATOR, 1); struct bfs_expr *lhs = expr; - struct bfs_expr *rhs = parse_clause(state); + struct bfs_expr *rhs = parse_clause(parser); if (!rhs) { bfs_expr_free(lhs); return NULL; @@ -3414,14 +3414,14 @@ static struct bfs_expr *parse_expr(struct parser_state *state) { /** * Parse the top-level expression. */ -static struct bfs_expr *parse_whole_expr(struct parser_state *state) { - if (skip_paths(state) != 0) { +static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { + if (skip_paths(parser) != 0) { return NULL; } struct bfs_expr *expr; - if (state->argv[0]) { - expr = parse_expr(state); + if (parser->argv[0]) { + expr = parse_expr(parser); } else { expr = bfs_expr_new(eval_true, 1, &fake_true_arg); } @@ -3429,17 +3429,17 @@ static struct bfs_expr *parse_whole_expr(struct parser_state *state) { return NULL; } - if (state->argv[0]) { - parse_error(state, "Unexpected argument.\n"); + if (parser->argv[0]) { + parse_error(parser, "Unexpected argument.\n"); goto fail; } - if (state->implicit_print) { + if (parser->implicit_print) { struct bfs_expr *print = bfs_expr_new(eval_fprint, 1, &fake_print_arg); if (!print) { goto fail; } - init_print_expr(state, print); + init_print_expr(parser, print); expr = new_binary_expr(eval_and, expr, print, &fake_and_arg); if (!expr) { @@ -3447,19 +3447,19 @@ static struct bfs_expr *parse_whole_expr(struct parser_state *state) { } } - if (state->mount_arg && state->xdev_arg) { - parse_conflict_warning(state, state->mount_arg, 1, state->xdev_arg, 1, + if (parser->mount_arg && parser->xdev_arg) { + parse_conflict_warning(parser, parser->mount_arg, 1, parser->xdev_arg, 1, "${blu}%s${rs} is redundant in the presence of ${blu}%s${rs}.\n\n", - state->xdev_arg[0], state->mount_arg[0]); + parser->xdev_arg[0], parser->mount_arg[0]); } - if (state->ctx->warn && state->depth_arg && state->prune_arg) { - parse_conflict_warning(state, state->depth_arg, 1, state->prune_arg, 1, + if (parser->ctx->warn && parser->depth_arg && parser->prune_arg) { + parse_conflict_warning(parser, parser->depth_arg, 1, parser->prune_arg, 1, "${blu}%s${rs} does not work in the presence of ${blu}%s${rs}.\n", - state->prune_arg[0], state->depth_arg[0]); + parser->prune_arg[0], parser->depth_arg[0]); - if (state->interactive) { - bfs_warning(state->ctx, "Do you want to continue? "); + if (parser->interactive) { + bfs_warning(parser->ctx, "Do you want to continue? "); if (ynprompt() == 0) { goto fail; } @@ -3468,10 +3468,10 @@ static struct bfs_expr *parse_whole_expr(struct parser_state *state) { fprintf(stderr, "\n"); } - if (state->ok_expr && state->files0_stdin_arg) { - parse_conflict_error(state, state->ok_expr->argv, state->ok_expr->argc, state->files0_stdin_arg, 2, + if (parser->ok_expr && parser->files0_stdin_arg) { + parse_conflict_error(parser, parser->ok_expr->argv, parser->ok_expr->argc, parser->files0_stdin_arg, 2, "${blu}%s${rs} conflicts with ${blu}%s${rs} ${bld}%s${rs}.\n", - state->ok_expr->argv[0], state->files0_stdin_arg[0], state->files0_stdin_arg[1]); + parser->ok_expr->argv[0], parser->files0_stdin_arg[0], parser->files0_stdin_arg[1]); goto fail; } @@ -3693,7 +3693,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { ctx->warn = stdin_tty; } - struct parser_state state = { + struct bfs_parser parser = { .ctx = ctx, .argv = ctx->argv + 1, .command = ctx->argv[0], @@ -3720,16 +3720,16 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { goto fail; } - ctx->expr = parse_whole_expr(&state); + ctx->expr = parse_whole_expr(&parser); if (!ctx->expr) { - if (state.just_info) { + if (parser.just_info) { goto done; } else { goto fail; } } - if (state.use_color == COLOR_AUTO && !ctx->colors) { + if (parser.use_color == COLOR_AUTO && !ctx->colors) { bfs_warning(ctx, "Error parsing $$LS_COLORS: %s.\n\n", xstrerror(ctx->colors_error)); } @@ -3737,8 +3737,8 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { goto fail; } - if (ctx->npaths == 0 && state.implicit_root) { - if (parse_root(&state, ".") != 0) { + if (ctx->npaths == 0 && parser.implicit_root) { + if (parse_root(&parser, ".") != 0) { goto fail; } } -- cgit v1.2.3 From 70092ae5c8f83a99fbc98dc8e2ca2eaab676a5a8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 20 Dec 2023 20:18:35 -0500 Subject: expr: Arena-allocate expressions --- src/ctx.c | 11 +- src/ctx.h | 6 ++ src/expr.c | 22 ++-- src/expr.h | 19 +++- src/opt.c | 113 +++++++------------- src/parse.c | 340 +++++++++++++++++++++++------------------------------------- 6 files changed, 210 insertions(+), 301 deletions(-) diff --git a/src/ctx.c b/src/ctx.c index f5dc465..a3c5140 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -22,6 +22,9 @@ struct bfs_ctx *bfs_ctx_new(void) { return NULL; } + SLIST_INIT(&ctx->expr_list); + ARENA_INIT(&ctx->expr_arena, struct bfs_expr); + ctx->maxdepth = INT_MAX; ctx->flags = BFTW_RECOVER; ctx->strategy = BFTW_BFS; @@ -202,9 +205,6 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { CFILE *cout = ctx->cout; CFILE *cerr = ctx->cerr; - bfs_expr_free(ctx->exclude); - bfs_expr_free(ctx->expr); - bfs_mtab_free(ctx->mtab); bfs_groups_free(ctx->groups); @@ -241,6 +241,11 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { free_colors(ctx->colors); + for_slist (struct bfs_expr, expr, &ctx->expr_list) { + bfs_expr_clear(expr); + } + arena_destroy(&ctx->expr_arena); + for (size_t i = 0; i < ctx->npaths; ++i) { free((char *)ctx->paths[i]); } diff --git a/src/ctx.h b/src/ctx.h index 75891da..aa91f2c 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -8,9 +8,11 @@ #ifndef BFS_CTX_H #define BFS_CTX_H +#include "alloc.h" #include "bftw.h" #include "config.h" #include "diag.h" +#include "expr.h" #include "trie.h" #include #include @@ -34,6 +36,10 @@ struct bfs_ctx { struct bfs_expr *expr; /** An expression for files to filter out. */ struct bfs_expr *exclude; + /** A list of allocated expressions. */ + struct bfs_exprs expr_list; + /** bfs_expr arena. */ + struct arena expr_arena; /** -mindepth option. */ int mindepth; diff --git a/src/expr.c b/src/expr.c index 380038c..2002fc7 100644 --- a/src/expr.c +++ b/src/expr.c @@ -3,24 +3,27 @@ #include "expr.h" #include "alloc.h" +#include "ctx.h" #include "eval.h" #include "exec.h" #include "printf.h" #include "xregex.h" #include #include +#include -struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval_fn, size_t argc, char **argv) { - struct bfs_expr *expr = ZALLOC(struct bfs_expr); +struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t argc, char **argv) { + struct bfs_expr *expr = arena_alloc(&ctx->expr_arena); if (!expr) { - perror("zalloc()"); return NULL; } + memset(expr, 0, sizeof(*expr)); expr->eval_fn = eval_fn; expr->argc = argc; expr->argv = argv; expr->probability = 0.5; + SLIST_PREPEND(&ctx->expr_list, expr); return expr; } @@ -36,21 +39,12 @@ bool bfs_expr_never_returns(const struct bfs_expr *expr) { return expr->always_true && expr->always_false; } -void bfs_expr_free(struct bfs_expr *expr) { - if (!expr) { - return; - } - - if (bfs_expr_is_parent(expr)) { - bfs_expr_free(expr->rhs); - bfs_expr_free(expr->lhs); - } else if (expr->eval_fn == eval_exec) { +void bfs_expr_clear(struct bfs_expr *expr) { + if (expr->eval_fn == eval_exec) { bfs_exec_free(expr->exec); } else if (expr->eval_fn == eval_fprintf) { bfs_printf_free(expr->printf); } else if (expr->eval_fn == eval_regex) { bfs_regfree(expr->regex); } - - free(expr); } diff --git a/src/expr.h b/src/expr.h index 95118b9..290f1f8 100644 --- a/src/expr.h +++ b/src/expr.h @@ -74,10 +74,21 @@ enum bfs_size_unit { BFS_PB, }; +/** + * A linked list of expressions. + */ +struct bfs_exprs { + struct bfs_expr *head; + struct bfs_expr **tail; +}; + /** * A command line expression. */ struct bfs_expr { + /** The next allocated expression. */ + struct bfs_expr *next; + /** The function that evaluates this expression. */ bfs_eval_fn *eval_fn; @@ -199,10 +210,12 @@ struct bfs_expr { }; }; +struct bfs_ctx; + /** * Create a new expression. */ -struct bfs_expr *bfs_expr_new(bfs_eval_fn *eval, size_t argc, char **argv); +struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval, size_t argc, char **argv); /** * @return Whether the expression has child expressions. @@ -220,8 +233,8 @@ bool bfs_expr_never_returns(const struct bfs_expr *expr); bool bfs_expr_cmp(const struct bfs_expr *expr, long long n); /** - * Free an expression tree. + * Free any resources owned by an expression. */ -void bfs_expr_free(struct bfs_expr *expr); +void bfs_expr_clear(struct bfs_expr *expr); #endif // BFS_EXPR_H diff --git a/src/opt.c b/src/opt.c index a989670..1dc985a 100644 --- a/src/opt.c +++ b/src/opt.c @@ -284,7 +284,7 @@ static void df_join(struct df_domain *dest, const struct df_domain *src) { */ struct bfs_opt { /** The context we're optimizing. */ - const struct bfs_ctx *ctx; + struct bfs_ctx *ctx; /** Data flow state before this expression is evaluated. */ struct df_domain before; @@ -330,31 +330,22 @@ static void opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, } /** Create a constant expression. */ -static struct bfs_expr *opt_const(bool value) { +static struct bfs_expr *opt_const(const struct bfs_opt *opt, bool value) { static bfs_eval_fn *fns[] = {eval_false, eval_true}; static char *fake_args[] = {"-false", "-true"}; - return bfs_expr_new(fns[value], 1, &fake_args[value]); -} - -/** Extract a child expression, freeing the outer expression. */ -static struct bfs_expr *extract_child_expr(struct bfs_expr *expr, struct bfs_expr **child) { - struct bfs_expr *ret = *child; - *child = NULL; - bfs_expr_free(expr); - return ret; + return bfs_expr_new(opt->ctx, fns[value], 1, &fake_args[value]); } /** * Negate an expression. */ -static struct bfs_expr *negate_expr(struct bfs_expr *rhs, char **argv) { +static struct bfs_expr *negate_expr(const struct bfs_opt *opt, struct bfs_expr *rhs, char **argv) { if (rhs->eval_fn == eval_not) { - return extract_child_expr(rhs, &rhs->rhs); + return rhs->rhs; } - struct bfs_expr *expr = bfs_expr_new(eval_not, 1, argv); + struct bfs_expr *expr = bfs_expr_new(opt->ctx, eval_not, 1, argv); if (!expr) { - bfs_expr_free(rhs); return NULL; } @@ -373,7 +364,7 @@ static struct bfs_expr *optimize_or_expr(const struct bfs_opt *opt, struct bfs_e static struct bfs_expr *de_morgan(const struct bfs_opt *opt, struct bfs_expr *expr, char **argv) { bool debug = opt_debug(opt, 1, "De Morgan's laws: %pe ", expr); - struct bfs_expr *parent = negate_expr(expr, argv); + struct bfs_expr *parent = negate_expr(opt, expr, argv); if (!parent) { return NULL; } @@ -393,10 +384,9 @@ static struct bfs_expr *de_morgan(const struct bfs_opt *opt, struct bfs_expr *ex expr->argv = &fake_and_arg; } - expr->lhs = negate_expr(expr->lhs, argv); - expr->rhs = negate_expr(expr->rhs, argv); + expr->lhs = negate_expr(opt, expr->lhs, argv); + expr->rhs = negate_expr(opt, expr->rhs, argv); if (!expr->lhs || !expr->rhs) { - bfs_expr_free(parent); return NULL; } @@ -411,7 +401,6 @@ static struct bfs_expr *de_morgan(const struct bfs_opt *opt, struct bfs_expr *ex expr->rhs = optimize_not_expr(opt, expr->rhs); } if (!expr->lhs || !expr->rhs) { - bfs_expr_free(parent); return NULL; } @@ -426,7 +415,6 @@ static struct bfs_expr *de_morgan(const struct bfs_opt *opt, struct bfs_expr *ex parent = expr; } if (!expr) { - bfs_expr_free(parent); return NULL; } @@ -450,16 +438,15 @@ static struct bfs_expr *optimize_not_expr(const struct bfs_opt *opt, struct bfs_ int optlevel = opt->ctx->optlevel; if (optlevel >= 1) { if (rhs->eval_fn == eval_true || rhs->eval_fn == eval_false) { - struct bfs_expr *ret = opt_const(rhs->eval_fn == eval_false); + struct bfs_expr *ret = opt_const(opt, rhs->eval_fn == eval_false); opt_debug(opt, 1, "constant propagation: %pe <==> %pe\n", expr, ret); - bfs_expr_free(expr); return ret; } else if (rhs->eval_fn == eval_not) { opt_debug(opt, 1, "double negation: %pe <==> %pe\n", expr, rhs->rhs); - return extract_child_expr(expr, &rhs->rhs); + return rhs->rhs; } else if (bfs_expr_never_returns(rhs)) { opt_debug(opt, 1, "reachability: %pe <==> %pe\n", expr, rhs); - return extract_child_expr(expr, &expr->rhs); + return expr->rhs; } else if ((rhs->eval_fn == eval_and || rhs->eval_fn == eval_or) && (rhs->lhs->eval_fn == eval_not || rhs->rhs->eval_fn == eval_not)) { return de_morgan(opt, expr, expr->argv); @@ -480,17 +467,13 @@ static struct bfs_expr *optimize_not_expr_recursive(struct bfs_opt *opt, struct struct bfs_opt rhs_state = *opt; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { - goto fail; + return NULL; } opt->after_true = rhs_state.after_false; opt->after_false = rhs_state.after_true; return optimize_not_expr(opt, expr); - -fail: - bfs_expr_free(expr); - return NULL; } /** Optimize a conjunction. */ @@ -505,18 +488,18 @@ static struct bfs_expr *optimize_and_expr(const struct bfs_opt *opt, struct bfs_ if (optlevel >= 1) { if (lhs->eval_fn == eval_true) { opt_debug(opt, 1, "conjunction elimination: %pe <==> %pe\n", expr, rhs); - return extract_child_expr(expr, &expr->rhs); + return expr->rhs; } else if (rhs->eval_fn == eval_true) { opt_debug(opt, 1, "conjunction elimination: %pe <==> %pe\n", expr, lhs); - return extract_child_expr(expr, &expr->lhs); + return expr->lhs; } else if (lhs->always_false) { opt_debug(opt, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); - return extract_child_expr(expr, &expr->lhs); + return expr->lhs; } else if (lhs->always_true && rhs->eval_fn == eval_false) { bool debug = opt_debug(opt, 1, "strength reduction: %pe <==> ", expr); - struct bfs_expr *ret = extract_child_expr(expr, &expr->lhs); - ret = negate_expr(ret, &fake_not_arg); + struct bfs_expr *ret = expr->lhs; + ret = negate_expr(opt, ret, &fake_not_arg); if (debug && ret) { cfprintf(ctx->cerr, "%pe\n", ret); } @@ -524,7 +507,7 @@ static struct bfs_expr *optimize_and_expr(const struct bfs_opt *opt, struct bfs_ } else if (optlevel >= 2 && lhs->pure && rhs->eval_fn == eval_false) { opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); - return extract_child_expr(expr, &expr->rhs); + return expr->rhs; } else if (lhs->eval_fn == eval_not && rhs->eval_fn == eval_not) { return de_morgan(opt, expr, expr->lhs->argv); } @@ -544,14 +527,14 @@ static struct bfs_expr *optimize_and_expr_recursive(struct bfs_opt *opt, struct struct bfs_opt lhs_state = *opt; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { - goto fail; + return NULL; } struct bfs_opt rhs_state = *opt; rhs_state.before = lhs_state.after_true; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { - goto fail; + return NULL; } opt->after_true = rhs_state.after_true; @@ -559,10 +542,6 @@ static struct bfs_expr *optimize_and_expr_recursive(struct bfs_opt *opt, struct df_join(&opt->after_false, &rhs_state.after_false); return optimize_and_expr(opt, expr); - -fail: - bfs_expr_free(expr); - return NULL; } /** Optimize a disjunction. */ @@ -578,17 +557,17 @@ static struct bfs_expr *optimize_or_expr(const struct bfs_opt *opt, struct bfs_e if (lhs->always_true) { opt_debug(opt, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); - return extract_child_expr(expr, &expr->lhs); + return expr->lhs; } else if (lhs->eval_fn == eval_false) { opt_debug(opt, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, rhs); - return extract_child_expr(expr, &expr->rhs); + return expr->rhs; } else if (rhs->eval_fn == eval_false) { opt_debug(opt, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, lhs); - return extract_child_expr(expr, &expr->lhs); + return expr->lhs; } else if (lhs->always_false && rhs->eval_fn == eval_true) { bool debug = opt_debug(opt, 1, "strength reduction: %pe <==> ", expr); - struct bfs_expr *ret = extract_child_expr(expr, &expr->lhs); - ret = negate_expr(ret, &fake_not_arg); + struct bfs_expr *ret = expr->lhs; + ret = negate_expr(opt, ret, &fake_not_arg); if (debug && ret) { cfprintf(ctx->cerr, "%pe\n", ret); } @@ -596,7 +575,7 @@ static struct bfs_expr *optimize_or_expr(const struct bfs_opt *opt, struct bfs_e } else if (optlevel >= 2 && lhs->pure && rhs->eval_fn == eval_true) { opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); - return extract_child_expr(expr, &expr->rhs); + return expr->rhs; } else if (lhs->eval_fn == eval_not && rhs->eval_fn == eval_not) { return de_morgan(opt, expr, expr->lhs->argv); } @@ -616,14 +595,14 @@ static struct bfs_expr *optimize_or_expr_recursive(struct bfs_opt *opt, struct b struct bfs_opt lhs_state = *opt; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { - goto fail; + return NULL; } struct bfs_opt rhs_state = *opt; rhs_state.before = lhs_state.after_false; expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { - goto fail; + return NULL; } opt->after_false = rhs_state.after_false; @@ -631,10 +610,6 @@ static struct bfs_expr *optimize_or_expr_recursive(struct bfs_opt *opt, struct b df_join(&opt->after_true, &rhs_state.after_true); return optimize_or_expr(opt, expr); - -fail: - bfs_expr_free(expr); - return NULL; } /** Optimize an expression in an ignored-result context. */ @@ -646,23 +621,22 @@ static struct bfs_expr *ignore_result(const struct bfs_opt *opt, struct bfs_expr if (expr->eval_fn == eval_not) { opt_debug(opt, 1, "ignored result: %pe --> %pe\n", expr, expr->rhs); opt_warning(opt, expr, "The result of this expression is ignored.\n\n"); - expr = extract_child_expr(expr, &expr->rhs); + expr = expr->rhs; } else if (optlevel >= 2 && (expr->eval_fn == eval_and || expr->eval_fn == eval_or || expr->eval_fn == eval_comma) && expr->rhs->pure) { opt_debug(opt, 2, "ignored result: %pe --> %pe\n", expr, expr->lhs); opt_warning(opt, expr->rhs, "The result of this expression is ignored.\n\n"); - expr = extract_child_expr(expr, &expr->lhs); + expr = expr->lhs; } else { break; } } if (optlevel >= 2 && expr->pure && expr->eval_fn != eval_false) { - struct bfs_expr *ret = opt_const(false); + struct bfs_expr *ret = opt_const(opt, false); opt_debug(opt, 2, "ignored result: %pe --> %pe\n", expr, ret); opt_warning(opt, expr, "The result of this expression is ignored.\n\n"); - bfs_expr_free(expr); return ret; } } @@ -684,15 +658,15 @@ static struct bfs_expr *optimize_comma_expr(const struct bfs_opt *opt, struct bf if (bfs_expr_never_returns(lhs)) { opt_debug(opt, 1, "reachability: %pe <==> %pe\n", expr, lhs); opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); - return extract_child_expr(expr, &expr->lhs); + return expr->lhs; } else if ((lhs->always_true && rhs->eval_fn == eval_true) || (lhs->always_false && rhs->eval_fn == eval_false)) { opt_debug(opt, 1, "redundancy elimination: %pe <==> %pe\n", expr, lhs); - return extract_child_expr(expr, &expr->lhs); + return expr->lhs; } else if (optlevel >= 2 && lhs->pure) { opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); - return extract_child_expr(expr, &expr->rhs); + return expr->rhs; } } @@ -710,7 +684,7 @@ static struct bfs_expr *optimize_comma_expr_recursive(struct bfs_opt *opt, struc struct bfs_opt lhs_state = *opt; expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); if (!expr->lhs) { - goto fail; + return NULL; } struct bfs_opt rhs_state = *opt; @@ -719,14 +693,10 @@ static struct bfs_expr *optimize_comma_expr_recursive(struct bfs_opt *opt, struc expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); if (!expr->rhs) { - goto fail; + return NULL; } return optimize_comma_expr(opt, expr); - -fail: - bfs_expr_free(expr); - return NULL; } /** Optimize an icmp-style ([+-]N) expression. */ @@ -1213,10 +1183,9 @@ static struct bfs_expr *optimize_expr_recursive(struct bfs_opt *opt, struct bfs_ opt->after_false = opt->before; if (optlevel >= 2 && df_is_bottom(&opt->before)) { - struct bfs_expr *ret = opt_const(false); + struct bfs_expr *ret = opt_const(opt, false); opt_debug(opt, 2, "reachability: %pe --> %pe\n", expr, ret); opt_warning(opt, expr, "This expression is unreachable.\n\n"); - bfs_expr_free(expr); return ret; } @@ -1257,10 +1226,9 @@ static struct bfs_expr *optimize_expr_recursive(struct bfs_opt *opt, struct bfs_ if (df_is_bottom(&opt->after_true)) { if (expr->pure) { - struct bfs_expr *ret = opt_const(false); + struct bfs_expr *ret = opt_const(opt, false); opt_warning(opt, expr, "This expression is always false.\n\n"); opt_debug(opt, 2, "data flow: %pe --> %pe\n", expr, ret); - bfs_expr_free(expr); return ret; } else { expr->always_false = true; @@ -1268,10 +1236,9 @@ static struct bfs_expr *optimize_expr_recursive(struct bfs_opt *opt, struct bfs_ } } else if (df_is_bottom(&opt->after_false)) { if (expr->pure) { - struct bfs_expr *ret = opt_const(true); + struct bfs_expr *ret = opt_const(opt, true); opt_debug(opt, 2, "data flow: %pe --> %pe\n", expr, ret); opt_warning(opt, expr, "This expression is always true.\n\n"); - bfs_expr_free(expr); return ret; } else { expr->always_true = true; diff --git a/src/parse.c b/src/parse.c index 18a1064..17fe8ad 100644 --- a/src/parse.c +++ b/src/parse.c @@ -54,50 +54,6 @@ static char *fake_or_arg = "-o"; static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; -/** - * Create a new unary expression. - */ -static struct bfs_expr *new_unary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *rhs, char **argv) { - struct bfs_expr *expr = bfs_expr_new(eval_fn, 1, argv); - if (!expr) { - bfs_expr_free(rhs); - return NULL; - } - - expr->lhs = NULL; - expr->rhs = rhs; - bfs_assert(bfs_expr_is_parent(expr)); - - expr->persistent_fds = rhs->persistent_fds; - expr->ephemeral_fds = rhs->ephemeral_fds; - return expr; -} - -/** - * Create a new binary expression. - */ -static struct bfs_expr *new_binary_expr(bfs_eval_fn *eval_fn, struct bfs_expr *lhs, struct bfs_expr *rhs, char **argv) { - struct bfs_expr *expr = bfs_expr_new(eval_fn, 1, argv); - if (!expr) { - bfs_expr_free(rhs); - bfs_expr_free(lhs); - return NULL; - } - - expr->lhs = lhs; - expr->rhs = rhs; - bfs_assert(bfs_expr_is_parent(expr)); - - expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; - if (lhs->ephemeral_fds > rhs->ephemeral_fds) { - expr->ephemeral_fds = lhs->ephemeral_fds; - } else { - expr->ephemeral_fds = rhs->ephemeral_fds; - } - - return expr; -} - /** * Color use flags. */ @@ -343,6 +299,58 @@ static bool parse_expr_warning(const struct bfs_parser *parser, const struct bfs return ret; } +/** + * Allocate a new expression. + */ +static struct bfs_expr *parse_new_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc, char **argv) { + struct bfs_expr *expr = bfs_expr_new(parser->ctx, eval_fn, argc, argv); + if (!expr) { + parse_perror(parser, "bfs_expr_new()"); + } + return expr; +} + +/** + * Create a new unary expression. + */ +static struct bfs_expr *new_unary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *rhs, char **argv) { + struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv); + if (!expr) { + return NULL; + } + + expr->lhs = NULL; + expr->rhs = rhs; + bfs_assert(bfs_expr_is_parent(expr)); + + expr->persistent_fds = rhs->persistent_fds; + expr->ephemeral_fds = rhs->ephemeral_fds; + return expr; +} + +/** + * Create a new binary expression. + */ +static struct bfs_expr *new_binary_expr(const struct bfs_parser *parser, bfs_eval_fn *eval_fn, struct bfs_expr *lhs, struct bfs_expr *rhs, char **argv) { + struct bfs_expr *expr = parse_new_expr(parser, eval_fn, 1, argv); + if (!expr) { + return NULL; + } + + expr->lhs = lhs; + expr->rhs = rhs; + bfs_assert(bfs_expr_is_parent(expr)); + + expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; + if (lhs->ephemeral_fds > rhs->ephemeral_fds) { + expr->ephemeral_fds = lhs->ephemeral_fds; + } else { + expr->ephemeral_fds = rhs->ephemeral_fds; + } + + return expr; +} + /** * Fill in a "-print"-type expression. */ @@ -645,7 +653,7 @@ static bool looks_like_icmp(const char *str) { */ static struct bfs_expr *parse_flag(struct bfs_parser *parser, size_t argc) { char **argv = parser_advance(parser, T_FLAG, argc); - return bfs_expr_new(eval_true, argc, argv); + return parse_new_expr(parser, eval_true, argc, argv); } /** @@ -674,7 +682,7 @@ static struct bfs_expr *parse_unary_flag(struct bfs_parser *parser) { */ static struct bfs_expr *parse_option(struct bfs_parser *parser, size_t argc) { char **argv = parser_advance(parser, T_OPTION, argc); - return bfs_expr_new(eval_true, argc, argv); + return parse_new_expr(parser, eval_true, argc, argv); } /** @@ -703,7 +711,7 @@ static struct bfs_expr *parse_unary_option(struct bfs_parser *parser) { */ static struct bfs_expr *parse_test(struct bfs_parser *parser, bfs_eval_fn *eval_fn, size_t argc) { char **argv = parser_advance(parser, T_TEST, argc); - return bfs_expr_new(eval_fn, argc, argv); + return parse_new_expr(parser, eval_fn, argc, argv); } /** @@ -742,7 +750,7 @@ static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eva parser->implicit_print = false; } - return bfs_expr_new(eval_fn, argc, argv); + return parse_new_expr(parser, eval_fn, argc, argv); } /** @@ -771,7 +779,7 @@ static struct bfs_expr *parse_unary_action(struct bfs_parser *parser, bfs_eval_f */ static int parse_exclude(struct bfs_parser *parser, struct bfs_expr *expr) { struct bfs_ctx *ctx = parser->ctx; - ctx->exclude = new_binary_expr(eval_or, ctx->exclude, expr, &fake_or_arg); + ctx->exclude = new_binary_expr(parser, eval_or, ctx->exclude, expr, &fake_or_arg); if (ctx->exclude) { return 0; } else { @@ -789,7 +797,6 @@ static struct bfs_expr *parse_test_icmp(struct bfs_parser *parser, bfs_eval_fn * } if (!parse_icmp(parser, expr, 0)) { - bfs_expr_free(expr); return NULL; } @@ -848,7 +855,6 @@ static struct bfs_expr *parse_debug(struct bfs_parser *parser, int arg1, int arg if (parse_debug_flag(flag, len, "help")) { debug_help(ctx->cout); parser->just_info = true; - bfs_expr_free(expr); return NULL; } else if (parse_debug_flag(flag, len, "all")) { ctx->debug = DEBUG_ALL; @@ -896,7 +902,6 @@ static struct bfs_expr *parse_optlevel(struct bfs_parser *parser, int arg1, int if (strcmp(expr->argv[0], "-Ofast") == 0) { *optlevel = 4; } else if (!parse_int(parser, expr->argv, expr->argv[0] + 2, optlevel, IF_INT | IF_UNSIGNED)) { - bfs_expr_free(expr); return NULL; } @@ -963,16 +968,12 @@ static struct bfs_expr *parse_newer(struct bfs_parser *parser, int field, int ar struct bfs_stat sb; if (stat_arg(parser, &expr->argv[1], &sb) != 0) { - goto fail; + return NULL; } expr->reftime = sb.mtime; expr->stat_field = field; return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -1004,7 +1005,7 @@ static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg const char *tail = parse_icmp(parser, expr, IF_PARTIAL_OK); if (!tail) { - goto fail; + return NULL; } if (!*tail) { @@ -1033,7 +1034,7 @@ static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg break; default: parse_expr_error(parser, expr, "Unknown time unit ${bld}%c${rs}.\n", *tail); - goto fail; + return NULL; } expr->num += time; @@ -1044,20 +1045,16 @@ static struct bfs_expr *parse_time(struct bfs_parser *parser, int field, int arg tail = parse_int(parser, &expr->argv[1], tail, &time, IF_PARTIAL_OK | IF_LONG_LONG | IF_UNSIGNED); if (!tail) { - goto fail; + return NULL; } if (!*tail) { parse_expr_error(parser, expr, "Missing time unit.\n"); - goto fail; + return NULL; } } expr->time_unit = BFS_SECONDS; return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -1087,7 +1084,6 @@ static struct bfs_expr *parse_color(struct bfs_parser *parser, int color, int ar if (color) { if (!colors) { parse_expr_error(parser, expr, "Error parsing $$LS_COLORS: %s.\n", xstrerror(ctx->colors_error)); - bfs_expr_free(expr); return NULL; } @@ -1182,7 +1178,6 @@ static struct bfs_expr *parse_depth_limit(struct bfs_parser *parser, int is_min, int *depth = is_min ? &ctx->mindepth : &ctx->maxdepth; char **arg = &expr->argv[1]; if (!parse_int(parser, arg, *arg, depth, IF_INT | IF_UNSIGNED)) { - bfs_expr_free(expr); return NULL; } @@ -1235,7 +1230,7 @@ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg } else { parse_perror(parser, "strndup()"); } - goto fail; + return NULL; } path = strchr(path, ':'); @@ -1257,10 +1252,6 @@ static struct bfs_expr *parse_exec(struct bfs_parser *parser, int flags, int arg } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -1292,7 +1283,6 @@ static struct bfs_expr *parse_f(struct bfs_parser *parser, int arg1, int arg2) { } if (parse_root(parser, expr->argv[1]) != 0) { - bfs_expr_free(expr); return NULL; } @@ -1318,7 +1308,7 @@ static struct bfs_expr *parse_files0_from(struct bfs_parser *parser, int arg1, i } if (!file) { parse_expr_error(parser, expr, "%m.\n"); - goto fail; + return NULL; } while (true) { @@ -1348,10 +1338,9 @@ static struct bfs_expr *parse_files0_from(struct bfs_parser *parser, int arg1, i return expr; fail: - if (file && file != stdin) { + if (file != stdin) { fclose(file); } - bfs_expr_free(expr); return NULL; } @@ -1385,7 +1374,6 @@ static struct bfs_expr *parse_flags(struct bfs_parser *parser, int arg1, int arg } else { parse_expr_error(parser, expr, "Invalid flags.\n"); } - bfs_expr_free(expr); return NULL; } @@ -1398,18 +1386,14 @@ static struct bfs_expr *parse_flags(struct bfs_parser *parser, int arg1, int arg static struct bfs_expr *parse_fls(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(parser, eval_fls); if (!expr) { - goto fail; + return NULL; } if (expr_open(parser, expr, expr->argv[1]) != 0) { - goto fail; + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -1417,16 +1401,15 @@ fail: */ static struct bfs_expr *parse_fprint(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(parser, eval_fprint); - if (expr) { - if (expr_open(parser, expr, expr->argv[1]) != 0) { - goto fail; - } + if (!expr) { + return NULL; } - return expr; -fail: - bfs_expr_free(expr); - return NULL; + if (expr_open(parser, expr, expr->argv[1]) != 0) { + return NULL; + } + + return expr; } /** @@ -1434,16 +1417,15 @@ fail: */ static struct bfs_expr *parse_fprint0(struct bfs_parser *parser, int arg1, int arg2) { struct bfs_expr *expr = parse_unary_action(parser, eval_fprint0); - if (expr) { - if (expr_open(parser, expr, expr->argv[1]) != 0) { - goto fail; - } + if (!expr) { + return NULL; } - return expr; -fail: - bfs_expr_free(expr); - return NULL; + if (expr_open(parser, expr, expr->argv[1]) != 0) { + return NULL; + } + + return expr; } /** @@ -1470,18 +1452,14 @@ static struct bfs_expr *parse_fprintf(struct bfs_parser *parser, int arg1, int a } if (expr_open(parser, expr, file) != 0) { - goto fail; + return NULL; } if (bfs_printf_parse(parser->ctx, expr, format) != 0) { - goto fail; + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -1495,7 +1473,6 @@ static struct bfs_expr *parse_fstype(struct bfs_parser *parser, int arg1, int ar if (!bfs_ctx_mtab(parser->ctx)) { parse_expr_error(parser, expr, "Couldn't parse the mount table: %m.\n"); - bfs_expr_free(expr); return NULL; } @@ -1517,21 +1494,17 @@ static struct bfs_expr *parse_group(struct bfs_parser *parser, int arg1, int arg expr->int_cmp = BFS_INT_EQUAL; } else if (looks_like_icmp(expr->argv[1])) { if (!parse_icmp(parser, expr, 0)) { - goto fail; + return NULL; } } else if (errno) { parse_expr_error(parser, expr, "%m.\n"); - goto fail; + return NULL; } else { parse_expr_error(parser, expr, "No such group.\n"); - goto fail; + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -1564,21 +1537,17 @@ static struct bfs_expr *parse_user(struct bfs_parser *parser, int arg1, int arg2 expr->int_cmp = BFS_INT_EQUAL; } else if (looks_like_icmp(expr->argv[1])) { if (!parse_icmp(parser, expr, 0)) { - goto fail; + return NULL; } } else if (errno) { parse_expr_error(parser, expr, "%m.\n"); - goto fail; + return NULL; } else { parse_expr_error(parser, expr, "No such user.\n"); - goto fail; + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -1614,13 +1583,11 @@ static struct bfs_expr *parse_jobs(struct bfs_parser *parser, int arg1, int arg2 unsigned int n; if (!parse_int(parser, expr->argv, expr->argv[0] + 2, &n, IF_INT | IF_UNSIGNED)) { - bfs_expr_free(expr); return NULL; } if (n == 0) { parse_expr_error(parser, expr, "${bld}0${rs} is not enough threads.\n"); - bfs_expr_free(expr); return NULL; } @@ -1680,7 +1647,6 @@ static struct bfs_expr *parse_fnmatch(const struct bfs_parser *parser, struct bf expr->fnm_flags = FNM_CASEFOLD; #else parse_expr_error(parser, expr, "Missing platform support.\n"); - bfs_expr_free(expr); return NULL; #endif } else { @@ -1811,7 +1777,7 @@ static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int a struct bfs_expr *expr = parse_unary_test(parser, eval_newer); if (!expr) { - goto fail; + return NULL; } expr->stat_field = parse_newerxy_field(arg[6]); @@ -1819,12 +1785,12 @@ static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int a parse_expr_error(parser, expr, "For ${blu}-newer${bld}XY${rs}, ${bld}X${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, or ${bld}B${rs}, not ${err}%c${rs}.\n", arg[6]); - goto fail; + return NULL; } if (arg[7] == 't') { if (parse_reftime(parser, expr) != 0) { - goto fail; + return NULL; } } else { enum bfs_stat_field field = parse_newerxy_field(arg[7]); @@ -1832,28 +1798,24 @@ static struct bfs_expr *parse_newerxy(struct bfs_parser *parser, int arg1, int a parse_expr_error(parser, expr, "For ${blu}-newer${bld}XY${rs}, ${bld}Y${rs} should be ${bld}a${rs}, ${bld}c${rs}, ${bld}m${rs}, ${bld}B${rs}, or ${bld}t${rs}, not ${err}%c${rs}.\n", arg[7]); - goto fail; + return NULL; } struct bfs_stat sb; if (stat_arg(parser, &expr->argv[1], &sb) != 0) { - goto fail; + return NULL; } const struct timespec *reftime = bfs_stat_time(&sb, field); if (!reftime) { parse_expr_error(parser, expr, "Couldn't get file %s.\n", bfs_stat_field_name(field)); - goto fail; + return NULL; } expr->reftime = *reftime; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -1872,7 +1834,7 @@ static struct bfs_expr *parse_nogroup(struct bfs_parser *parser, int arg1, int a * Parse -nohidden. */ static struct bfs_expr *parse_nohidden(struct bfs_parser *parser, int arg1, int arg2) { - struct bfs_expr *hidden = bfs_expr_new(eval_hidden, 1, &fake_hidden_arg); + struct bfs_expr *hidden = parse_new_expr(parser, eval_hidden, 1, &fake_hidden_arg); if (!hidden) { return NULL; } @@ -2158,14 +2120,10 @@ static struct bfs_expr *parse_perm(struct bfs_parser *parser, int field, int arg } if (parse_mode(parser, mode, expr) != 0) { - goto fail; + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -2202,7 +2160,6 @@ static struct bfs_expr *parse_printf(struct bfs_parser *parser, int arg1, int ar init_print_expr(parser, expr); if (bfs_printf_parse(parser->ctx, expr, expr->argv[1]) != 0) { - bfs_expr_free(expr); return NULL; } @@ -2241,31 +2198,26 @@ static struct bfs_expr *parse_quit(struct bfs_parser *parser, int arg1, int arg2 static struct bfs_expr *parse_regex(struct bfs_parser *parser, int flags, int arg2) { struct bfs_expr *expr = parse_unary_test(parser, eval_regex); if (!expr) { - goto fail; + return NULL; } if (bfs_regcomp(&expr->regex, expr->argv[1], parser->regex_type, flags) != 0) { - if (!expr->regex) { + if (expr->regex) { + char *str = bfs_regerror(expr->regex); + if (str) { + parse_expr_error(parser, expr, "%s.\n", str); + free(str); + } else { + parse_perror(parser, "bfs_regerror()"); + } + } else { parse_perror(parser, "bfs_regcomp()"); - goto fail; } - char *str = bfs_regerror(expr->regex); - if (!str) { - parse_perror(parser, "bfs_regerror()"); - goto fail; - } - - parse_expr_error(parser, expr, "%s.\n", str); - free(str); - goto fail; + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -2324,8 +2276,6 @@ list_types: cfprintf(cfile, " ${bld}grep${rs}: Like ${grn}grep${rs}\n"); #endif cfprintf(cfile, " ${bld}sed${rs}: Like ${grn}sed${rs} (same as ${bld}posix-basic${rs})\n"); - - bfs_expr_free(expr); return NULL; } @@ -2348,7 +2298,6 @@ static struct bfs_expr *parse_samefile(struct bfs_parser *parser, int arg1, int struct bfs_stat sb; if (stat_arg(parser, &expr->argv[1], &sb) != 0) { - bfs_expr_free(expr); return NULL; } @@ -2396,8 +2345,6 @@ list_strategies: cfprintf(cfile, " ${bld}dfs${rs}: depth-first search\n"); cfprintf(cfile, " ${bld}ids${rs}: iterative deepening search\n"); cfprintf(cfile, " ${bld}eds${rs}: exponential deepening search\n"); - - bfs_expr_free(expr); return NULL; } @@ -2411,15 +2358,11 @@ static struct bfs_expr *parse_since(struct bfs_parser *parser, int field, int ar } if (parse_reftime(parser, expr) != 0) { - goto fail; + return NULL; } expr->stat_field = field; return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -2433,7 +2376,7 @@ static struct bfs_expr *parse_size(struct bfs_parser *parser, int arg1, int arg2 const char *unit = parse_icmp(parser, expr, IF_PARTIAL_OK); if (!unit) { - goto fail; + return NULL; } if (strlen(unit) > 1) { @@ -2468,16 +2411,12 @@ static struct bfs_expr *parse_size(struct bfs_parser *parser, int arg1, int arg2 break; default: - goto bad_unit; + bad_unit: + parse_expr_error(parser, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%pq${rs}.\n", unit); + return NULL; } return expr; - -bad_unit: - parse_expr_error(parser, expr, "Expected a size unit (one of ${bld}cwbkMGTP${rs}); found ${err}%pq${rs}.\n", unit); -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -2543,11 +2482,11 @@ static struct bfs_expr *parse_type(struct bfs_parser *parser, int x, int arg2) { case '\0': parse_expr_error(parser, expr, "Expected a type flag.\n"); - goto fail; + return NULL; default: parse_expr_error(parser, expr, "Unknown type flag ${err}%c${rs}; expected one of [${bld}bcdpflsD${rs}].\n", *c); - goto fail; + return NULL; } ++c; @@ -2558,15 +2497,11 @@ static struct bfs_expr *parse_type(struct bfs_parser *parser, int x, int arg2) { continue; } else { parse_expr_error(parser, expr, "Types must be comma-separated.\n"); - goto fail; + return NULL; } } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } /** @@ -3244,14 +3179,12 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) { } if (skip_paths(parser) != 0) { - bfs_expr_free(expr); return NULL; } arg = parser->argv[0]; if (!arg || strcmp(arg, ")") != 0) { parse_argv_error(parser, parser->last_arg, 1, "Expected a ${red})${rs}.\n"); - bfs_expr_free(expr); return NULL; } @@ -3277,7 +3210,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) { return NULL; } - return bfs_expr_new(eval_true, parser->argv - argv, argv); + return parse_new_expr(parser, eval_true, parser->argv - argv, argv); } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { char **argv = parser_advance(parser, T_OPERATOR, 1); @@ -3286,7 +3219,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) { return NULL; } - return new_unary_expr(eval_not, factor, argv); + return new_unary_expr(parser, eval_not, factor, argv); } else { return parse_primary(parser); } @@ -3303,7 +3236,6 @@ static struct bfs_expr *parse_term(struct bfs_parser *parser) { while (term) { if (skip_paths(parser) != 0) { - bfs_expr_free(term); return NULL; } @@ -3326,11 +3258,10 @@ static struct bfs_expr *parse_term(struct bfs_parser *parser) { struct bfs_expr *lhs = term; struct bfs_expr *rhs = parse_factor(parser); if (!rhs) { - bfs_expr_free(lhs); return NULL; } - term = new_binary_expr(eval_and, lhs, rhs, argv); + term = new_binary_expr(parser, eval_and, lhs, rhs, argv); } return term; @@ -3346,7 +3277,6 @@ static struct bfs_expr *parse_clause(struct bfs_parser *parser) { while (clause) { if (skip_paths(parser) != 0) { - bfs_expr_free(clause); return NULL; } @@ -3364,11 +3294,10 @@ static struct bfs_expr *parse_clause(struct bfs_parser *parser) { struct bfs_expr *lhs = clause; struct bfs_expr *rhs = parse_term(parser); if (!rhs) { - bfs_expr_free(lhs); return NULL; } - clause = new_binary_expr(eval_or, lhs, rhs, argv); + clause = new_binary_expr(parser, eval_or, lhs, rhs, argv); } return clause; @@ -3383,7 +3312,6 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser) { while (expr) { if (skip_paths(parser) != 0) { - bfs_expr_free(expr); return NULL; } @@ -3401,11 +3329,10 @@ static struct bfs_expr *parse_expr(struct bfs_parser *parser) { struct bfs_expr *lhs = expr; struct bfs_expr *rhs = parse_clause(parser); if (!rhs) { - bfs_expr_free(lhs); return NULL; } - expr = new_binary_expr(eval_comma, lhs, rhs, argv); + expr = new_binary_expr(parser, eval_comma, lhs, rhs, argv); } return expr; @@ -3423,7 +3350,7 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { if (parser->argv[0]) { expr = parse_expr(parser); } else { - expr = bfs_expr_new(eval_true, 1, &fake_true_arg); + expr = parse_new_expr(parser, eval_true, 1, &fake_true_arg); } if (!expr) { return NULL; @@ -3431,19 +3358,19 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { if (parser->argv[0]) { parse_error(parser, "Unexpected argument.\n"); - goto fail; + return NULL; } if (parser->implicit_print) { - struct bfs_expr *print = bfs_expr_new(eval_fprint, 1, &fake_print_arg); + struct bfs_expr *print = parse_new_expr(parser, eval_fprint, 1, &fake_print_arg); if (!print) { - goto fail; + return NULL; } init_print_expr(parser, print); - expr = new_binary_expr(eval_and, expr, print, &fake_and_arg); + expr = new_binary_expr(parser, eval_and, expr, print, &fake_and_arg); if (!expr) { - goto fail; + return NULL; } } @@ -3461,7 +3388,7 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { if (parser->interactive) { bfs_warning(parser->ctx, "Do you want to continue? "); if (ynprompt() == 0) { - goto fail; + return NULL; } } @@ -3472,14 +3399,10 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { parse_conflict_error(parser, parser->ok_expr->argv, parser->ok_expr->argc, parser->files0_stdin_arg, 2, "${blu}%s${rs} conflicts with ${blu}%s${rs} ${bld}%s${rs}.\n", parser->ok_expr->argv[0], parser->files0_stdin_arg[0], parser->files0_stdin_arg[1]); - goto fail; + return NULL; } return expr; - -fail: - bfs_expr_free(expr); - return NULL; } static const char *bftw_strategy_name(enum bftw_strategy strategy) { @@ -3637,7 +3560,7 @@ static void dump_costs(const struct bfs_ctx *ctx) { struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { struct bfs_ctx *ctx = bfs_ctx_new(); if (!ctx) { - perror("bfs_new_ctx()"); + perror("bfs_ctx_new()"); goto fail; } @@ -3715,7 +3638,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .now = ctx->now, }; - ctx->exclude = bfs_expr_new(eval_false, 1, &fake_false_arg); + ctx->exclude = parse_new_expr(&parser, eval_false, 1, &fake_false_arg); if (!ctx->exclude) { goto fail; } @@ -3734,6 +3657,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { } if (bfs_optimize(ctx) != 0) { + bfs_perror(ctx, "bfs_optimize()"); goto fail; } -- cgit v1.2.3 From 9cf8ea46ecc274351db419c2ab8fdf68c2d223d8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 1 Jan 2024 23:46:11 -0500 Subject: tests: Remove unneeded command -v unbuffer || skip --- tests/bfs/nocolor_env.sh | 2 -- tests/bfs/nocolor_env_empty.sh | 2 -- 2 files changed, 4 deletions(-) diff --git a/tests/bfs/nocolor_env.sh b/tests/bfs/nocolor_env.sh index 0a17fb2..d1c2afb 100644 --- a/tests/bfs/nocolor_env.sh +++ b/tests/bfs/nocolor_env.sh @@ -1,5 +1,3 @@ -command -v unbuffer &>/dev/null || skip - NO_COLOR=1 bfs_pty rainbow >"$OUT" sort_output diff_output diff --git a/tests/bfs/nocolor_env_empty.sh b/tests/bfs/nocolor_env_empty.sh index 0ffc046..1edfb1d 100644 --- a/tests/bfs/nocolor_env_empty.sh +++ b/tests/bfs/nocolor_env_empty.sh @@ -1,5 +1,3 @@ -command -v unbuffer &>/dev/null || skip - NO_COLOR= bfs_pty rainbow >"$OUT" sort_output diff_output -- cgit v1.2.3 From 1117564e6f06860b826f0bc9d27e7d26b7e56eee Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 1 Jan 2024 23:43:47 -0500 Subject: ci: Update action and image versions --- .github/workflows/ci.yml | 12 ++++++------ .github/workflows/codecov.yml | 4 ++-- .github/workflows/codeql.yml | 8 ++++---- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bc0719e..38194f2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,10 +6,10 @@ jobs: linux: name: Linux - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: | @@ -48,7 +48,7 @@ jobs: runs-on: macos-13 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: | @@ -64,14 +64,14 @@ jobs: if: ${{ github.repository_owner == 'tavianator' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 concurrency: spurion steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - - uses: tailscale/github-action@main + - uses: tailscale/github-action@v2 with: oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 073479a..9c2119b 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -4,10 +4,10 @@ on: [push] jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Install dependencies run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3a2f81f..71073c6 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -13,7 +13,7 @@ on: jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: actions: read contents: read @@ -24,7 +24,7 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Install dependencies run: | @@ -41,7 +41,7 @@ jobs: liburing-dev - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: cpp queries: +security-and-quality @@ -51,6 +51,6 @@ jobs: make -j$(nproc) all - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 with: category: "/language:cpp" -- cgit v1.2.3 From ee200c07643801c8b53e5b80df704ecbf77a884e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 23 Dec 2023 18:13:20 -0500 Subject: ci: Use vmactions/freebsd-vm --- .github/workflows/ci.yml | 37 +++++++++++++++++++++---------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 38194f2..701eafc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,25 +62,30 @@ jobs: freebsd: name: FreeBSD - if: ${{ github.repository_owner == 'tavianator' && (github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.repository) }} - runs-on: ubuntu-22.04 - concurrency: spurion - steps: - uses: actions/checkout@v4 - - uses: tailscale/github-action@v2 - with: - oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }} - oauth-secret: ${{ secrets.TS_OAUTH_SECRET }} - tags: tag:ci - - name: Run tests - run: | - spurion=$(tailscale ip -6 spurion) - mkdir ~/.ssh - printf 'Host %s\n\tStrictHostKeyChecking=accept-new\n\tUser github\n' "$spurion" >~/.ssh/config - rsync -rl --delete . "[$spurion]:bfs" - ssh "$spurion" '. ~/.ssh/rc; gmake -C bfs -j$(nproc) distcheck CC=clang16 JOBS=-j$(nproc)' + uses: vmactions/freebsd-vm@v1 + with: + release: "14.0" + usesh: true + copyback: false + + prepare: | + pkg install -y \ + bash \ + expect \ + gmake \ + llvm16 \ + oniguruma \ + sudo \ + tcl-wrapper + pw useradd -n action -m -G wheel -s /usr/local/bin/bash + echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/local/etc/sudoers + + run: | + chown -R action:action . + su action -c 'gmake -j$(nproc) distcheck CC=clang16 JOBS=-j$(nproc)' -- cgit v1.2.3 From eae90d86b5e3dda10f541dadcea6462587ff2bfc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 2 Jan 2024 13:55:21 -0500 Subject: bfstd: Fix fflags type on OpenBSD --- src/bfstd.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index a3e22f5..c0e61cb 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -609,8 +609,14 @@ error: int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear) { #if BSD && !__GNU__ char *str_arg = (char *)*str; - unsigned long set_arg = 0; - unsigned long clear_arg = 0; + +#if __OpenBSD__ + typedef uint32_t fflags_t; +#else + typedef unsigned long fflags_t; +#endif + fflags_t set_arg = 0; + fflags_t clear_arg = 0; #if __NetBSD__ int ret = string_to_flags(&str_arg, &set_arg, &clear_arg); -- cgit v1.2.3 From 79f1521b0e628be72bed3a648f0ae90b62fc69b8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 2 Jan 2024 13:57:29 -0500 Subject: pwcache: Fix uninitialized pointers on OpenBSD POSIX specifies that the get{pw,gr}*_r() functions store a NULL pointer to *result on error. However, OpenBSD does not always do this[1][2]: > if (bufsize < GETGR_R_SIZE_MAX) > return ERANGE; Work around it by explicitly initializing ret to NULL. [1]: https://github.com/openbsd/src/blob/e4829a9cc666f01ca5062d7fc15c20ab2d69229e/lib/libc/gen/getgrent.c#L135-L136 [2]: https://github.com/openbsd/src/blob/e4829a9cc666f01ca5062d7fc15c20ab2d69229e/lib/libc/gen/getgrent.c#L183-L184 --- src/pwcache.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/pwcache.c b/src/pwcache.c index c728ba9..79437d8 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -90,7 +90,7 @@ struct bfs_users *bfs_users_new(void) { static void *bfs_getpwnam_impl(const void *key, void *ptr, size_t bufsize) { struct bfs_passwd *storage = ptr; - struct passwd *ret; + struct passwd *ret = NULL; errno = getpwnam_r(key, &storage->pwd, storage->buf, bufsize, &ret); return ret; } @@ -109,7 +109,7 @@ static void *bfs_getpwuid_impl(const void *key, void *ptr, size_t bufsize) { const uid_t *uid = key; struct bfs_passwd *storage = ptr; - struct passwd *ret; + struct passwd *ret = NULL; errno = getpwuid_r(*uid, &storage->pwd, storage->buf, bufsize, &ret); return ret; } @@ -171,7 +171,7 @@ struct bfs_groups *bfs_groups_new(void) { static void *bfs_getgrnam_impl(const void *key, void *ptr, size_t bufsize) { struct bfs_group *storage = ptr; - struct group *ret; + struct group *ret = NULL; errno = getgrnam_r(key, &storage->grp, storage->buf, bufsize, &ret); return ret; } @@ -190,7 +190,7 @@ static void *bfs_getgrgid_impl(const void *key, void *ptr, size_t bufsize) { const gid_t *gid = key; struct bfs_group *storage = ptr; - struct group *ret; + struct group *ret = NULL; errno = getgrgid_r(*gid, &storage->grp, storage->buf, bufsize, &ret); return ret; } -- cgit v1.2.3 From 683f2c41c72efcb82ce866e3dcc311ac9bd8b66d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 2 Jan 2024 10:26:07 -0500 Subject: ci: Add an OpenBSD job --- .github/workflows/ci.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 701eafc..ab8805b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -89,3 +89,33 @@ jobs: run: | chown -R action:action . su action -c 'gmake -j$(nproc) distcheck CC=clang16 JOBS=-j$(nproc)' + + openbsd: + name: OpenBSD + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Run tests + uses: vmactions/openbsd-vm@v1 + with: + release: "7.4" + usesh: true + copyback: false + + prepare: | + pkg_add \ + bash \ + expect \ + gmake \ + oniguruma + adduser -group USER -batch action wheel >/etc/doas.conf + + run: | + chown -R action:action . + jobs=$(sysctl -n hw.ncpu) + doas -u action gmake -j$jobs check JOBS=-j$jobs TEST_FLAGS="--sudo=doas --verbose=skipped" -- cgit v1.2.3 From f0d4c378f07195c2313b20c6b7072c1735359814 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 2 Jan 2024 19:31:48 -0500 Subject: config: Don't use target_clones on NetBSD --- src/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index 91cef71..7080fb6 100644 --- a/src/config.h +++ b/src/config.h @@ -287,7 +287,7 @@ typedef long double max_align_t; * Check if function multiversioning via GNU indirect functions (ifunc) is supported. */ #ifndef BFS_USE_TARGET_CLONES -# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__ || __NetBSD__) +# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) # define BFS_USE_TARGET_CLONES true # endif #endif -- cgit v1.2.3 From 3b2c1067c56f234580145244530237071a1af46d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 2 Jan 2024 18:32:41 -0500 Subject: config: Polyfill __NetBSD_Prereq__ --- src/config.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/config.h b/src/config.h index 7080fb6..7dd9dc2 100644 --- a/src/config.h +++ b/src/config.h @@ -149,6 +149,10 @@ # define __GLIBC_PREREQ(maj, min) false #endif +#ifndef __NetBSD_Prereq__ +# define __NetBSD_Prereq__(maj, min, patch) false +#endif + // Fundamental utilities /** -- cgit v1.2.3 From b12670c79356fa9eeca52bafb1bfadf4cd86a1d7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 2 Jan 2024 18:33:11 -0500 Subject: xspawn: Check for NetBSD 10 before using posix_spawn_..._fchdir() --- src/xspawn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xspawn.c b/src/xspawn.c index 40115a1..98f1f65 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -167,7 +167,7 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { } #ifndef BFS_HAS_POSIX_SPAWN_FCHDIR -# define BFS_HAS_POSIX_SPAWN_FCHDIR __NetBSD__ +# define BFS_HAS_POSIX_SPAWN_FCHDIR __NetBSD_Prereq__(10, 0, 0) #endif #ifndef BFS_HAS_POSIX_SPAWN_FCHDIR_NP -- cgit v1.2.3 From 7c232609183f41745d5a451fac34b41f59741e99 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 11:58:45 -0500 Subject: tests/bsd/type_w: Check for truncate(1) before using it --- tests/bsd/type_w.sh | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/bsd/type_w.sh b/tests/bsd/type_w.sh index aec9cb1..3aa50d5 100644 --- a/tests/bsd/type_w.sh +++ b/tests/bsd/type_w.sh @@ -5,7 +5,11 @@ command -v newfs &>/dev/null || skip cd "$TEST" # Create a ramdisk -truncate -s1M img +if command -v truncate &>/dev/null; then + truncate -s1M img +else + dd if=/dev/zero of=img bs=1k count=1k +fi md=$(bfs_sudo mdconfig img) || skip defer bfs_sudo mdconfig -du "$md" -- cgit v1.2.3 From b7aa47b983f2d80a7a80d7c026d648767e0f18e4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 12:01:06 -0500 Subject: tests/posix/name_bracket: Skip on NetBSD too --- tests/posix/name_bracket.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/posix/name_bracket.sh b/tests/posix/name_bracket.sh index 80ca186..e2f943d 100644 --- a/tests/posix/name_bracket.sh +++ b/tests/posix/name_bracket.sh @@ -1,5 +1,9 @@ -# fnmatch() is broken on macOS -test "$UNAME" = "Darwin" && skip +# fnmatch() is broken on some platforms +case "$UNAME" in + Darwin|NetBSD) + skip + ;; +esac # An unclosed [ should be matched literally bfs_diff weirdnames -name '[' -- cgit v1.2.3 From 6435684a7d515e18247ae1b3dd9ec8681fee22d0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 12:02:26 -0500 Subject: bit: Rename bswap{16,32,64}() NetBSD already defines this in , so pick names that don't conflict. --- src/bit.h | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/bit.h b/src/bit.h index 7f71d04..c469305 100644 --- a/src/bit.h +++ b/src/bit.h @@ -174,30 +174,30 @@ #endif #if __STDC_VERSION__ >= C23 -# define bswap16 stdc_memreverse8u16 -# define bswap32 stdc_memreverse8u32 -# define bswap64 stdc_memreverse8u64 +# define bswap_u16 stdc_memreverse8u16 +# define bswap_u32 stdc_memreverse8u32 +# define bswap_u64 stdc_memreverse8u64 #elif __GNUC__ -# define bswap16 __builtin_bswap16 -# define bswap32 __builtin_bswap32 -# define bswap64 __builtin_bswap64 +# define bswap_u16 __builtin_bswap16 +# define bswap_u32 __builtin_bswap32 +# define bswap_u64 __builtin_bswap64 #else -static inline uint16_t bswap16(uint16_t n) { +static inline uint16_t bswap_u16(uint16_t n) { return (n << 8) | (n >> 8); } -static inline uint32_t bswap32(uint32_t n) { - return ((uint32_t)bswap16(n) << 16) | bswap16(n >> 16); +static inline uint32_t bswap_u32(uint32_t n) { + return ((uint32_t)bswap_u16(n) << 16) | bswap_u16(n >> 16); } -static inline uint64_t bswap64(uint64_t n) { - return ((uint64_t)bswap32(n) << 32) | bswap32(n >> 32); +static inline uint64_t bswap_u64(uint64_t n) { + return ((uint64_t)bswap_u32(n) << 32) | bswap_u32(n >> 32); } #endif -static inline uint8_t bswap8(uint8_t n) { +static inline uint8_t bswap_u8(uint8_t n) { return n; } @@ -206,10 +206,10 @@ static inline uint8_t bswap8(uint8_t n) { */ #define bswap(n) \ _Generic((n), \ - uint8_t: bswap8, \ - uint16_t: bswap16, \ - uint32_t: bswap32, \ - uint64_t: bswap64)(n) + uint8_t: bswap_u8, \ + uint16_t: bswap_u16, \ + uint32_t: bswap_u32, \ + uint64_t: bswap_u64)(n) // Define an overload for each unsigned type #define UINT_OVERLOADS(macro) \ -- cgit v1.2.3 From 5b8c0174d936a6c89a6e98ceeb22b74cfc27595b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 2 Jan 2024 17:20:02 -0500 Subject: ci: Add a NetBSD job --- .github/workflows/ci.yml | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ab8805b..68b8dcd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -88,7 +88,7 @@ jobs: run: | chown -R action:action . - su action -c 'gmake -j$(nproc) distcheck CC=clang16 JOBS=-j$(nproc)' + sudo -u action gmake -j$(nproc) distcheck CC=clang16 JOBS=-j$(nproc) openbsd: name: OpenBSD @@ -119,3 +119,36 @@ jobs: chown -R action:action . jobs=$(sysctl -n hw.ncpu) doas -u action gmake -j$jobs check JOBS=-j$jobs TEST_FLAGS="--sudo=doas --verbose=skipped" + + netbsd: + name: NetBSD + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Run tests + uses: vmactions/netbsd-vm@v1 + with: + release: "9.3" + usesh: true + copyback: false + + prepare: | + PATH="/sbin:/usr/sbin:$PATH" + pkg_add \ + bash \ + clang \ + gmake \ + oniguruma \ + sudo \ + tcl-expect + useradd -m -G wheel -g =uid action + echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/pkg/etc/sudoers + + run: | + PATH="/sbin:/usr/sbin:$PATH" + chown -R action:action . + jobs=$(sysctl -n hw.ncpu) + sudo -u action gmake -j$jobs check CC=clang LDFLAGS="-rpath /usr/pkg/lib" JOBS=-j$jobs TEST_FLAGS="--sudo --verbose=skipped" -- cgit v1.2.3 From 08867473e75e8e20ca76c7fb181204839e28b271 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 15:00:00 -0500 Subject: config: Check for GCC >= 11 before using malloc attribute args --- src/config.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/config.h b/src/config.h index 7dd9dc2..ed9a869 100644 --- a/src/config.h +++ b/src/config.h @@ -253,10 +253,10 @@ typedef long double max_align_t; * Annotates allocator-like functions. */ #if __has_attribute(malloc) -# if __clang__ -# define attr_malloc(...) attr_nodiscard __attribute__((malloc)) -# else +# if __GNUC__ >= 11 # define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__))) +# else +# define attr_malloc(...) attr_nodiscard __attribute__((malloc)) # endif #else # define attr_malloc(...) attr_nodiscard -- cgit v1.2.3 From 976514edfb6063170049f561f5edbf330bbe4132 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 16:42:07 -0500 Subject: config: Disable xattrs on DragonFly BSD DragonFly is missing extattr_get_link() and extattr_list_{file,link}(). --- src/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index ed9a869..24c6258 100644 --- a/src/config.h +++ b/src/config.h @@ -103,7 +103,7 @@ # define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__) #endif #ifndef BFS_USE_SYS_EXTATTR_H -# define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H +# define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__) #endif #ifndef BFS_USE_SYS_MKDEV_H # define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H -- cgit v1.2.3 From d874481987c381fd572a6f3c43840c50111a9554 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 15:55:27 -0500 Subject: fsade: Fix ACL checks on DragonFly BSD --- GNUmakefile | 4 ++++ src/fsade.c | 54 +++++++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 49 insertions(+), 9 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index a4d1c2d..7680cb3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -160,6 +160,10 @@ ifeq ($(OS),NetBSD) LOCAL_LDLIBS += -lutil endif +ifeq ($(OS),DragonFly) +LOCAL_LDLIBS += -lposix1e +endif + ifneq ($(filter gcov,$(MAKECMDGOALS)),) LOCAL_CFLAGS += --coverage # gcov only intercepts fork()/exec() with -std=gnu* diff --git a/src/fsade.c b/src/fsade.c index a48eeb0..d6ef4b8 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -117,24 +117,60 @@ static bool is_absence_error(int error) { #if BFS_CAN_CHECK_ACL +/** Unified interface for incompatible acl_get_entry() implementations. */ +static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { +#if __DragonFly__ && !defined(ACL_FIRST_ENTRY) && !defined(ACL_NEXT_ENTRY) +# define ACL_FIRST_ENTRY 0 +# define ACL_NEXT_ENTRY 1 + + switch (which) { + case ACL_FIRST_ENTRY: + *entry = &acl->acl_entry[0]; + break; + case ACL_NEXT_ENTRY: + ++*entry; + break; + default: + errno = EINVAL; + return -1; + } + + acl_entry_t last = &acl->acl_entry[acl->acl_cnt]; + return *entry == last; +#else + int ret = acl_get_entry(acl, which, entry); +# if __APPLE__ + // POSIX.1e specifies a return value of 1 for success, but macOS returns 0 instead + return !ret; +# else + return ret; +# endif +#endif +} + +/** Unified interface for acl_get_tag_type(). */ +static int bfs_acl_tag_type(acl_entry_t entry, acl_tag_t *tag) { +#if __DragonFly__ + *tag = entry->ae_tag; + return 0; +#else + return acl_get_tag_type(entry, tag); +#endif +} + /** Check if a POSIX.1e ACL is non-trivial. */ static int bfs_check_posix1e_acl(acl_t acl, bool ignore_required) { int ret = 0; acl_entry_t entry; - for (int status = acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); -#if __APPLE__ - // POSIX.1e specifies a return value of 1 for success, but macOS - // returns 0 instead - status == 0; -#else + for (int status = bfs_acl_entry(acl, ACL_FIRST_ENTRY, &entry); status > 0; -#endif - status = acl_get_entry(acl, ACL_NEXT_ENTRY, &entry)) { + status = bfs_acl_entry(acl, ACL_NEXT_ENTRY, &entry)) + { #if defined(ACL_USER_OBJ) && defined(ACL_GROUP_OBJ) && defined(ACL_OTHER) if (ignore_required) { acl_tag_t tag; - if (acl_get_tag_type(entry, &tag) != 0) { + if (bfs_acl_tag_type(entry, &tag) != 0) { ret = -1; continue; } -- cgit v1.2.3 From 45fb1d952c3b262278a3b22e9c7d60cca19a5407 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 17:17:35 -0500 Subject: Work around DragonFly BSD kernel bug DragonFly's x86_64 assembly implementation of copyinstr() checks the wrong pointer when deciding whether to return EFAULT or ENAMETOOLONG, causing it to always return EFAULT for overlong paths. Work around it by treating EFAULT the same as ENAMETOOLONG on DragonFly. Link: https://twitter.com/tavianator/status/1742991411203485713 --- src/bftw.c | 14 +++++++++++++- tests/xtouch.c | 6 ++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/bftw.c b/src/bftw.c index 0b74cd9..355cb54 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -739,10 +739,22 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons } int fd = bftw_file_openat(state, file, base, at_path); - if (fd >= 0 || errno != ENAMETOOLONG) { + if (fd >= 0) { return fd; } + switch (errno) { + case ENAMETOOLONG: +#if __DragonFly__ + // https://twitter.com/tavianator/status/1742991411203485713 + case EFAULT: +#endif + break; + + default: + return -1; + } + // Handle ENAMETOOLONG by manually traversing the path component-by-component struct bftw_list parents; SLIST_INIT(&parents); diff --git a/tests/xtouch.c b/tests/xtouch.c index 260a3a3..ed8bbee 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -70,13 +70,19 @@ static int open_parent(const struct args *args, const char **path) { switch (errno) { case ENAMETOOLONG: +#if __DragonFly__ + // https://twitter.com/tavianator/status/1742991411203485713 + case EFAULT: +#endif break; + case ENOENT: if (args->flags & CREATE_PARENTS) { break; } else { goto err; } + default: goto err; } -- cgit v1.2.3 From fc4b011dc2e5ce95ade6bd6e359cf1500b898204 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 3 Jan 2024 12:33:16 -0500 Subject: ci: Add a DragonFly BSD job --- .github/workflows/ci.yml | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 68b8dcd..5c61dc5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,3 +152,34 @@ jobs: chown -R action:action . jobs=$(sysctl -n hw.ncpu) sudo -u action gmake -j$jobs check CC=clang LDFLAGS="-rpath /usr/pkg/lib" JOBS=-j$jobs TEST_FLAGS="--sudo --verbose=skipped" + + dragonflybsd: + name: DragonFly BSD + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Run tests + uses: vmactions/dragonflybsd-vm@v1 + with: + release: "6.4.0" + usesh: true + copyback: false + + prepare: | + pkg install -y \ + bash \ + expect \ + gmake \ + oniguruma \ + sudo \ + tcl-wrapper + pw useradd -n action -m -G wheel -s /usr/local/bin/bash + echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/local/etc/sudoers + + run: | + chown -R action:action . + jobs=$(sysctl -n hw.ncpu) + sudo -u action gmake -j$jobs check JOBS=-j$jobs TEST_FLAGS="--sudo --verbose=skipped" -- cgit v1.2.3 From 70acbc194fa1cc4972293d4e3affee5ba6fe5539 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 7 Jan 2024 12:11:04 -0500 Subject: list: New SLIST_HEAD() and SLIST_TAIL() macros --- src/list.h | 43 ++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/src/list.h b/src/list.h index 91f416f..02a7ba2 100644 --- a/src/list.h +++ b/src/list.h @@ -197,14 +197,51 @@ #define SLIST_CHECK_(list) \ (void)sizeof(list->tail - &list->head) +/** + * Get the head of a singly-linked list. + * + * @param list + * The list in question. + * @return + * The first item in the list. + */ +#define SLIST_HEAD(list) \ + SLIST_HEAD_((list)) + +#define SLIST_HEAD_(list) \ + (SLIST_CHECK_(list), list->head) + /** * Check if a singly-linked list is empty. */ #define SLIST_EMPTY(list) \ - SLIST_EMPTY_((list)) + (!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. + * + * @param list + * The list in question. + * @param node (optional) + * If specified, use item->node.next rather than item->next. + * @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_EMPTY_(list) \ - (SLIST_CHECK_(list), !list->head) +#define SLIST_TAIL__(list, next) \ + (list->head ? SLIST_CONTAINER_(list->tail, list->head, next) : NULL) /** * Check if an item is attached to a singly-linked list. -- cgit v1.2.3 From 4a36bb92a5bbdc41965a6d2c6eae6cdca5983474 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 7 Jan 2024 12:19:17 -0500 Subject: expr: Make expressions variadic Rather than only unary/binary expressions, we now support an arbitrary number of children. The optimizer has been re-written almost completely and now supports optimal reordering of longer expression chains, rather than just arm-swapping. Fixes #85. --- src/color.c | 39 +- src/ctx.c | 2 +- src/diag.c | 5 +- src/eval.c | 55 +- src/expr.c | 37 +- src/expr.h | 28 +- src/opt.c | 2468 +++++++++++++++++++++++++++++++++++++++-------------------- src/parse.c | 73 +- 8 files changed, 1768 insertions(+), 939 deletions(-) diff --git a/src/color.c b/src/color.c index 5247cbf..1f10c04 100644 --- a/src/color.c +++ b/src/color.c @@ -1109,7 +1109,11 @@ attr(printf(2, 3)) static int cbuff(CFILE *cfile, const char *format, ...); /** Dump a parsed expression tree, for debugging. */ -static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { +static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose, int depth) { + if (depth >= 2) { + return dstrcat(&cfile->buffer, "(...)"); + } + if (!expr) { return dstrcat(&cfile->buffer, "(null)"); } @@ -1118,13 +1122,7 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { return -1; } - const struct bfs_expr *lhs = NULL; - const struct bfs_expr *rhs = NULL; - if (bfs_expr_is_parent(expr)) { - lhs = expr->lhs; - rhs = expr->rhs; - if (cbuff(cfile, "${red}%pq${rs}", expr->argv[0]) < 0) { return -1; } @@ -1152,21 +1150,20 @@ static int print_expr(CFILE *cfile, const struct bfs_expr *expr, bool verbose) { } } - if (lhs) { - if (dstrcat(&cfile->buffer, " ") != 0) { - return -1; - } - if (print_expr(cfile, lhs, verbose) != 0) { - return -1; - } - } - - if (rhs) { + int count = 0; + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { if (dstrcat(&cfile->buffer, " ") != 0) { return -1; } - if (print_expr(cfile, rhs, verbose) != 0) { - return -1; + if (++count >= 3) { + if (dstrcat(&cfile->buffer, "...") != 0) { + return -1; + } + break; + } else { + if (print_expr(cfile, child, verbose, depth + 1) != 0) { + return -1; + } } } @@ -1276,12 +1273,12 @@ static int cvbuff(CFILE *cfile, const char *format, va_list args) { break; case 'e': - if (print_expr(cfile, va_arg(args, const struct bfs_expr *), false) != 0) { + if (print_expr(cfile, va_arg(args, const struct bfs_expr *), false, 0) != 0) { return -1; } break; case 'E': - if (print_expr(cfile, va_arg(args, const struct bfs_expr *), true) != 0) { + if (print_expr(cfile, va_arg(args, const struct bfs_expr *), true, 0) != 0) { return -1; } break; diff --git a/src/ctx.c b/src/ctx.c index a3c5140..1f3e72e 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -241,7 +241,7 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { free_colors(ctx->colors); - for_slist (struct bfs_expr, expr, &ctx->expr_list) { + for_slist (struct bfs_expr, expr, &ctx->expr_list, freelist) { bfs_expr_clear(expr); } arena_destroy(&ctx->expr_arena); diff --git a/src/diag.c b/src/diag.c index bf2343d..efa7ebd 100644 --- a/src/diag.c +++ b/src/diag.c @@ -158,9 +158,8 @@ static bool highlight_expr_recursive(const struct bfs_ctx *ctx, const struct bfs } } - if (bfs_expr_is_parent(expr)) { - ret |= highlight_expr_recursive(ctx, expr->lhs, args); - ret |= highlight_expr_recursive(ctx, expr->rhs, args); + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + ret |= highlight_expr_recursive(ctx, child, args); } return ret; diff --git a/src/eval.c b/src/eval.c index 372fcf5..49c4aff 100644 --- a/src/eval.c +++ b/src/eval.c @@ -372,11 +372,10 @@ static int eval_exec_finish(const struct bfs_expr *expr, const struct bfs_ctx *c } ret = -1; } - } else if (bfs_expr_is_parent(expr)) { - if (expr->lhs && eval_exec_finish(expr->lhs, ctx) != 0) { - ret = -1; - } - if (expr->rhs && eval_exec_finish(expr->rhs, ctx) != 0) { + } + + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + if (eval_exec_finish(child, ctx) != 0) { ret = -1; } } @@ -1045,50 +1044,48 @@ static bool eval_expr(struct bfs_expr *expr, struct bfs_eval *state) { * Evaluate a negation. */ bool eval_not(const struct bfs_expr *expr, struct bfs_eval *state) { - return !eval_expr(expr->rhs, state); + return !eval_expr(bfs_expr_children(expr), state); } /** * Evaluate a conjunction. */ bool eval_and(const struct bfs_expr *expr, struct bfs_eval *state) { - if (!eval_expr(expr->lhs, state)) { - return false; - } - - if (state->quit) { - return false; + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + if (!eval_expr(child, state) || state->quit) { + return false; + } } - return eval_expr(expr->rhs, state); + return true; } /** * Evaluate a disjunction. */ bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) { - if (eval_expr(expr->lhs, state)) { - return true; - } - - if (state->quit) { - return false; + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + if (eval_expr(child, state) || state->quit) { + return true; + } } - return eval_expr(expr->rhs, state); + return false; } /** * Evaluate the comma operator. */ bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) { - eval_expr(expr->lhs, state); - - if (state->quit) { - return false; + bool ret; + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + ret = eval_expr(child, state); + if (state->quit) { + break; + } } - return eval_expr(expr->rhs, state); + return ret; } /** Update the status bar. */ @@ -1571,12 +1568,8 @@ static bool eval_must_buffer(const struct bfs_expr *expr) { return true; } - if (bfs_expr_is_parent(expr)) { - if (expr->lhs && eval_must_buffer(expr->lhs)) { - return true; - } - - if (expr->rhs && eval_must_buffer(expr->rhs)) { + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + if (eval_must_buffer(child)) { return true; } } diff --git a/src/expr.c b/src/expr.c index 2002fc7..3e0033f 100644 --- a/src/expr.c +++ b/src/expr.c @@ -23,7 +23,12 @@ struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t expr->argc = argc; expr->argv = argv; expr->probability = 0.5; - SLIST_PREPEND(&ctx->expr_list, expr); + SLIST_PREPEND(&ctx->expr_list, expr, freelist); + + if (bfs_expr_is_parent(expr)) { + SLIST_INIT(&expr->children); + } + return expr; } @@ -34,6 +39,36 @@ bool bfs_expr_is_parent(const struct bfs_expr *expr) { || expr->eval_fn == eval_comma; } +struct bfs_expr *bfs_expr_children(const struct bfs_expr *expr) { + if (bfs_expr_is_parent(expr)) { + return expr->children.head; + } else { + return NULL; + } +} + +void bfs_expr_append(struct bfs_expr *expr, struct bfs_expr *child) { + bfs_assert(bfs_expr_is_parent(expr)); + + SLIST_APPEND(&expr->children, child); + + if (!child->pure) { + expr->pure = false; + } + + expr->persistent_fds += child->persistent_fds; + if (expr->ephemeral_fds < child->ephemeral_fds) { + expr->ephemeral_fds = child->ephemeral_fds; + } +} + +void bfs_expr_extend(struct bfs_expr *expr, struct bfs_exprs *children) { + while (!SLIST_EMPTY(children)) { + struct bfs_expr *child = SLIST_POP(children); + bfs_expr_append(expr, child); + } +} + bool bfs_expr_never_returns(const struct bfs_expr *expr) { // Expressions that never return are vacuously both always true and always false return expr->always_true && expr->always_false; diff --git a/src/expr.h b/src/expr.h index 290f1f8..4d607a4 100644 --- a/src/expr.h +++ b/src/expr.h @@ -86,8 +86,10 @@ struct bfs_exprs { * A command line expression. */ struct bfs_expr { - /** The next allocated expression. */ + /** This expression's next sibling, if any. */ struct bfs_expr *next; + /** The next allocated expression. */ + struct { struct bfs_expr *next; } freelist; /** The function that evaluates this expression. */ bfs_eval_fn *eval_fn; @@ -123,12 +125,7 @@ struct bfs_expr { /** Auxilliary data for the evaluation function. */ union { /** Child expressions. */ - struct { - /** The left hand side of the expression. */ - struct bfs_expr *lhs; - /** The right hand side of the expression. */ - struct bfs_expr *rhs; - }; + struct bfs_exprs children; /** Integer comparisons. */ struct { @@ -218,10 +215,25 @@ struct bfs_ctx; struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval, size_t argc, char **argv); /** - * @return Whether the expression has child expressions. + * @return Whether this type of expression has children. */ bool bfs_expr_is_parent(const struct bfs_expr *expr); +/** + * @return The first child of this expression, or NULL if it has none. + */ +struct bfs_expr *bfs_expr_children(const struct bfs_expr *expr); + +/** + * Add a child to an expression. + */ +void bfs_expr_append(struct bfs_expr *expr, struct bfs_expr *child); + +/** + * Add a list of children to an expression. + */ +void bfs_expr_extend(struct bfs_expr *expr, struct bfs_exprs *children); + /** * @return Whether expr is known to always quit. */ diff --git a/src/opt.c b/src/opt.c index 1dc985a..7203c61 100644 --- a/src/opt.c +++ b/src/opt.c @@ -21,11 +21,12 @@ * -bar is likely to return false. * * -O4/-Ofast: aggressive optimizations that may affect correctness in corner - * cases. The main effect is to use impure to determine if any side-effects are - * reachable at all, and skipping the traversal if not. + * cases. The main effect is to use opt->impure to determine if any side- + * effects are reachable at all, skipping the traversal if not. */ #include "opt.h" +#include "bit.h" #include "color.h" #include "config.h" #include "ctx.h" @@ -33,6 +34,7 @@ #include "eval.h" #include "exec.h" #include "expr.h" +#include "list.h" #include "pwcache.h" #include #include @@ -41,9 +43,9 @@ #include #include -static char *fake_and_arg = "-a"; -static char *fake_or_arg = "-o"; -static char *fake_not_arg = "!"; +static char *fake_and_arg = "-and"; +static char *fake_or_arg = "-or"; +static char *fake_not_arg = "-not"; /** * The data flow domain for predicates. @@ -69,6 +71,70 @@ static void pred_join(enum df_pred *dest, enum df_pred src) { *dest |= src; } +/** + * Types of predicates we track. + */ +enum pred_type { + /** -readable */ + READABLE_PRED, + /** -writable */ + WRITABLE_PRED, + /** -executable */ + EXECUTABLE_PRED, + /** -acl */ + ACL_PRED, + /** -capable */ + CAPABLE_PRED, + /** -empty */ + EMPTY_PRED, + /** -hidden */ + HIDDEN_PRED, + /** -nogroup */ + NOGROUP_PRED, + /** -nouser */ + NOUSER_PRED, + /** -sparse */ + SPARSE_PRED, + /** -xattr */ + XATTR_PRED, + /** The number of pred_types. */ + 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 "???"; +} + /** * A contrained integer range. */ @@ -97,6 +163,11 @@ static void range_init_top(struct df_range *range) { range->max = LLONG_MAX; } +/** Check for an infinite range. */ +static bool range_is_top(const struct df_range *range) { + return range->min == 0 && range->max == LLONG_MAX; +} + /** Compute the minimum of two values. */ static long long min_value(long long a, long long b) { if (a < b) { @@ -170,35 +241,29 @@ enum range_type { RANGE_TYPES, }; -/** - * Types of predicates we track. - */ -enum pred_type { - /** -readable */ - READABLE_PRED, - /** -writable */ - WRITABLE_PRED, - /** -executable */ - EXECUTABLE_PRED, - /** -acl */ - ACL_PRED, - /** -capable */ - CAPABLE_PRED, - /** -empty */ - EMPTY_PRED, - /** -hidden */ - HIDDEN_PRED, - /** -nogroup */ - NOGROUP_PRED, - /** -nouser */ - NOUSER_PRED, - /** -sparse */ - SPARSE_PRED, - /** -xattr */ - XATTR_PRED, - /** The number of pred_types. */ - PRED_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 "???"; +} /** * The data flow analysis domain. @@ -210,9 +275,9 @@ struct df_domain { /** The value ranges we track. */ struct df_range ranges[RANGE_TYPES]; - /** Bitmask of possible file types. */ + /** Bitmask of possible -types. */ unsigned int types; - /** Bitmask of possible link target types. */ + /** Bitmask of possible -xtypes. */ unsigned int xtypes; }; @@ -265,6 +330,31 @@ static void df_init_top(struct df_domain *value) { value->xtypes = ~0; } +/** 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 < RANGE_TYPES; ++i) { + if (!range_is_top(&value->ranges[i])) { + return false; + } + } + + if (value->types != ~0U) { + return false; + } + + if (value->xtypes != ~0U) { + return false; + } + + return true; +} + /** Compute the union of two fact sets. */ static void df_join(struct df_domain *dest, const struct df_domain *src) { for (int i = 0; i < PRED_TYPES; ++i) { @@ -285,6 +375,15 @@ static void df_join(struct df_domain *dest, const struct df_domain *src) { struct bfs_opt { /** The context we're optimizing. */ struct bfs_ctx *ctx; + /** Optimization level (ctx->optlevel). */ + int level; + /** Recursion depth. */ + int depth; + + /** Whether to produce warnings. */ + bool warn; + /** Whether the result of this expression is ignored. */ + bool ignore_result; /** Data flow state before this expression is evaluated. */ struct df_domain before; @@ -296,18 +395,14 @@ struct bfs_opt { struct df_domain *impure; }; -/** Constrain the value of a predicate. */ -static void opt_constrain_pred(struct bfs_opt *opt, enum pred_type type, bool value) { - constrain_pred(&opt->after_true.preds[type], value); - constrain_pred(&opt->after_false.preds[type], !value); -} - /** Log an optimization. */ -attr(printf(3, 4)) -static bool opt_debug(const struct bfs_opt *opt, int level, const char *format, ...) { - bfs_assert(opt->ctx->optlevel >= level); +attr(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) { + cfprintf(opt->ctx->cerr, "│ "); + } - if (bfs_debug(opt->ctx, DEBUG_OPT, "${cyn}-O%d${rs}: ", level)) { va_list args; va_start(args, format); cvfprintf(opt->ctx->cerr, format, args); @@ -318,450 +413,577 @@ static bool opt_debug(const struct bfs_opt *opt, int level, const char *format, } } -/** Warn about an expression. */ -attr(printf(3, 4)) -static void opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, const char *format, ...) { - if (bfs_expr_warning(opt->ctx, expr)) { +/** Log a recursive call. */ +attr(printf(2, 3)) +static bool opt_enter(struct bfs_opt *opt, const char *format, ...) { + int depth = opt->depth; + if (depth > 0) { + --opt->depth; + } + + bool debug = opt_debug(opt, "%s", depth > 0 ? "├─╮ " : ""); + if (debug) { va_list args; va_start(args, format); - bfs_vwarning(opt->ctx, format, args); + cvfprintf(opt->ctx->cerr, format, args); va_end(args); } -} -/** Create a constant expression. */ -static struct bfs_expr *opt_const(const struct bfs_opt *opt, bool value) { - static bfs_eval_fn *fns[] = {eval_false, eval_true}; - static char *fake_args[] = {"-false", "-true"}; - return bfs_expr_new(opt->ctx, fns[value], 1, &fake_args[value]); + opt->depth = depth + 1; + return debug; } -/** - * Negate an expression. - */ -static struct bfs_expr *negate_expr(const struct bfs_opt *opt, struct bfs_expr *rhs, char **argv) { - if (rhs->eval_fn == eval_not) { - return rhs->rhs; - } +/** Log a recursive return. */ +attr(printf(2, 3)) +static bool opt_leave(struct bfs_opt *opt, const char *format, ...) { + bool debug = false; + int depth = opt->depth; - struct bfs_expr *expr = bfs_expr_new(opt->ctx, eval_not, 1, argv); - if (!expr) { - return NULL; + if (format) { + if (depth > 1) { + opt->depth -= 2; + } + + debug = opt_debug(opt, "%s", depth > 1 ? "├─╯ " : ""); + if (debug) { + va_list args; + va_start(args, format); + cvfprintf(opt->ctx->cerr, format, args); + va_end(args); + } } - expr->lhs = NULL; - expr->rhs = rhs; - return expr; + opt->depth = depth - 1; + return debug; } -static struct bfs_expr *optimize_not_expr(const struct bfs_opt *opt, struct bfs_expr *expr); -static struct bfs_expr *optimize_and_expr(const struct bfs_opt *opt, struct bfs_expr *expr); -static struct bfs_expr *optimize_or_expr(const struct bfs_opt *opt, struct bfs_expr *expr); - -/** - * Apply De Morgan's laws. - */ -static struct bfs_expr *de_morgan(const struct bfs_opt *opt, struct bfs_expr *expr, char **argv) { - bool debug = opt_debug(opt, 1, "De Morgan's laws: %pe ", expr); - - struct bfs_expr *parent = negate_expr(opt, expr, argv); - if (!parent) { - return NULL; +/** Log a shallow visit. */ +attr(printf(2, 3)) +static bool opt_visit(struct bfs_opt *opt, const char *format, ...) { + int depth = opt->depth; + if (depth > 0) { + --opt->depth; } - bool has_parent = true; - if (parent->eval_fn != eval_not) { - expr = parent; - has_parent = false; + bool debug = opt_debug(opt, "%s", depth > 0 ? "├─◯ " : ""); + if (debug) { + va_list args; + va_start(args, format); + cvfprintf(opt->ctx->cerr, format, args); + va_end(args); } - bfs_assert(expr->eval_fn == eval_and || expr->eval_fn == eval_or); - if (expr->eval_fn == eval_and) { - expr->eval_fn = eval_or; - expr->argv = &fake_or_arg; - } else { - expr->eval_fn = eval_and; - expr->argv = &fake_and_arg; - } + opt->depth = depth; + return debug; +} - expr->lhs = negate_expr(opt, expr->lhs, argv); - expr->rhs = negate_expr(opt, expr->rhs, argv); - if (!expr->lhs || !expr->rhs) { - return NULL; +/** Log the deletion of an expression. */ +attr(printf(2, 3)) +static bool opt_delete(struct bfs_opt *opt, const char *format, ...) { + int depth = opt->depth; + + if (depth > 0) { + --opt->depth; } + bool debug = opt_debug(opt, "%s", depth > 0 ? "├─✘ " : ""); if (debug) { - cfprintf(opt->ctx->cerr, "<==> %pe\n", parent); + va_list args; + va_start(args, format); + cvfprintf(opt->ctx->cerr, format, args); + va_end(args); } - if (expr->lhs->eval_fn == eval_not) { - expr->lhs = optimize_not_expr(opt, expr->lhs); - } - if (expr->rhs->eval_fn == eval_not) { - expr->rhs = optimize_not_expr(opt, expr->rhs); - } - if (!expr->lhs || !expr->rhs) { - return NULL; - } + opt->depth = depth; + return debug; +} - if (expr->eval_fn == eval_and) { - expr = optimize_and_expr(opt, expr); - } else { - expr = optimize_or_expr(opt, expr); +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)); + + FILE *file = opt->ctx->cerr->file; + switch (value->preds[type]) { + case PRED_BOTTOM: + fprintf(file, "⊥\n"); + break; + case PRED_TOP: + fprintf(file, "⊤\n"); + break; + case PRED_TRUE: + fprintf(file, "true\n"); + break; + case PRED_FALSE: + fprintf(file, "false\n"); + break; } - if (has_parent) { - parent->rhs = expr; +} + +/** 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)); + + FILE *file = opt->ctx->cerr->file; + const struct df_range *range = &value->ranges[type]; + if (range_is_bottom(range)) { + fprintf(file, "⊥\n"); + } else if (range_is_top(range)) { + fprintf(file, "⊤\n"); + } else if (range->min == range->max) { + fprintf(file, "%lld\n", range->min); } else { - parent = expr; - } - if (!expr) { - return NULL; + if (range->min == LLONG_MIN) { + fprintf(file, "(-∞, "); + } else { + fprintf(file, "[%lld, ", range->min); + } + if (range->max == LLONG_MAX) { + fprintf(file, "∞)\n"); + } else { + fprintf(file, "%lld]\n", range->max); + } } +} - if (has_parent) { - parent = optimize_not_expr(opt, parent); +/** Print a set of types. */ +static void types_dump(dump_fn *dump, struct bfs_opt *opt, const char *name, unsigned int types) { + dump(opt, "${blu}%s${rs}: ", name); + + FILE *file = opt->ctx->cerr->file; + if (types == 0) { + fprintf(file, " ⊥\n"); + } else if (types == ~0U) { + fprintf(file, " ⊤\n"); + } else if (count_ones(types) < count_ones(~types)) { + fprintf(file, " 0x%X\n", types); + } else { + fprintf(file, "~0x%X\n", ~types); } - return parent; } -/** Optimize an expression recursively. */ -static struct bfs_expr *optimize_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr); +/** Calculate the number of lines of df_dump() output. */ +static int df_dump_lines(const struct df_domain *value) { + int lines = 0; -/** - * Optimize a negation. - */ -static struct bfs_expr *optimize_not_expr(const struct bfs_opt *opt, struct bfs_expr *expr) { - bfs_assert(expr->eval_fn == eval_not); - - struct bfs_expr *rhs = expr->rhs; - - int optlevel = opt->ctx->optlevel; - if (optlevel >= 1) { - if (rhs->eval_fn == eval_true || rhs->eval_fn == eval_false) { - struct bfs_expr *ret = opt_const(opt, rhs->eval_fn == eval_false); - opt_debug(opt, 1, "constant propagation: %pe <==> %pe\n", expr, ret); - return ret; - } else if (rhs->eval_fn == eval_not) { - opt_debug(opt, 1, "double negation: %pe <==> %pe\n", expr, rhs->rhs); - return rhs->rhs; - } else if (bfs_expr_never_returns(rhs)) { - opt_debug(opt, 1, "reachability: %pe <==> %pe\n", expr, rhs); - return expr->rhs; - } else if ((rhs->eval_fn == eval_and || rhs->eval_fn == eval_or) - && (rhs->lhs->eval_fn == eval_not || rhs->rhs->eval_fn == eval_not)) { - return de_morgan(opt, expr, expr->argv); - } + for (int i = 0; i < PRED_TYPES; ++i) { + lines += value->preds[i] != PRED_TOP; } - expr->pure = rhs->pure; - expr->always_true = rhs->always_false; - expr->always_false = rhs->always_true; - expr->cost = rhs->cost; - expr->probability = 1.0 - rhs->probability; + for (int i = 0; i < RANGE_TYPES; ++i) { + lines += !range_is_top(&value->ranges[i]); + } - return expr; + lines += value->types != ~0U; + lines += value->xtypes != ~0U; + + return lines; } -/** Optimize a negation recursively. */ -static struct bfs_expr *optimize_not_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { - struct bfs_opt rhs_state = *opt; - expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); - if (!expr->rhs) { - return NULL; +/** Get the right debugging function for a df_dump() line. */ +static dump_fn *df_dump_line(int lines, int *line) { + ++*line; + + if (lines == 1) { + return opt_visit; + } else if (*line == 1) { + return opt_enter; + } else if (*line == lines) { + return opt_leave; + } else { + return opt_debug; } +} - opt->after_true = rhs_state.after_false; - opt->after_false = rhs_state.after_true; - - return optimize_not_expr(opt, expr); -} - -/** Optimize a conjunction. */ -static struct bfs_expr *optimize_and_expr(const struct bfs_opt *opt, struct bfs_expr *expr) { - bfs_assert(expr->eval_fn == eval_and); - - struct bfs_expr *lhs = expr->lhs; - struct bfs_expr *rhs = expr->rhs; - - const struct bfs_ctx *ctx = opt->ctx; - int optlevel = ctx->optlevel; - if (optlevel >= 1) { - if (lhs->eval_fn == eval_true) { - opt_debug(opt, 1, "conjunction elimination: %pe <==> %pe\n", expr, rhs); - return expr->rhs; - } else if (rhs->eval_fn == eval_true) { - opt_debug(opt, 1, "conjunction elimination: %pe <==> %pe\n", expr, lhs); - return expr->lhs; - } else if (lhs->always_false) { - opt_debug(opt, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); - opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); - return expr->lhs; - } else if (lhs->always_true && rhs->eval_fn == eval_false) { - bool debug = opt_debug(opt, 1, "strength reduction: %pe <==> ", expr); - struct bfs_expr *ret = expr->lhs; - ret = negate_expr(opt, ret, &fake_not_arg); - if (debug && ret) { - cfprintf(ctx->cerr, "%pe\n", ret); - } - return ret; - } else if (optlevel >= 2 && lhs->pure && rhs->eval_fn == eval_false) { - opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); - opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); - return expr->rhs; - } else if (lhs->eval_fn == eval_not && rhs->eval_fn == eval_not) { - return de_morgan(opt, expr, expr->lhs->argv); - } +/** Print a data flow value. */ +static void df_dump(struct bfs_opt *opt, const char *str, const struct df_domain *value) { + if (df_is_bottom(value)) { + opt_debug(opt, "%s: ⊥\n", str); + return; + } else if (df_is_top(value)) { + opt_debug(opt, "%s: ⊤\n", str); + return; } - expr->pure = lhs->pure && rhs->pure; - expr->always_true = lhs->always_true && rhs->always_true; - expr->always_false = lhs->always_false || rhs->always_false; - expr->cost = lhs->cost + lhs->probability * rhs->cost; - expr->probability = lhs->probability * rhs->probability; + if (!opt_debug(opt, "%s:\n", str)) { + return; + } - return expr; -} + int lines = df_dump_lines(value); + int line = 0; -/** Optimize a conjunction recursively. */ -static struct bfs_expr *optimize_and_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { - struct bfs_opt lhs_state = *opt; - expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); - if (!expr->lhs) { - return NULL; + for (int i = 0; i < PRED_TYPES; ++i) { + if (value->preds[i] != PRED_TOP) { + pred_dump(df_dump_line(lines, &line), opt, value, i); + } } - struct bfs_opt rhs_state = *opt; - rhs_state.before = lhs_state.after_true; - expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); - if (!expr->rhs) { - return NULL; + for (int i = 0; i < RANGE_TYPES; ++i) { + if (!range_is_top(&value->ranges[i])) { + range_dump(df_dump_line(lines, &line), opt, value, i); + } } - opt->after_true = rhs_state.after_true; - opt->after_false = lhs_state.after_false; - df_join(&opt->after_false, &rhs_state.after_false); - - return optimize_and_expr(opt, expr); -} - -/** Optimize a disjunction. */ -static struct bfs_expr *optimize_or_expr(const struct bfs_opt *opt, struct bfs_expr *expr) { - bfs_assert(expr->eval_fn == eval_or); - - struct bfs_expr *lhs = expr->lhs; - struct bfs_expr *rhs = expr->rhs; - - const struct bfs_ctx *ctx = opt->ctx; - int optlevel = ctx->optlevel; - if (optlevel >= 1) { - if (lhs->always_true) { - opt_debug(opt, 1, "short-circuit: %pe <==> %pe\n", expr, lhs); - opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); - return expr->lhs; - } else if (lhs->eval_fn == eval_false) { - opt_debug(opt, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, rhs); - return expr->rhs; - } else if (rhs->eval_fn == eval_false) { - opt_debug(opt, 1, "disjunctive syllogism: %pe <==> %pe\n", expr, lhs); - return expr->lhs; - } else if (lhs->always_false && rhs->eval_fn == eval_true) { - bool debug = opt_debug(opt, 1, "strength reduction: %pe <==> ", expr); - struct bfs_expr *ret = expr->lhs; - ret = negate_expr(opt, ret, &fake_not_arg); - if (debug && ret) { - cfprintf(ctx->cerr, "%pe\n", ret); - } - return ret; - } else if (optlevel >= 2 && lhs->pure && rhs->eval_fn == eval_true) { - opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); - opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); - return expr->rhs; - } else if (lhs->eval_fn == eval_not && rhs->eval_fn == eval_not) { - return de_morgan(opt, expr, expr->lhs->argv); - } + if (value->types != ~0U) { + types_dump(df_dump_line(lines, &line), opt, "-type", value->types); } - expr->pure = lhs->pure && rhs->pure; - expr->always_true = lhs->always_true || rhs->always_true; - expr->always_false = lhs->always_false && rhs->always_false; - expr->cost = lhs->cost + (1 - lhs->probability) * rhs->cost; - expr->probability = lhs->probability + rhs->probability - lhs->probability * rhs->probability; + if (value->xtypes != ~0U) { + types_dump(df_dump_line(lines, &line), opt, "-xtype", value->xtypes); + } +} - return expr; +/** Check if an expression is constant. */ +static bool is_const(const struct bfs_expr *expr) { + return expr->eval_fn == eval_true || expr->eval_fn == eval_false; } -/** Optimize a disjunction recursively. */ -static struct bfs_expr *optimize_or_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { - struct bfs_opt lhs_state = *opt; - expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); - if (!expr->lhs) { - return NULL; +/** Warn about an expression. */ +attr(printf(3, 4)) +static void opt_warning(const struct bfs_opt *opt, const struct bfs_expr *expr, const char *format, ...) { + if (!opt->warn) { + return; } - struct bfs_opt rhs_state = *opt; - rhs_state.before = lhs_state.after_false; - expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); - if (!expr->rhs) { - return NULL; + if (bfs_expr_is_parent(expr) || is_const(expr)) { + return; } - opt->after_false = rhs_state.after_false; - opt->after_true = lhs_state.after_true; - df_join(&opt->after_true, &rhs_state.after_true); - - return optimize_or_expr(opt, expr); + if (bfs_expr_warning(opt->ctx, expr)) { + va_list args; + va_start(args, format); + bfs_vwarning(opt->ctx, format, args); + va_end(args); + } } -/** Optimize an expression in an ignored-result context. */ -static struct bfs_expr *ignore_result(const struct bfs_opt *opt, struct bfs_expr *expr) { - int optlevel = opt->ctx->optlevel; +/** Remove and return an expression's children. */ +static void foster_children(struct bfs_expr *expr, struct bfs_exprs *children) { + bfs_assert(bfs_expr_is_parent(expr)); - if (optlevel >= 1) { - while (true) { - if (expr->eval_fn == eval_not) { - opt_debug(opt, 1, "ignored result: %pe --> %pe\n", expr, expr->rhs); - opt_warning(opt, expr, "The result of this expression is ignored.\n\n"); - expr = expr->rhs; - } else if (optlevel >= 2 - && (expr->eval_fn == eval_and || expr->eval_fn == eval_or || expr->eval_fn == eval_comma) - && expr->rhs->pure) { - opt_debug(opt, 2, "ignored result: %pe --> %pe\n", expr, expr->lhs); - opt_warning(opt, expr->rhs, "The result of this expression is ignored.\n\n"); - expr = expr->lhs; - } else { - break; - } - } + SLIST_INIT(children); + SLIST_EXTEND(children, &expr->children); - if (optlevel >= 2 && expr->pure && expr->eval_fn != eval_false) { - struct bfs_expr *ret = opt_const(opt, false); - opt_debug(opt, 2, "ignored result: %pe --> %pe\n", expr, ret); - opt_warning(opt, expr, "The result of this expression is ignored.\n\n"); - return ret; - } - } + expr->persistent_fds = 0; + expr->ephemeral_fds = 0; + expr->pure = true; +} - return expr; +/** Return an expression's only child. */ +static struct bfs_expr *only_child(struct bfs_expr *expr) { + bfs_assert(bfs_expr_is_parent(expr)); + struct bfs_expr *child = bfs_expr_children(expr); + bfs_assert(child && !child->next); + return child; } -/** Optimize a comma expression. */ -static struct bfs_expr *optimize_comma_expr(const struct bfs_opt *opt, struct bfs_expr *expr) { - bfs_assert(expr->eval_fn == eval_comma); +/** Foster an expression's only child. */ +static struct bfs_expr *foster_only_child(struct bfs_expr *expr) { + struct bfs_expr *child = only_child(expr); + struct bfs_exprs children; + foster_children(expr, &children); + return child; +} - struct bfs_expr *lhs = expr->lhs; - struct bfs_expr *rhs = expr->rhs; - - int optlevel = opt->ctx->optlevel; - if (optlevel >= 1) { - lhs = expr->lhs = ignore_result(opt, lhs); - - if (bfs_expr_never_returns(lhs)) { - opt_debug(opt, 1, "reachability: %pe <==> %pe\n", expr, lhs); - opt_warning(opt, expr->rhs, "This expression is unreachable.\n\n"); - return expr->lhs; - } else if ((lhs->always_true && rhs->eval_fn == eval_true) - || (lhs->always_false && rhs->eval_fn == eval_false)) { - opt_debug(opt, 1, "redundancy elimination: %pe <==> %pe\n", expr, lhs); - return expr->lhs; - } else if (optlevel >= 2 && lhs->pure) { - opt_debug(opt, 2, "purity: %pe <==> %pe\n", expr, rhs); - opt_warning(opt, expr->lhs, "The result of this expression is ignored.\n\n"); - return expr->rhs; +/** An expression visitor. */ +struct visitor; + +/** An expression-visiting function. */ +typedef struct bfs_expr *visit_fn(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor); + +/** An entry in a visitor lookup table. */ +struct visitor_table { + /** The evaluation function to match on. */ + bfs_eval_fn *eval_fn; + /** The visitor function. */ + visit_fn *visit; +}; + +/** Look up a visitor in a table. */ +static visit_fn *look_up_visitor(const struct bfs_expr *expr, const struct visitor_table table[]) { + for (size_t i = 0; table[i].eval_fn; ++i) { + if (expr->eval_fn == table[i].eval_fn) { + return table[i].visit; } } - expr->pure = lhs->pure && rhs->pure; - expr->always_true = bfs_expr_never_returns(lhs) || rhs->always_true; - expr->always_false = bfs_expr_never_returns(lhs) || rhs->always_false; - expr->cost = lhs->cost + rhs->cost; - expr->probability = rhs->probability; - - return expr; + return NULL; } -/** Optimize a comma expression recursively. */ -static struct bfs_expr *optimize_comma_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { - struct bfs_opt lhs_state = *opt; - expr->lhs = optimize_expr_recursive(&lhs_state, expr->lhs); - if (!expr->lhs) { - return NULL; - } +struct visitor { + /** The name of this visitor. */ + const char *name; + + /** A function to call before visiting children. */ + visit_fn *enter; + /** The default visitor. */ + visit_fn *visit; + /** A function to call after visiting children. */ + visit_fn *leave; + + /** A visitor lookup table. */ + struct visitor_table table[]; +}; - struct bfs_opt rhs_state = *opt; - rhs_state.before = lhs_state.after_true; - df_join(&rhs_state.before, &lhs_state.after_false); +/** Recursive visitor implementation. */ +static struct bfs_expr *visit_deep(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor); - expr->rhs = optimize_expr_recursive(&rhs_state, expr->rhs); - if (!expr->rhs) { +/** Visit a negation. */ +static struct bfs_expr *visit_not(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_expr *rhs = foster_only_child(expr); + + struct bfs_opt nested = *opt; + rhs = visit_deep(&nested, rhs, visitor); + if (!rhs) { return NULL; } - return optimize_comma_expr(opt, expr); + opt->after_true = nested.after_false; + opt->after_false = nested.after_true; + + bfs_expr_append(expr, rhs); + return expr; } -/** Optimize an icmp-style ([+-]N) expression. */ -static void optimize_icmp(struct bfs_opt *opt, const struct bfs_expr *expr, enum range_type type) { - struct df_range *true_range = &opt->after_true.ranges[type]; - struct df_range *false_range = &opt->after_false.ranges[type]; - long long value = expr->num; +/** Visit a conjunction. */ +static struct bfs_expr *visit_and(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_exprs children; + foster_children(expr, &children); - switch (expr->int_cmp) { - case BFS_INT_EQUAL: - constrain_min(true_range, value); - constrain_max(true_range, value); - range_remove(false_range, value); - break; + // Base case (-and) == (-true) + df_init_bottom(&opt->after_false); + struct bfs_opt nested = *opt; - case BFS_INT_LESS: - constrain_min(false_range, value); - constrain_max(true_range, value); - range_remove(true_range, value); - break; + while (!SLIST_EMPTY(&children)) { + struct bfs_expr *child = SLIST_POP(&children); - case BFS_INT_GREATER: - constrain_max(false_range, value); - constrain_min(true_range, value); - range_remove(true_range, value); - break; - } -} + if (SLIST_EMPTY(&children)) { + nested.ignore_result = opt->ignore_result; + } else { + nested.ignore_result = false; + } -/** Optimize -{execut,read,writ}able. */ -static struct bfs_expr *optimize_access(struct bfs_opt *opt, struct bfs_expr *expr) { - expr->probability = 1.0; + child = visit_deep(&nested, child, visitor); + if (!child) { + return NULL; + } - if (expr->num & R_OK) { - opt_constrain_pred(opt, READABLE_PRED, true); - expr->probability *= 0.99; - } + df_join(&opt->after_false, &nested.after_false); + nested.before = nested.after_true; - if (expr->num & W_OK) { - opt_constrain_pred(opt, WRITABLE_PRED, true); - expr->probability *= 0.8; + bfs_expr_append(expr, child); } - if (expr->num & X_OK) { - opt_constrain_pred(opt, EXECUTABLE_PRED, true); - expr->probability *= 0.2; - } + opt->after_true = nested.after_true; return expr; } -/** Optimize -empty. */ -static struct bfs_expr *optimize_empty(struct bfs_opt *opt, struct bfs_expr *expr) { - if (opt->ctx->optlevel >= 4) { - // Since -empty attempts to open and read directories, it may - // have side effects such as reporting permission errors, and - // thus shouldn't be re-ordered without aggressive optimizations - expr->pure = true; - } +/** Visit a disjunction. */ +static struct bfs_expr *visit_or(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_exprs children; + foster_children(expr, &children); - return expr; -} + // Base case (-or) == (-false) + df_init_bottom(&opt->after_true); + struct bfs_opt nested = *opt; -/** Optimize -{exec,ok}{,dir}. */ -static struct bfs_expr *optimize_exec(struct bfs_opt *opt, struct bfs_expr *expr) { + while (!SLIST_EMPTY(&children)) { + struct bfs_expr *child = SLIST_POP(&children); + + if (SLIST_EMPTY(&children)) { + nested.ignore_result = opt->ignore_result; + } else { + nested.ignore_result = false; + } + + child = visit_deep(&nested, child, visitor); + if (!child) { + return NULL; + } + + df_join(&opt->after_true, &nested.after_true); + nested.before = nested.after_false; + + bfs_expr_append(expr, child); + } + + opt->after_false = nested.after_false; + + return expr; +} + +/** Visit a comma expression. */ +static struct bfs_expr *visit_comma(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_exprs children; + foster_children(expr, &children); + + struct bfs_opt nested = *opt; + + while (!SLIST_EMPTY(&children)) { + struct bfs_expr *child = SLIST_POP(&children); + + if (SLIST_EMPTY(&children)) { + nested.ignore_result = opt->ignore_result; + } else { + nested.ignore_result = true; + } + + child = visit_deep(&nested, child, visitor); + if (!child) { + return NULL; + } + + nested.before = nested.after_true; + df_join(&nested.before, &nested.after_false); + + bfs_expr_append(expr, child); + } + + opt->after_true = nested.after_true; + opt->after_false = nested.after_false; + + return expr; +} + +/** Default enter() function. */ +static struct bfs_expr *visit_enter(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + opt_enter(opt, "%pe\n", expr); + opt->after_true = opt->before; + opt->after_false = opt->before; + return expr; +} + +/** Default leave() function. */ +static struct bfs_expr *visit_leave(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + opt_leave(opt, "%pe\n", expr); + return expr; +} + +static struct bfs_expr *visit_deep(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + bool entered = false; + + visit_fn *enter = visitor->enter ? visitor->enter : visit_enter; + visit_fn *leave = visitor->leave ? visitor->leave : visit_leave; + + static const struct visitor_table table[] = { + {eval_not, visit_not}, + {eval_and, visit_and}, + {eval_or, visit_or}, + {eval_comma, visit_comma}, + {NULL, NULL}, + }; + visit_fn *recursive = look_up_visitor(expr, table); + if (recursive) { + if (!entered) { + expr = enter(opt, expr, visitor); + if (!expr) { + return NULL; + } + entered = true; + } + + expr = recursive(opt, expr, visitor); + if (!expr) { + return NULL; + } + } + + visit_fn *general = visitor->visit; + if (general) { + if (!entered) { + expr = enter(opt, expr, visitor); + if (!expr) { + return NULL; + } + entered = true; + } + + expr = general(opt, expr, visitor); + if (!expr) { + return NULL; + } + } + + visit_fn *specific = look_up_visitor(expr, visitor->table); + if (specific) { + if (!entered) { + expr = enter(opt, expr, visitor); + if (!expr) { + return NULL; + } + entered = true; + } + + expr = specific(opt, expr, visitor); + if (!expr) { + return NULL; + } + } + + if (entered) { + expr = leave(opt, expr, visitor); + } else { + opt_visit(opt, "%pe\n", expr); + } + + return expr; +} + +/** Visit an expression recursively. */ +static struct bfs_expr *visit(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + opt_enter(opt, "%s()\n", visitor->name); + expr = visit_deep(opt, expr, visitor); + opt_leave(opt, "\n"); + return expr; +} + +/** Visit an expression non-recursively. */ +static struct bfs_expr *visit_shallow(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + visit_fn *general = visitor->visit; + if (expr && general) { + expr = general(opt, expr, visitor); + } + + visit_fn *specific = look_up_visitor(expr, visitor->table); + if (expr && specific) { + expr = specific(opt, expr, visitor); + } + + return expr; +} + +/** Annotate -{execut,read,writ}able. */ +static struct bfs_expr *annotate_access(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + expr->probability = 1.0; + if (expr->num & R_OK) { + expr->probability *= 0.99; + } + if (expr->num & W_OK) { + expr->probability *= 0.8; + } + if (expr->num & X_OK) { + expr->probability *= 0.2; + } + + return expr; +} + +/** Annotate -empty. */ +static struct bfs_expr *annotate_empty(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + if (opt->level >= 4) { + // Since -empty attempts to open and read directories, it may + // have side effects such as reporting permission errors, and + // thus shouldn't be re-ordered without aggressive optimizations + expr->pure = true; + } + + return expr; +} + +/** Annotate -exec. */ +static struct bfs_expr *annotate_exec(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { if (expr->exec->flags & BFS_EXEC_MULTI) { expr->always_true = true; } else { @@ -771,33 +993,650 @@ static struct bfs_expr *optimize_exec(struct bfs_opt *opt, struct bfs_expr *expr return expr; } -/** Optimize -name/-lname/-path. */ -static struct bfs_expr *optimize_fnmatch(struct bfs_opt *opt, struct bfs_expr *expr) { - if (strchr(expr->argv[1], '*')) { +/** Annotate -name/-lname/-path. */ +static struct bfs_expr *annotate_fnmatch(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + if (expr->literal) { + expr->probability = 0.1; + } else { expr->probability = 0.5; + } + + return expr; +} + +/** Estimate probability for -x?type. */ +static void estimate_type_probability(struct bfs_expr *expr) { + unsigned int types = expr->num; + + expr->probability = 0.0; + if (types & (1 << BFS_BLK)) { + expr->probability += 0.00000721183; + } + if (types & (1 << BFS_CHR)) { + expr->probability += 0.0000499855; + } + if (types & (1 << BFS_DIR)) { + expr->probability += 0.114475; + } + if (types & (1 << BFS_DOOR)) { + expr->probability += 0.000001; + } + if (types & (1 << BFS_FIFO)) { + expr->probability += 0.00000248684; + } + if (types & (1 << BFS_REG)) { + expr->probability += 0.859772; + } + if (types & (1 << BFS_LNK)) { + expr->probability += 0.0256816; + } + if (types & (1 << BFS_SOCK)) { + expr->probability += 0.0000116881; + } + if (types & (1 << BFS_WHT)) { + expr->probability += 0.000001; + } +} + +/** Annotate -type. */ +static struct bfs_expr *annotate_type(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + estimate_type_probability(expr); + return expr; +} + +/** Annotate -xtype. */ +static struct bfs_expr *annotate_xtype(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + if (opt->level >= 4) { + // Since -xtype dereferences symbolic links, it may have side + // effects such as reporting permission errors, and thus + // shouldn't be re-ordered without aggressive optimizations + expr->pure = true; + } + + estimate_type_probability(expr); + return expr; +} + +/** Annotate a negation. */ +static struct bfs_expr *annotate_not(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_expr *rhs = only_child(expr); + expr->pure = rhs->pure; + expr->always_true = rhs->always_false; + expr->always_false = rhs->always_true; + expr->cost = rhs->cost; + expr->probability = 1.0 - rhs->probability; + return expr; +} + +/** Annotate a conjunction. */ +static struct bfs_expr *annotate_and(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + expr->pure = true; + expr->always_true = true; + expr->always_false = false; + expr->cost = 0.0; + expr->probability = 1.0; + + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + expr->pure &= child->pure; + expr->always_true &= child->always_true; + expr->always_false |= child->always_false; + expr->cost += expr->probability * child->cost; + expr->probability *= child->probability; + } + + return expr; +} + +/** Annotate a disjunction. */ +static struct bfs_expr *annotate_or(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + expr->pure = true; + expr->always_true = false; + expr->always_false = true; + expr->cost = 0.0; + + float false_prob = 1.0; + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + expr->pure &= child->pure; + expr->always_true |= child->always_true; + expr->always_false &= child->always_false; + expr->cost += false_prob * child->cost; + false_prob *= (1.0 - child->probability); + } + expr->probability = 1.0 - false_prob; + + return expr; +} + +/** Annotate a comma expression. */ +static struct bfs_expr *annotate_comma(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + expr->pure = true; + expr->cost = 0.0; + + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + expr->pure &= child->pure; + expr->always_true = child->always_true; + expr->always_false = child->always_false; + expr->cost += child->cost; + expr->probability = child->probability; + } + + return expr; +} + +/** Annotate an arbitrary expression. */ +static struct bfs_expr *annotate_visit(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + /** Table of pure expressions. */ + static bfs_eval_fn *const pure[] = { + eval_access, + eval_acl, + eval_capable, + eval_depth, + eval_false, + eval_flags, + eval_fstype, + eval_gid, + eval_hidden, + eval_inum, + eval_links, + eval_lname, + eval_name, + eval_newer, + eval_nogroup, + eval_nouser, + eval_path, + eval_perm, + eval_regex, + eval_samefile, + eval_size, + eval_sparse, + eval_time, + eval_true, + eval_type, + eval_uid, + eval_used, + eval_xattr, + eval_xattrname, + }; + + expr->pure = false; + for (size_t i = 0; i < countof(pure); ++i) { + if (expr->eval_fn == pure[i]) { + expr->pure = true; + break; + } + } + + /** Table of always-true expressions. */ + static bfs_eval_fn *const always_true[] = { + eval_fls, + eval_fprint, + eval_fprint0, + eval_fprintf, + eval_fprintx, + eval_prune, + eval_true, + // Non-returning + eval_exit, + eval_quit, + }; + + expr->always_true = false; + for (size_t i = 0; i < countof(always_true); ++i) { + if (expr->eval_fn == always_true[i]) { + expr->always_true = true; + break; + } + } + + /** Table of always-false expressions. */ + static bfs_eval_fn *const always_false[] = { + eval_false, + // Non-returning + eval_exit, + eval_quit, + }; + + expr->always_false = false; + for (size_t i = 0; i < countof(always_false); ++i) { + if (expr->eval_fn == always_false[i]) { + expr->always_false = true; + break; + } + } + +#define FAST_COST 40.0 +#define FNMATCH_COST 400.0 +#define STAT_COST 1000.0 +#define PRINT_COST 20000.0 + + /** Table of expression costs. */ + static const struct { + bfs_eval_fn *eval_fn; + float cost; + } costs[] = { + {eval_access, STAT_COST}, + {eval_acl, STAT_COST}, + {eval_capable, STAT_COST}, + {eval_empty, 2 * STAT_COST}, // readdir() is worse than stat() + {eval_fls, PRINT_COST}, + {eval_fprint, PRINT_COST}, + {eval_fprint0, PRINT_COST}, + {eval_fprintf, PRINT_COST}, + {eval_fprintx, PRINT_COST}, + {eval_fstype, STAT_COST}, + {eval_gid, STAT_COST}, + {eval_inum, STAT_COST}, + {eval_links, STAT_COST}, + {eval_lname, FNMATCH_COST}, + {eval_name, FNMATCH_COST}, + {eval_newer, STAT_COST}, + {eval_nogroup, STAT_COST}, + {eval_nouser, STAT_COST}, + {eval_path, FNMATCH_COST}, + {eval_perm, STAT_COST}, + {eval_samefile, STAT_COST}, + {eval_size, STAT_COST}, + {eval_sparse, STAT_COST}, + {eval_time, STAT_COST}, + {eval_uid, STAT_COST}, + {eval_used, STAT_COST}, + {eval_xattr, STAT_COST}, + {eval_xattrname, STAT_COST}, + }; + + expr->cost = FAST_COST; + for (size_t i = 0; i < countof(costs); ++i) { + if (expr->eval_fn == costs[i].eval_fn) { + expr->cost = costs[i].cost; + break; + } + } + + /** Table of expression probabilities. */ + static const struct { + /** The evaluation function with this cost. */ + bfs_eval_fn *eval_fn; + /** The matching probability. */ + float probability; + } probs[] = { + {eval_acl, 0.00002}, + {eval_capable, 0.000002}, + {eval_empty, 0.01}, + {eval_false, 0.0}, + {eval_hidden, 0.01}, + {eval_nogroup, 0.01}, + {eval_nouser, 0.01}, + {eval_samefile, 0.01}, + {eval_true, 1.0}, + {eval_xattr, 0.01}, + {eval_xattrname, 0.01}, + }; + + expr->probability = 0.5; + for (size_t i = 0; i < countof(probs); ++i) { + if (expr->eval_fn == probs[i].eval_fn) { + expr->probability = probs[i].probability; + break; + } + } + + return expr; +} + +/** + * Annotating visitor. + */ +static const struct visitor annotate = { + .name = "annotate", + .visit = annotate_visit, + .table = { + {eval_access, annotate_access}, + {eval_empty, annotate_empty}, + {eval_exec, annotate_exec}, + {eval_lname, annotate_fnmatch}, + {eval_name, annotate_fnmatch}, + {eval_path, annotate_fnmatch}, + {eval_type, annotate_type}, + {eval_xtype, annotate_xtype}, + + {eval_not, annotate_not}, + {eval_and, annotate_and}, + {eval_or, annotate_or}, + {eval_comma, annotate_comma}, + + {NULL, NULL}, + }, +}; + +/** Create a constant expression. */ +static struct bfs_expr *opt_const(struct bfs_opt *opt, bool value) { + static bfs_eval_fn *const fns[] = {eval_false, eval_true}; + static char *fake_args[] = {"-false", "-true"}; + + struct bfs_expr *expr = bfs_expr_new(opt->ctx, fns[value], 1, &fake_args[value]); + return visit_shallow(opt, expr, &annotate); +} + +/** Negate an expression, keeping it canonical. */ +static struct bfs_expr *negate_expr(struct bfs_opt *opt, struct bfs_expr *expr, char **argv) { + if (expr->eval_fn == eval_not) { + return only_child(expr); + } else if (expr->eval_fn == eval_true) { + return opt_const(opt, false); + } else if (expr->eval_fn == eval_false) { + return opt_const(opt, true); + } + + struct bfs_expr *ret = bfs_expr_new(opt->ctx, eval_not, 1, argv); + if (!ret) { + return NULL; + } + + bfs_expr_append(ret, expr); + return visit_shallow(opt, ret, &annotate); +} + +/** Sink negations into a conjunction/disjunction using De Morgan's laws. */ +static struct bfs_expr *sink_not_andor(struct bfs_opt *opt, struct bfs_expr *expr) { + opt_debug(opt, "De Morgan's laws\n"); + + char **argv = expr->argv; + expr = only_child(expr); + opt_enter(opt, "%pe\n", expr); + + if (expr->eval_fn == eval_and) { + expr->eval_fn = eval_or; + expr->argv = &fake_or_arg; } else { - expr->probability = 0.1; + bfs_assert(expr->eval_fn == eval_or); + expr->eval_fn = eval_and; + expr->argv = &fake_and_arg; + } + + struct bfs_exprs children; + foster_children(expr, &children); + + struct bfs_expr *child; + while ((child = SLIST_POP(&children))) { + opt_enter(opt, "%pe\n", child); + + child = negate_expr(opt, child, argv); + if (!child) { + return NULL; + } + + opt_leave(opt, "%pe\n", child); + bfs_expr_append(expr, child); + } + + opt_leave(opt, "%pe\n", expr); + return visit_shallow(opt, expr, &annotate); +} + +/** Sink a negation into a comma expression. */ +static struct bfs_expr *sink_not_comma(struct bfs_opt *opt, struct bfs_expr *expr) { + bfs_assert(expr->eval_fn == eval_comma); + + opt_enter(opt, "%pe\n", expr); + + char **argv = expr->argv; + expr = only_child(expr); + + struct bfs_exprs children; + foster_children(expr, &children); + + struct bfs_expr *child; + while ((child = SLIST_POP(&children))) { + if (SLIST_EMPTY(&children)) { + opt_enter(opt, "%pe\n", child); + opt_debug(opt, "sink\n"); + + child = negate_expr(opt, child, argv); + if (!child) { + return NULL; + } + + opt_leave(opt, "%pe\n", child); + } else { + opt_visit(opt, "%pe\n", child); + } + + bfs_expr_append(expr, child); + } + + opt_leave(opt, "%pe\n", expr); + return visit_shallow(opt, expr, &annotate); +} + +/** Canonicalize a negation. */ +static struct bfs_expr *canonicalize_not(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_expr *rhs = only_child(expr); + + if (rhs->eval_fn == eval_not) { + opt_debug(opt, "double negation\n"); + rhs = only_child(expr); + return only_child(rhs); + } else if (rhs->eval_fn == eval_and || rhs->eval_fn == eval_or) { + return sink_not_andor(opt, expr); + } else if (rhs->eval_fn == eval_comma) { + return sink_not_comma(opt, expr); + } else if (is_const(rhs)) { + opt_debug(opt, "constant propagation\n"); + return opt_const(opt, rhs->eval_fn == eval_false); + } else { + return expr; + } +} + +/** Canonicalize an associative operator. */ +static struct bfs_expr *canonicalize_assoc(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_exprs children; + foster_children(expr, &children); + + struct bfs_exprs flat; + SLIST_INIT(&flat); + + struct bfs_expr *child; + while ((child = SLIST_POP(&children))) { + if (child->eval_fn == expr->eval_fn) { + struct bfs_expr *head = SLIST_HEAD(&child->children); + struct bfs_expr *tail = SLIST_TAIL(&child->children); + + if (!head) { + opt_delete(opt, "%pe [empty]\n", child); + } else { + opt_enter(opt, "%pe\n", child); + opt_debug(opt, "associativity\n"); + if (head == tail) { + opt_leave(opt, "%pe\n", head); + } else if (head->next == tail) { + opt_leave(opt, "%pe %pe\n", head, tail); + } else { + opt_leave(opt, "%pe ... %pe\n", head, tail); + } + } + + SLIST_EXTEND(&flat, &child->children); + } else { + opt_visit(opt, "%pe\n", child); + SLIST_APPEND(&flat, child); + } + } + + bfs_expr_extend(expr, &flat); + + return visit_shallow(opt, expr, &annotate); +} + +/** + * Canonicalizing visitor. + */ +static const struct visitor canonicalize = { + .name = "canonicalize", + .table = { + {eval_not, canonicalize_not}, + {eval_and, canonicalize_assoc}, + {eval_or, canonicalize_assoc}, + {eval_comma, canonicalize_assoc}, + {NULL, NULL}, + }, +}; + +/** Calculate the cost of an ordered pair of expressions. */ +static float expr_cost(const struct bfs_expr *parent, const struct bfs_expr *lhs, const struct bfs_expr *rhs) { + // https://cs.stackexchange.com/a/66921/21004 + float prob = lhs->probability; + if (parent->eval_fn == eval_or) { + prob = 1.0 - prob; + } + return lhs->cost + prob * rhs->cost; +} + +/** Sort a block of expressions. */ +static void sort_exprs(struct bfs_opt *opt, struct bfs_expr *parent, struct bfs_exprs *exprs) { + if (!exprs->head || !exprs->head->next) { + return; + } + + struct bfs_exprs left, right; + SLIST_INIT(&left); + SLIST_INIT(&right); + + // Split + for (struct bfs_expr *hare = exprs->head; hare && (hare = hare->next); hare = hare->next) { + struct bfs_expr *tortoise = SLIST_POP(exprs); + SLIST_APPEND(&left, tortoise); + } + SLIST_EXTEND(&right, exprs); + + // Recurse + sort_exprs(opt, parent, &left); + sort_exprs(opt, parent, &right); + + // Merge + while (!SLIST_EMPTY(&left) && !SLIST_EMPTY(&right)) { + struct bfs_expr *lhs = left.head; + struct bfs_expr *rhs = right.head; + + float cost = expr_cost(parent, lhs, rhs); + float swapped = expr_cost(parent, rhs, lhs); + + if (cost <= swapped) { + SLIST_POP(&left); + SLIST_APPEND(exprs, lhs); + } else { + opt_enter(opt, "%pe %pe [${ylw}%g${rs}]\n", lhs, rhs, cost); + SLIST_POP(&right); + SLIST_APPEND(exprs, rhs); + opt_leave(opt, "%pe %pe [${ylw}%g${rs}]\n", rhs, lhs, swapped); + } + } + SLIST_EXTEND(exprs, &left); + SLIST_EXTEND(exprs, &right); +} + +/** Reorder children to reduce cost. */ +static struct bfs_expr *reorder_andor(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_exprs children; + foster_children(expr, &children); + + // Split into blocks of consecutive pure/impure expressions, and sort + // the pure blocks + struct bfs_exprs pure; + SLIST_INIT(&pure); + + struct bfs_expr *child; + while ((child = SLIST_POP(&children))) { + if (child->pure) { + SLIST_APPEND(&pure, child); + } else { + sort_exprs(opt, expr, &pure); + bfs_expr_extend(expr, &pure); + bfs_expr_append(expr, child); + } + } + sort_exprs(opt, expr, &pure); + bfs_expr_extend(expr, &pure); + + return visit_shallow(opt, expr, &annotate); +} + +/** + * Reordering visitor. + */ +static const struct visitor reorder = { + .name = "reorder", + .table = { + {eval_and, reorder_andor}, + {eval_or, reorder_andor}, + {NULL, NULL}, + }, +}; + +/** Transfer function for simple predicates. */ +static void data_flow_pred(struct bfs_opt *opt, enum pred_type pred, bool value) { + constrain_pred(&opt->after_true.preds[pred], value); + constrain_pred(&opt->after_false.preds[pred], !value); +} + +/** Transfer function for icmp-style ([+-]N) expressions. */ +static void data_flow_icmp(struct bfs_opt *opt, const struct bfs_expr *expr, enum range_type type) { + struct df_range *true_range = &opt->after_true.ranges[type]; + struct df_range *false_range = &opt->after_false.ranges[type]; + long long value = expr->num; + + switch (expr->int_cmp) { + case BFS_INT_EQUAL: + constrain_min(true_range, value); + constrain_max(true_range, value); + range_remove(false_range, value); + break; + + case BFS_INT_LESS: + constrain_min(false_range, value); + constrain_max(true_range, value); + range_remove(true_range, value); + break; + + case BFS_INT_GREATER: + constrain_max(false_range, value); + constrain_min(true_range, value); + range_remove(true_range, value); + break; + } +} + +/** 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) { + data_flow_pred(opt, READABLE_PRED, true); + } + if (expr->num & W_OK) { + data_flow_pred(opt, WRITABLE_PRED, true); + } + if (expr->num & X_OK) { + data_flow_pred(opt, EXECUTABLE_PRED, true); } return expr; } -/** Optimize -gid. */ -static struct bfs_expr *optimize_gid(struct bfs_opt *opt, struct bfs_expr *expr) { +/** Transfer function for -gid. */ +static struct bfs_expr *data_flow_gid(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { struct df_range *range = &opt->after_true.ranges[GID_RANGE]; if (range->min == range->max) { gid_t gid = range->min; bool nogroup = !bfs_getgrgid(opt->ctx->groups, gid); if (errno == 0) { - opt_constrain_pred(opt, NOGROUP_PRED, nogroup); + data_flow_pred(opt, NOGROUP_PRED, nogroup); } } return expr; } -/** Optimize -inum. */ -static struct bfs_expr *optimize_inum(struct bfs_opt *opt, struct bfs_expr *expr) { +/** Transfer function for -inum. */ +static struct bfs_expr *data_flow_inum(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { struct df_range *range = &opt->after_true.ranges[INUM_RANGE]; if (range->min == range->max) { expr->probability = 0.01; @@ -808,8 +1647,8 @@ static struct bfs_expr *optimize_inum(struct bfs_opt *opt, struct bfs_expr *expr return expr; } -/** Optimize -links. */ -static struct bfs_expr *optimize_links(struct bfs_opt *opt, struct bfs_expr *expr) { +/** Transfer function for -links. */ +static struct bfs_expr *data_flow_links(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { struct df_range *range = &opt->after_true.ranges[LINKS_RANGE]; if (1 >= range->min && 1 <= range->max) { expr->probability = 0.99; @@ -820,30 +1659,20 @@ static struct bfs_expr *optimize_links(struct bfs_opt *opt, struct bfs_expr *exp return expr; } -/** Optimize -uid. */ -static struct bfs_expr *optimize_uid(struct bfs_opt *opt, struct bfs_expr *expr) { - struct df_range *range = &opt->after_true.ranges[UID_RANGE]; - if (range->min == range->max) { - uid_t uid = range->min; - bool nouser = !bfs_getpwuid(opt->ctx->users, uid); - if (errno == 0) { - opt_constrain_pred(opt, NOUSER_PRED, nouser); - } - } +/** Transfer function for -samefile. */ +static struct bfs_expr *data_flow_samefile(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct df_range *true_range = &opt->after_true.ranges[INUM_RANGE]; + constrain_min(true_range, expr->ino); + constrain_max(true_range, expr->ino); - return expr; -} + struct df_range *false_range = &opt->after_false.ranges[INUM_RANGE]; + range_remove(false_range, expr->ino); -/** Optimize -samefile. */ -static struct bfs_expr *optimize_samefile(struct bfs_opt *opt, struct bfs_expr *expr) { - struct df_range *range = &opt->after_true.ranges[INUM_RANGE]; - constrain_min(range, expr->ino); - constrain_max(range, expr->ino); return expr; } -/** Optimize -size. */ -static struct bfs_expr *optimize_size(struct bfs_opt *opt, struct bfs_expr *expr) { +/** Transfer function for -size. */ +static struct bfs_expr *data_flow_size(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { struct df_range *range = &opt->after_true.ranges[SIZE_RANGE]; if (range->min == range->max) { expr->probability = 0.01; @@ -854,473 +1683,449 @@ static struct bfs_expr *optimize_size(struct bfs_opt *opt, struct bfs_expr *expr return expr; } -/** Estimate probability for -x?type. */ -static void estimate_type_probability(struct bfs_expr *expr) { - unsigned int types = expr->num; - - expr->probability = 0.0; - if (types & (1 << BFS_BLK)) { - expr->probability += 0.00000721183; - } - if (types & (1 << BFS_CHR)) { - expr->probability += 0.0000499855; - } - if (types & (1 << BFS_DIR)) { - expr->probability += 0.114475; - } - if (types & (1 << BFS_DOOR)) { - expr->probability += 0.000001; - } - if (types & (1 << BFS_FIFO)) { - expr->probability += 0.00000248684; - } - if (types & (1 << BFS_REG)) { - expr->probability += 0.859772; - } - if (types & (1 << BFS_LNK)) { - expr->probability += 0.0256816; - } - if (types & (1 << BFS_SOCK)) { - expr->probability += 0.0000116881; - } - if (types & (1 << BFS_WHT)) { - expr->probability += 0.000001; - } -} - -/** Optimize -type. */ -static struct bfs_expr *optimize_type(struct bfs_opt *opt, struct bfs_expr *expr) { +/** Transfer function for -type. */ +static struct bfs_expr *data_flow_type(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { opt->after_true.types &= expr->num; opt->after_false.types &= ~expr->num; - - estimate_type_probability(expr); - return expr; } -/** Optimize -xtype. */ -static struct bfs_expr *optimize_xtype(struct bfs_opt *opt, struct bfs_expr *expr) { - if (opt->ctx->optlevel >= 4) { - // Since -xtype dereferences symbolic links, it may have side - // effects such as reporting permission errors, and thus - // shouldn't be re-ordered without aggressive optimizations - expr->pure = true; +/** Transfer function for -uid. */ +static struct bfs_expr *data_flow_uid(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct df_range *range = &opt->after_true.ranges[UID_RANGE]; + if (range->min == range->max) { + uid_t uid = range->min; + bool nouser = !bfs_getpwuid(opt->ctx->users, uid); + if (errno == 0) { + data_flow_pred(opt, NOUSER_PRED, nouser); + } } + return expr; +} + +/** Transfer function for -xtype. */ +static struct bfs_expr *data_flow_xtype(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { opt->after_true.xtypes &= expr->num; opt->after_false.xtypes &= ~expr->num; - - estimate_type_probability(expr); - return expr; } -/** - * Table of pure expressions. - */ -static bfs_eval_fn *const opt_pure[] = { - eval_access, - eval_acl, - eval_capable, - eval_depth, - eval_false, - eval_flags, - eval_fstype, - eval_gid, - eval_hidden, - eval_inum, - eval_links, - eval_lname, - eval_name, - eval_newer, - eval_nogroup, - eval_nouser, - eval_path, - eval_perm, - eval_regex, - eval_samefile, - eval_size, - eval_sparse, - eval_time, - eval_true, - eval_type, - eval_uid, - eval_used, - eval_xattr, - eval_xattrname, -}; - -/** - * Table of always-true expressions. - */ -static bfs_eval_fn *const opt_always_true[] = { - eval_fls, - eval_fprint, - eval_fprint0, - eval_fprintf, - eval_fprintx, - eval_prune, - eval_true, - - // Non-returning - eval_exit, - eval_quit, -}; - -/** - * Table of always-false expressions. - */ -static bfs_eval_fn *const opt_always_false[] = { - eval_false, - - // Non-returning - eval_exit, - eval_quit, -}; - -#define FAST_COST 40.0 -#define FNMATCH_COST 400.0 -#define STAT_COST 1000.0 -#define PRINT_COST 20000.0 - -/** - * Table of expression costs. - */ -static const struct { - /** The evaluation function with this cost. */ - bfs_eval_fn *eval_fn; - /** The matching cost. */ - float cost; -} opt_costs[] = { - {eval_access, STAT_COST}, - {eval_acl, STAT_COST}, - {eval_capable, STAT_COST}, - {eval_empty, 2 * STAT_COST}, // readdir() is worse than stat() - {eval_fls, PRINT_COST}, - {eval_fprint, PRINT_COST}, - {eval_fprint0, PRINT_COST}, - {eval_fprintf, PRINT_COST}, - {eval_fprintx, PRINT_COST}, - {eval_fstype, STAT_COST}, - {eval_gid, STAT_COST}, - {eval_inum, STAT_COST}, - {eval_links, STAT_COST}, - {eval_lname, FNMATCH_COST}, - {eval_name, FNMATCH_COST}, - {eval_newer, STAT_COST}, - {eval_nogroup, STAT_COST}, - {eval_nouser, STAT_COST}, - {eval_path, FNMATCH_COST}, - {eval_perm, STAT_COST}, - {eval_samefile, STAT_COST}, - {eval_size, STAT_COST}, - {eval_sparse, STAT_COST}, - {eval_time, STAT_COST}, - {eval_uid, STAT_COST}, - {eval_used, STAT_COST}, - {eval_xattr, STAT_COST}, - {eval_xattrname, STAT_COST}, -}; - -/** - * Table of expression probabilities. - */ -static const struct { - /** The evaluation function with this cost. */ - bfs_eval_fn *eval_fn; - /** The matching probability. */ - float probability; -} opt_probs[] = { - {eval_acl, 0.00002}, - {eval_capable, 0.000002}, - {eval_empty, 0.01}, - {eval_false, 0.0}, - {eval_hidden, 0.01}, - {eval_nogroup, 0.01}, - {eval_nouser, 0.01}, - {eval_samefile, 0.01}, - {eval_true, 1.0}, - {eval_xattr, 0.01}, - {eval_xattrname, 0.01}, -}; +/** Data flow visitor entry. */ +static struct bfs_expr *data_flow_enter(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + visit_enter(opt, expr, visitor); -/** - * Table of simple predicates. - */ -static const struct { - /** The evaluation function this optimizer applies to. */ - bfs_eval_fn *eval_fn; - /** The corresponding predicate. */ - enum pred_type pred; -} opt_preds[] = { - {eval_acl, ACL_PRED}, - {eval_capable, CAPABLE_PRED}, - {eval_empty, EMPTY_PRED}, - {eval_hidden, HIDDEN_PRED}, - {eval_nogroup, NOGROUP_PRED}, - {eval_nouser, NOUSER_PRED}, - {eval_sparse, SPARSE_PRED}, - {eval_xattr, XATTR_PRED}, -}; + df_dump(opt, "before", &opt->before); -/** - * Table of simple range comparisons. - */ -static const struct { - /** The evaluation function this optimizer applies to. */ - bfs_eval_fn *eval_fn; - /** The corresponding range. */ - enum range_type range; -} opt_ranges[] = { - {eval_depth, DEPTH_RANGE}, - {eval_gid, GID_RANGE}, - {eval_inum, INUM_RANGE}, - {eval_links, LINKS_RANGE}, - {eval_size, SIZE_RANGE}, - {eval_uid, UID_RANGE}, -}; + if (!bfs_expr_is_parent(expr) && !expr->pure) { + df_join(opt->impure, &opt->before); + df_dump(opt, "impure", opt->impure); + } -/** Signature for custom optimizer functions. */ -typedef struct bfs_expr *bfs_opt_fn(struct bfs_opt *opt, struct bfs_expr *expr); + return expr; +} -/** Table of custom optimizer functions. */ -static const struct { - /** The evaluation function this optimizer applies to. */ - bfs_eval_fn *eval_fn; - /** The corresponding optimizer function. */ - bfs_opt_fn *opt_fn; -} opt_fns[] = { - // Primaries - {eval_access, optimize_access}, - {eval_empty, optimize_empty}, - {eval_exec, optimize_exec}, - {eval_gid, optimize_gid}, - {eval_inum, optimize_inum}, - {eval_links, optimize_links}, - {eval_lname, optimize_fnmatch}, - {eval_name, optimize_fnmatch}, - {eval_path, optimize_fnmatch}, - {eval_samefile, optimize_samefile}, - {eval_size, optimize_size}, - {eval_type, optimize_type}, - {eval_uid, optimize_uid}, - {eval_xtype, optimize_xtype}, - - // Operators - {eval_and, optimize_and_expr_recursive}, - {eval_comma, optimize_comma_expr_recursive}, - {eval_not, optimize_not_expr_recursive}, - {eval_or, optimize_or_expr_recursive}, -}; +/** Data flow visitor exit. */ +static struct bfs_expr *data_flow_leave(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + if (expr->always_true) { + expr->probability = 1.0; + df_init_bottom(&opt->after_false); + } -/** - * Look up the appropriate optimizer for an expression and call it. - */ -static struct bfs_expr *optimize_expr_lookup(struct bfs_opt *opt, struct bfs_expr *expr) { - for (size_t i = 0; i < countof(opt_pure); ++i) { - if (opt_pure[i] == expr->eval_fn) { - expr->pure = true; - break; - } + if (expr->always_false) { + expr->probability = 0.0; + df_init_bottom(&opt->after_true); } - for (size_t i = 0; i < countof(opt_always_true); ++i) { - if (opt_always_true[i] == expr->eval_fn) { + df_dump(opt, "after true", &opt->after_true); + df_dump(opt, "after false", &opt->after_false); + + if (df_is_bottom(&opt->after_false)) { + if (!expr->pure) { expr->always_true = true; - break; + expr->probability = 0.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"); + expr = opt_const(opt, true); + if (!expr) { + return NULL; + } } } - for (size_t i = 0; i < countof(opt_always_false); ++i) { - if (opt_always_false[i] == expr->eval_fn) { + if (df_is_bottom(&opt->after_true)) { + if (!expr->pure) { expr->always_false = true; - break; + expr->probability = 0.0; + } else if (expr->eval_fn != eval_false) { + opt_warning(opt, expr, "This expression is always false.\n\n"); + opt_debug(opt, "pure, always false\n"); + expr = opt_const(opt, false); + if (!expr) { + return NULL; + } } } - expr->cost = FAST_COST; - for (size_t i = 0; i < countof(opt_costs); ++i) { - if (opt_costs[i].eval_fn == expr->eval_fn) { - expr->cost = opt_costs[i].cost; - break; + return visit_leave(opt, expr, visitor); +} + +/** Data flow visitor function. */ +static struct bfs_expr *data_flow_visit(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + if (opt->ignore_result && expr->pure) { + opt_debug(opt, "ignored result\n"); + opt_warning(opt, expr, "The result of this expression is ignored.\n\n"); + expr = opt_const(opt, false); + if (!expr) { + return NULL; } } - for (size_t i = 0; i < countof(opt_probs); ++i) { - if (opt_probs[i].eval_fn == expr->eval_fn) { - expr->probability = opt_probs[i].probability; - break; + if (df_is_bottom(&opt->before)) { + opt_debug(opt, "unreachable\n"); + opt_warning(opt, expr, "This expression is unreachable.\n\n"); + expr = opt_const(opt, false); + if (!expr) { + return NULL; } } - for (size_t i = 0; i < countof(opt_preds); ++i) { - if (opt_preds[i].eval_fn == expr->eval_fn) { - opt_constrain_pred(opt, opt_preds[i].pred, true); + /** Table of simple predicates. */ + static const struct { + bfs_eval_fn *eval_fn; + enum pred_type pred; + } preds[] = { + {eval_acl, ACL_PRED}, + {eval_capable, CAPABLE_PRED}, + {eval_empty, EMPTY_PRED}, + {eval_hidden, HIDDEN_PRED}, + {eval_nogroup, NOGROUP_PRED}, + {eval_nouser, NOUSER_PRED}, + {eval_sparse, SPARSE_PRED}, + {eval_xattr, XATTR_PRED}, + }; + + for (size_t i = 0; i < countof(preds); ++i) { + if (preds[i].eval_fn == expr->eval_fn) { + data_flow_pred(opt, preds[i].pred, true); break; } } - for (size_t i = 0; i < countof(opt_ranges); ++i) { - if (opt_ranges[i].eval_fn == expr->eval_fn) { - optimize_icmp(opt, expr, opt_ranges[i].range); + /** Table of simple range comparisons. */ + static const struct { + bfs_eval_fn *eval_fn; + enum range_type range; + } ranges[] = { + {eval_depth, DEPTH_RANGE}, + {eval_gid, GID_RANGE}, + {eval_inum, INUM_RANGE}, + {eval_links, LINKS_RANGE}, + {eval_size, SIZE_RANGE}, + {eval_uid, UID_RANGE}, + }; + + for (size_t i = 0; i < countof(ranges); ++i) { + if (ranges[i].eval_fn == expr->eval_fn) { + data_flow_icmp(opt, expr, ranges[i].range); break; } } - for (size_t i = 0; i < countof(opt_fns); ++i) { - if (opt_fns[i].eval_fn == expr->eval_fn) { - return opt_fns[i].opt_fn(opt, expr); - } + return expr; +} + +/** + * Data flow visitor. + */ +static const struct visitor data_flow = { + .name = "data_flow", + .enter = data_flow_enter, + .visit = data_flow_visit, + .leave = data_flow_leave, + .table = { + {eval_access, data_flow_access}, + {eval_gid, data_flow_gid}, + {eval_inum, data_flow_inum}, + {eval_links, data_flow_links}, + {eval_samefile, data_flow_samefile}, + {eval_size, data_flow_size}, + {eval_type, data_flow_type}, + {eval_uid, data_flow_uid}, + {eval_xtype, data_flow_xtype}, + {NULL, NULL}, + }, +}; + +/** Simplify a negation. */ +static struct bfs_expr *simplify_not(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + if (opt->ignore_result) { + opt_debug(opt, "ignored result\n"); + expr = only_child(expr); } return expr; } -static struct bfs_expr *optimize_expr_recursive(struct bfs_opt *opt, struct bfs_expr *expr) { - int optlevel = opt->ctx->optlevel; +/** Lift negations out of a conjunction/disjunction using De Morgan's laws. */ +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) { + if (child->eval_fn == eval_not) { + ++removed; + } else { + ++added; + } + } + if (added >= removed) { + return visit_shallow(opt, expr, &annotate); + } - opt->after_true = opt->before; - opt->after_false = opt->before; + opt_debug(opt, "De Morgan's laws\n"); - if (optlevel >= 2 && df_is_bottom(&opt->before)) { - struct bfs_expr *ret = opt_const(opt, false); - opt_debug(opt, 2, "reachability: %pe --> %pe\n", expr, ret); - opt_warning(opt, expr, "This expression is unreachable.\n\n"); - return ret; + if (expr->eval_fn == eval_and) { + expr->eval_fn = eval_or; + expr->argv = &fake_or_arg; + } else { + bfs_assert(expr->eval_fn == eval_or); + expr->eval_fn = eval_and; + expr->argv = &fake_and_arg; + } + + struct bfs_exprs children; + foster_children(expr, &children); + + struct bfs_expr *child; + while ((child = SLIST_POP(&children))) { + opt_enter(opt, "%pe\n", child); + + child = negate_expr(opt, child, &fake_not_arg); + if (!child) { + return NULL; + } + + opt_leave(opt, "%pe\n", child); + bfs_expr_append(expr, child); } - expr = optimize_expr_lookup(opt, expr); - if (!expr) { + expr = visit_shallow(opt, expr, &annotate); + return negate_expr(opt, expr, &fake_not_arg); +} + +/** Get the first ignorable expression in a conjunction/disjunction. */ +static struct bfs_expr *first_ignorable(struct bfs_opt *opt, struct bfs_expr *expr) { + if (opt->level < 2 || !opt->ignore_result) { return NULL; } - if (bfs_expr_is_parent(expr)) { - struct bfs_expr *lhs = expr->lhs; - struct bfs_expr *rhs = expr->rhs; - if (rhs) { - expr->persistent_fds = rhs->persistent_fds; - expr->ephemeral_fds = rhs->ephemeral_fds; + struct bfs_expr *ret = NULL; + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { + if (!child->pure) { + ret = NULL; + } else if (!ret) { + ret = child; + } + } + + return ret; +} + +/** Simplify a conjunction. */ +static struct bfs_expr *simplify_and(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_expr *ignorable = first_ignorable(opt, expr); + bool ignore = false; + + struct bfs_exprs children; + foster_children(expr, &children); + + while (!SLIST_EMPTY(&children)) { + struct bfs_expr *child = SLIST_POP(&children); + + if (child == ignorable) { + ignore = true; + } + + if (ignore) { + opt_delete(opt, "%pe [ignored result]\n", child); + opt_warning(opt, child, "The result of this expression is ignored.\n\n"); + continue; + } + + if (child->eval_fn == eval_true) { + opt_delete(opt, "%pe [conjunction elimination]\n", child); + continue; } - if (lhs) { - expr->persistent_fds += lhs->persistent_fds; - if (lhs->ephemeral_fds > expr->ephemeral_fds) { - expr->ephemeral_fds = lhs->ephemeral_fds; + + opt_visit(opt, "%pe\n", child); + bfs_expr_append(expr, child); + + if (child->always_false) { + while ((child = SLIST_POP(&children))) { + opt_delete(opt, "%pe [short-circuit]\n", child); } } - } else if (!expr->pure) { - df_join(opt->impure, &opt->before); } - if (expr->always_true) { - expr->probability = 1.0; - df_init_bottom(&opt->after_false); - } - if (expr->always_false) { - expr->probability = 0.0; - df_init_bottom(&opt->after_true); + struct bfs_expr *child = bfs_expr_children(expr); + if (!child) { + opt_debug(opt, "nullary identity\n"); + return opt_const(opt, true); + } else if (!child->next) { + opt_debug(opt, "unary identity\n"); + return only_child(expr); } - if (optlevel < 2 || expr->eval_fn == eval_true || expr->eval_fn == eval_false) { - return expr; - } + return lift_andor_not(opt, expr); +} - if (df_is_bottom(&opt->after_true)) { - if (expr->pure) { - struct bfs_expr *ret = opt_const(opt, false); - opt_warning(opt, expr, "This expression is always false.\n\n"); - opt_debug(opt, 2, "data flow: %pe --> %pe\n", expr, ret); - return ret; - } else { - expr->always_false = true; - expr->probability = 0.0; +/** Simplify a disjunction. */ +static struct bfs_expr *simplify_or(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_expr *ignorable = first_ignorable(opt, expr); + bool ignore = false; + + struct bfs_exprs children; + foster_children(expr, &children); + + while (!SLIST_EMPTY(&children)) { + struct bfs_expr *child = SLIST_POP(&children); + + if (child == ignorable) { + ignore = true; } - } else if (df_is_bottom(&opt->after_false)) { - if (expr->pure) { - struct bfs_expr *ret = opt_const(opt, true); - opt_debug(opt, 2, "data flow: %pe --> %pe\n", expr, ret); - opt_warning(opt, expr, "This expression is always true.\n\n"); - return ret; - } else { - expr->always_true = true; - expr->probability = 1.0; + + if (ignore) { + opt_delete(opt, "%pe [ignored result]\n", child); + opt_warning(opt, child, "The result of this expression is ignored.\n\n"); + continue; } - } - return expr; -} + if (child->eval_fn == eval_false) { + opt_delete(opt, "%pe [disjunctive syllogism]\n", child); + continue; + } -/** Swap the children of a binary expression if it would reduce the cost. */ -static bool reorder_expr(const struct bfs_opt *opt, struct bfs_expr *expr, float swapped_cost) { - if (swapped_cost < expr->cost) { - bool debug = opt_debug(opt, 3, "cost: %pe <==> ", expr); - struct bfs_expr *lhs = expr->lhs; - expr->lhs = expr->rhs; - expr->rhs = lhs; - if (debug) { - cfprintf(opt->ctx->cerr, "%pe (~${ylw}%g${rs} --> ~${ylw}%g${rs})\n", expr, expr->cost, swapped_cost); + opt_visit(opt, "%pe\n", child); + bfs_expr_append(expr, child); + + if (child->always_true) { + while ((child = SLIST_POP(&children))) { + opt_delete(opt, "%pe [short-circuit]\n", child); + } } - expr->cost = swapped_cost; - return true; - } else { - return false; } -} -/** - * Recursively reorder sub-expressions to reduce the overall cost. - * - * @param expr - * The expression to optimize. - * @return - * Whether any subexpression was reordered. - */ -static bool reorder_expr_recursive(const struct bfs_opt *opt, struct bfs_expr *expr) { - if (!bfs_expr_is_parent(expr)) { - return false; + struct bfs_expr *child = bfs_expr_children(expr); + if (!child) { + opt_debug(opt, "nullary identity\n"); + return opt_const(opt, false); + } else if (!child->next) { + opt_debug(opt, "unary identity\n"); + return only_child(expr); } - struct bfs_expr *lhs = expr->lhs; - struct bfs_expr *rhs = expr->rhs; + return lift_andor_not(opt, expr); +} - bool ret = false; - if (lhs) { - ret |= reorder_expr_recursive(opt, lhs); - } - if (rhs) { - ret |= reorder_expr_recursive(opt, rhs); - } +/** Simplify a comma expression. */ +static struct bfs_expr *simplify_comma(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + struct bfs_exprs children; + foster_children(expr, &children); + + while (!SLIST_EMPTY(&children)) { + struct bfs_expr *child = SLIST_POP(&children); - if (expr->eval_fn == eval_and || expr->eval_fn == eval_or) { - if (lhs->pure && rhs->pure) { - float rhs_prob = expr->eval_fn == eval_and ? rhs->probability : 1.0 - rhs->probability; - float swapped_cost = rhs->cost + rhs_prob * lhs->cost; - ret |= reorder_expr(opt, expr, swapped_cost); + if (opt->level >= 2 && child->pure && !SLIST_EMPTY(&children)) { + opt_delete(opt, "%pe [ignored result]\n", child); + opt_warning(opt, child, "The result of this expression is ignored.\n\n"); + continue; } + + opt_visit(opt, "%pe\n", child); + bfs_expr_append(expr, child); } - return ret; + struct bfs_expr *child = bfs_expr_children(expr); + if (child && !child->next) { + opt_debug(opt, "unary identity\n"); + return only_child(expr); + } + + return expr; } /** - * Optimize a top-level expression. + * Logical simplification visitor. */ -static struct bfs_expr *optimize_expr(struct bfs_opt *opt, struct bfs_expr *expr) { - struct df_domain saved_impure = *opt->impure; +static const struct visitor simplify = { + .name = "simplify", + .table = { + {eval_not, simplify_not}, + {eval_and, simplify_and}, + {eval_or, simplify_or}, + {eval_comma, simplify_comma}, + {NULL, NULL}, + }, +}; - expr = optimize_expr_recursive(opt, expr); - if (!expr) { - return NULL; - } +/** Optimize an expression. */ +static struct bfs_expr *optimize(struct bfs_opt *opt, struct bfs_expr *expr) { + opt_enter(opt, "pass 0:\n"); + expr = visit(opt, expr, &annotate); + opt_leave(opt, NULL); + + /** Table of optimization passes. */ + static const struct { + /** Minimum optlevel for this pass. */ + int level; + /** The visitor for this pass. */ + const struct visitor *visitor; + } passes[] = { + {1, &canonicalize}, + {3, &reorder}, + {2, &data_flow}, + {1, &simplify}, + }; - if (opt->ctx->optlevel >= 3 && reorder_expr_recursive(opt, expr)) { - // Re-do optimizations to account for the new ordering - *opt->impure = saved_impure; - expr = optimize_expr_recursive(opt, expr); - if (!expr) { - return NULL; + struct df_domain impure; + + for (int i = 0; i < 3; ++i) { + struct bfs_opt nested = *opt; + nested.impure = &impure; + impure = *opt->impure; + + opt_enter(&nested, "pass %d:\n", i + 1); + + for (size_t j = 0; j < countof(passes); ++j) { + if (opt->level < passes[j].level) { + continue; + } + + // Skip reordering the first time through the passes, to + // make warnings more understandable + if (passes[j].visitor == &reorder) { + if (i == 0) { + continue; + } else { + nested.warn = false; + } + } + + expr = visit(&nested, expr, passes[j].visitor); + if (!expr) { + return NULL; + } + } + + opt_leave(&nested, NULL); + + if (!bfs_expr_is_parent(expr)) { + break; } } + *opt->impure = impure; return expr; } @@ -1332,30 +2137,37 @@ int bfs_optimize(struct bfs_ctx *ctx) { struct bfs_opt opt = { .ctx = ctx, + .level = ctx->optlevel, + .depth = 0, + .warn = ctx->warn, + .ignore_result = false, .impure = &impure, }; df_init_top(&opt.before); - ctx->exclude = optimize_expr(&opt, ctx->exclude); + ctx->exclude = optimize(&opt, ctx->exclude); if (!ctx->exclude) { return -1; } // Only non-excluded files are evaluated opt.before = opt.after_false; + opt.ignore_result = true; struct df_range *depth = &opt.before.ranges[DEPTH_RANGE]; - constrain_min(depth, ctx->mindepth); - constrain_max(depth, ctx->maxdepth); + if (ctx->mindepth > 0) { + constrain_min(depth, ctx->mindepth); + } + if (ctx->maxdepth < INT_MAX) { + constrain_max(depth, ctx->maxdepth); + } - ctx->expr = optimize_expr(&opt, ctx->expr); + ctx->expr = optimize(&opt, ctx->expr); if (!ctx->expr) { return -1; } - ctx->expr = ignore_result(&opt, ctx->expr); - - if (df_is_bottom(&impure)) { + if (opt.level >= 2 && df_is_bottom(&impure)) { bfs_warning(ctx, "This command won't do anything.\n\n"); } @@ -1363,23 +2175,27 @@ int bfs_optimize(struct bfs_ctx *ctx) { long long mindepth = impure_depth->min; long long maxdepth = impure_depth->max; - int optlevel = ctx->optlevel; + opt_enter(&opt, "post-process:\n"); - if (optlevel >= 2 && mindepth > ctx->mindepth) { + if (opt.level >= 2 && mindepth > ctx->mindepth) { if (mindepth > INT_MAX) { mindepth = INT_MAX; } + opt_enter(&opt, "${blu}-mindepth${rs} ${bld}%d${rs}\n", ctx->mindepth); ctx->mindepth = mindepth; - opt_debug(&opt, 2, "data flow: mindepth --> %d\n", ctx->mindepth); + opt_leave(&opt, "${blu}-mindepth${rs} ${bld}%d${rs}\n", ctx->mindepth); } - if (optlevel >= 4 && maxdepth < ctx->maxdepth) { + if (opt.level >= 4 && maxdepth < ctx->maxdepth) { if (maxdepth < INT_MIN) { maxdepth = INT_MIN; } + opt_enter(&opt, "${blu}-maxdepth${rs} ${bld}%d${rs}\n", ctx->maxdepth); ctx->maxdepth = maxdepth; - opt_debug(&opt, 4, "data flow: maxdepth --> %d\n", ctx->maxdepth); + opt_leave(&opt, "${blu}-maxdepth${rs} ${bld}%d${rs}\n", ctx->maxdepth); } + opt_leave(&opt, NULL); + return 0; } diff --git a/src/parse.c b/src/parse.c index 17fe8ad..4212196 100644 --- a/src/parse.c +++ b/src/parse.c @@ -21,6 +21,7 @@ #include "exec.h" #include "expr.h" #include "fsade.h" +#include "list.h" #include "opt.h" #include "printf.h" #include "pwcache.h" @@ -47,10 +48,9 @@ #include // Strings printed by -D tree for "fake" expressions -static char *fake_and_arg = "-a"; -static char *fake_false_arg = "-false"; +static char *fake_and_arg = "-and"; static char *fake_hidden_arg = "-hidden"; -static char *fake_or_arg = "-o"; +static char *fake_or_arg = "-or"; static char *fake_print_arg = "-print"; static char *fake_true_arg = "-true"; @@ -319,12 +319,8 @@ static struct bfs_expr *new_unary_expr(const struct bfs_parser *parser, bfs_eval return NULL; } - expr->lhs = NULL; - expr->rhs = rhs; bfs_assert(bfs_expr_is_parent(expr)); - - expr->persistent_fds = rhs->persistent_fds; - expr->ephemeral_fds = rhs->ephemeral_fds; + bfs_expr_append(expr, rhs); return expr; } @@ -337,17 +333,9 @@ static struct bfs_expr *new_binary_expr(const struct bfs_parser *parser, bfs_eva return NULL; } - expr->lhs = lhs; - expr->rhs = rhs; bfs_assert(bfs_expr_is_parent(expr)); - - expr->persistent_fds = lhs->persistent_fds + rhs->persistent_fds; - if (lhs->ephemeral_fds > rhs->ephemeral_fds) { - expr->ephemeral_fds = lhs->ephemeral_fds; - } else { - expr->ephemeral_fds = rhs->ephemeral_fds; - } - + bfs_expr_append(expr, lhs); + bfs_expr_append(expr, rhs); return expr; } @@ -774,19 +762,6 @@ static struct bfs_expr *parse_unary_action(struct bfs_parser *parser, bfs_eval_f return parse_action(parser, eval_fn, 2); } -/** - * Add an expression to the exclusions. - */ -static int parse_exclude(struct bfs_parser *parser, struct bfs_expr *expr) { - struct bfs_ctx *ctx = parser->ctx; - ctx->exclude = new_binary_expr(parser, eval_or, ctx->exclude, expr, &fake_or_arg); - if (ctx->exclude) { - return 0; - } else { - return -1; - } -} - /** * Parse a test expression with integer data and a comparison flag. */ @@ -1839,10 +1814,7 @@ static struct bfs_expr *parse_nohidden(struct bfs_parser *parser, int arg1, int return NULL; } - if (parse_exclude(parser, hidden) != 0) { - return NULL; - } - + bfs_expr_append(parser->ctx->exclude, hidden); return parse_nullary_option(parser); } @@ -3206,10 +3178,7 @@ static struct bfs_expr *parse_factor(struct bfs_parser *parser) { parser->excluding = false; - if (parse_exclude(parser, factor) != 0) { - return NULL; - } - + bfs_expr_append(parser->ctx->exclude, factor); return parse_new_expr(parser, eval_true, parser->argv - argv, argv); } else if (strcmp(arg, "!") == 0 || strcmp(arg, "-not") == 0) { char **argv = parser_advance(parser, T_OPERATOR, 1); @@ -3428,19 +3397,29 @@ static void dump_expr_multiline(const struct bfs_ctx *ctx, enum debug_flags flag cfprintf(ctx->cerr, " "); } + bool close = true; + if (bfs_expr_is_parent(expr)) { - cfprintf(ctx->cerr, "(${red}%s${rs}\n", expr->argv[0]); - if (expr->lhs) { - dump_expr_multiline(ctx, flag, expr->lhs, indent + 1, 0); + if (SLIST_EMPTY(&expr->children)) { + cfprintf(ctx->cerr, "(${red}%s${rs}", expr->argv[0]); + ++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) { + int parens = child->next ? 0 : rparens + 1; + dump_expr_multiline(ctx, flag, child, indent + 1, parens); + } + close = false; } - dump_expr_multiline(ctx, flag, expr->rhs, indent + 1, rparens + 1); } else { if (flag == DEBUG_RATES) { cfprintf(ctx->cerr, "%pE", expr); } else { cfprintf(ctx->cerr, "%pe", expr); } + } + if (close) { for (int i = 0; i < rparens; ++i) { cfprintf(ctx->cerr, ")"); } @@ -3540,10 +3519,8 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { fputs("\n", stderr); - if (ctx->exclude->eval_fn != eval_false) { - bfs_debug(ctx, flag, "(${red}-exclude${rs}\n"); - dump_expr_multiline(ctx, flag, ctx->exclude, 1, 1); - } + bfs_debug(ctx, flag, "(${red}-exclude${rs}\n"); + dump_expr_multiline(ctx, flag, ctx->exclude, 1, 1); dump_expr_multiline(ctx, flag, ctx->expr, 0, 0); } @@ -3638,7 +3615,7 @@ struct bfs_ctx *bfs_parse_cmdline(int argc, char *argv[]) { .now = ctx->now, }; - ctx->exclude = parse_new_expr(&parser, eval_false, 1, &fake_false_arg); + ctx->exclude = parse_new_expr(&parser, eval_or, 1, &fake_or_arg); if (!ctx->exclude) { goto fail; } -- cgit v1.2.3 From 7e0467e79d9d4e561adc6c844e8ef481d8a6a7cc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 7 Jan 2024 12:33:47 -0500 Subject: eval: Check for xbasename() allocation failure --- src/eval.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/eval.c b/src/eval.c index 49c4aff..130b366 100644 --- a/src/eval.c +++ b/src/eval.c @@ -591,6 +591,7 @@ done: * -i?name test. */ bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) { + bool ret = false; const struct BFTW *ftwbuf = state->ftwbuf; const char *name = ftwbuf->path + ftwbuf->nameoff; @@ -599,9 +600,15 @@ bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) { // Any trailing slashes are not part of the name. This can only // happen for the root path. name = copy = xbasename(name); + if (!name) { + eval_report_error(state); + goto done; + } } - bool ret = eval_fnmatch(expr, name); + ret = eval_fnmatch(expr, name); + +done: free(copy); return ret; } -- cgit v1.2.3 From 4010140cb748cc4f7f57b0a3d514485796c665ce Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 7 Jan 2024 12:42:03 -0500 Subject: tests/bfs/D_opt: New test for more -D opt coverage --- tests/bfs/D_opt.out | 18 ++++++++++++++++++ tests/bfs/D_opt.sh | 1 + 2 files changed, 19 insertions(+) create mode 100644 tests/bfs/D_opt.out create mode 100644 tests/bfs/D_opt.sh diff --git a/tests/bfs/D_opt.out b/tests/bfs/D_opt.out new file mode 100644 index 0000000..3b461cf --- /dev/null +++ b/tests/bfs/D_opt.out @@ -0,0 +1,18 @@ +basic/a +basic/b +basic/c +basic/c/d +basic/e +basic/e/f +basic/g +basic/g/h +basic/i +basic/j +basic/j/foo +basic/k +basic/k/foo +basic/k/foo/bar +basic/l +basic/l/foo +basic/l/foo/bar +basic/l/foo/bar/baz diff --git a/tests/bfs/D_opt.sh b/tests/bfs/D_opt.sh new file mode 100644 index 0000000..d95cf86 --- /dev/null +++ b/tests/bfs/D_opt.sh @@ -0,0 +1 @@ +bfs_diff -D opt -nohidden -not \( -type b -o -type c \) -links -5 -links -10 -not -hidden basic -- cgit v1.2.3 From 7551c54b3dcab961aee5e38d6cbef49cced27f6e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 9 Jan 2024 13:28:45 -0500 Subject: build: Define _POSIX_PTHREAD_SEMANTICS for illumos Needed for the POSIX-compliant version of some interfaces. Link: https://illumos.org/man/3C/getpwnam --- GNUmakefile | 1 + 1 file changed, 1 insertion(+) diff --git a/GNUmakefile b/GNUmakefile index 7680cb3..a0719a2 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -53,6 +53,7 @@ LOCAL_CPPFLAGS := \ -D_DEFAULT_SOURCE \ -D_GNU_SOURCE \ -D_LARGEFILE64_SOURCE \ + -D_POSIX_PTHREAD_SEMANTICS \ -D_FILE_OFFSET_BITS=64 \ -D_TIME_BITS=64 \ -DBFS_VERSION=\"$(VERSION)\" -- cgit v1.2.3 From 3be3248d291f3bbfa2fdf9f80843b46e375f3aae Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 9 Jan 2024 13:48:22 -0500 Subject: build: Link with -lsocket -lnsl on illumos Link: https://illumos.org/man/3SOCKET/bind --- GNUmakefile | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/GNUmakefile b/GNUmakefile index a0719a2..66af797 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -165,6 +165,10 @@ ifeq ($(OS),DragonFly) LOCAL_LDLIBS += -lposix1e endif +ifeq ($(OS),SunOS) +LOCAL_LDLIBS += -lsocket -lnsl +endif + ifneq ($(filter gcov,$(MAKECMDGOALS)),) LOCAL_CFLAGS += --coverage # gcov only intercepts fork()/exec() with -std=gnu* -- cgit v1.2.3 From d39d6999a78747f8048fd57e2ca3143c81c53537 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 9 Jan 2024 13:17:24 -0500 Subject: config: Disable sys/acl.h on illumos Their ACL API is quite a bit different from most. Link: https://illumos.org/man/2/acl --- src/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index 24c6258..85ad57e 100644 --- a/src/config.h +++ b/src/config.h @@ -97,7 +97,7 @@ # define BFS_USE_PATHS_H BFS_HAS_PATHS_H #endif #ifndef BFS_USE_SYS_ACL_H -# define BFS_USE_SYS_ACL_H BFS_HAS_SYS_ACL_H +# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__) #endif #ifndef BFS_USE_SYS_CAPABILITY_H # define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__) -- cgit v1.2.3 From ae94cdc00136685abe61d55e1e357caaa636d785 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 9 Jan 2024 15:04:52 -0500 Subject: tests: Don't do chmod +s POSIX says > When using the symbolic mode form on a regular file, it is > implementation-defined whether or not: > > - Requests to set the set-user-ID-on-execution or set-group-ID-on- > execution bit when all execute bits are currently clear and none > are being set are ignored. And indeed, illumos ignores them with a warning: chmod: WARNING: /tmp/bfs.XXXX7KaGWb/rainbow/suid: Execute permission required for set-ID on execution Link: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/chmod.html --- tests/stddirs.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/stddirs.sh b/tests/stddirs.sh index a51c878..e08e6bf 100644 --- a/tests/stddirs.sh +++ b/tests/stddirs.sh @@ -106,8 +106,9 @@ make_rainbow() { ln -s nowhere "$1/broken" "$MKSOCK" "$1/socket" "$XTOUCH" -p "$1"/s{u,g,ug}id - chmod u+s "$1"/su{,g}id - chmod g+s "$1"/s{u,}gid + chmod 06644 "$1"/sugid + chmod 04644 "$1"/suid + chmod 02644 "$1"/sgid mkdir "$1/ow" "$1"/sticky{,_ow} chmod o+w "$1"/*ow chmod +t "$1"/sticky* -- cgit v1.2.3 From ae2934447147b106397829ef9a6d8cf03514240c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 9 Jan 2024 10:51:32 -0500 Subject: ci: Add an OmniOS builder --- .github/workflows/ci.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c61dc5..dbf77c9 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -183,3 +183,35 @@ jobs: chown -R action:action . jobs=$(sysctl -n hw.ncpu) sudo -u action gmake -j$jobs check JOBS=-j$jobs TEST_FLAGS="--sudo --verbose=skipped" + + omnios: + name: OmniOS + + runs-on: ubuntu-22.04 + + steps: + - uses: actions/checkout@v4 + + - name: Run tests + uses: vmactions/omnios-vm@v1 + with: + release: "r151048" + usesh: true + copyback: false + + prepare: | + pkg install \ + bash \ + build-essential \ + expect \ + gnu-make \ + onig \ + sudo + useradd -m -g staff action + echo "%staff ALL=(ALL) NOPASSWD: ALL" >>/etc/sudoers + + run: | + PATH="/usr/xpg4/bin:$PATH" + chown -R action:staff . + jobs=$(getconf NPROCESSORS_ONLN) + sudo -u action gmake -j$jobs check LDFLAGS="-Wl,-rpath,/opt/ooce/lib/amd64" JOBS=-j$jobs TEST_FLAGS="--sudo --verbose=skipped" -- cgit v1.2.3 From 67eebd6fd514ede9e10cd4639b8d0cce690513f4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 Jan 2024 13:01:14 -0500 Subject: tests: New output format --- tests/run.sh | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/tests/run.sh b/tests/run.sh index 785c414..41df716 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -51,10 +51,24 @@ bg_test() { fi ret=$? - if ((ret != 0 && ret != EX_SKIP)); then - ((VERBOSE_ERRORS)) || cat "$TMP/$TEST.err" >&2 - color printf "${BOL}${RED}%s failed!${RST}\n" "$TEST" - fi + case $ret in + 0) + if ((VERBOSE_TESTS)); then + color printf "${BOL}${GRN}[PASS]${RST} ${BLD}%s${RST}\n" "$TEST" + fi + ;; + $EX_SKIP) + if ((VERBOSE_SKIPPED || VERBOSE_TESTS)); then + color printf "${BOL}${CYN}[SKIP]${RST} ${BLD}%s${RST}\n" "$TEST" + fi + ;; + *) + if ((!VERBOSE_ERRORS)); then + cat "$TMP/$TEST.err" >&2 + fi + color printf "${BOL}${RED}[FAIL]${RST} ${BLD}%s${RST}\n" "$TEST" + ;; + esac return $ret } @@ -114,9 +128,11 @@ run_tests() { passed=0 failed=0 skipped=0 + ran=0 + total=${#TEST_CASES[@]} if ((COLOR_STDOUT || VERBOSE_TESTS)); then - TEST_FMT="${BOL}${YLW}%s${RST}${EOL}" + TEST_FMT="${BOL}${YLW}[%3d%%]${RST} ${BLD}%s${RST}${EOL}" else TEST_FMT="." fi @@ -134,13 +150,14 @@ run_tests() { fi fi - printf "$TEST_FMT" "$TEST" + percent=$((100 * ran / total)) + printf "$TEST_FMT" $percent "$TEST" mkdir -p "$TMP/$TEST" OUT="$TMP/$TEST.out" bg_test "$TESTS/$TEST.sh" & - ((++BG)) + ((++BG, ++ran)) done while ((BG > 0)); do @@ -150,13 +167,13 @@ run_tests() { printf "${BOL}" if ((passed > 0)); then - color printf "${GRN}tests passed: %d${RST}\n" "$passed" + color printf "${GRN}[PASS]${RST} ${BLD}%3d${RST} / ${BLD}%d${RST}\n" $passed $total fi if ((skipped > 0)); then - color printf "${CYN}tests skipped: %s${RST}\n" "$skipped" + color printf "${CYN}[SKIP]${RST} ${BLD}%3d${RST} / ${BLD}%d${RST}\n" $skipped $total fi if ((failed > 0)); then - color printf "${RED}tests failed: %s${RST}\n" "$failed" + color printf "${RED}[FAIL]${RST} ${BLD}%3d${RST} / ${BLD}%d${RST}\n" $failed $total exit 1 fi } @@ -180,10 +197,8 @@ skip() { caller | { read -r line file printf "${BOL}" - debug "$file" $line "${CYN}$TEST skipped!${RST}" "$(awk "NR == $line" "$file")" >&3 + debug "$file" $line "" "$(awk "NR == $line" "$file")" >&3 } - elif ((VERBOSE_TESTS)); then - color printf "${BOL}${CYN}%s skipped!${RST}\n" "$TEST" fi exit $EX_SKIP -- cgit v1.2.3 From a9c9fc392bf2c3803251e5f63c944cd221a1ca67 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 12 Jan 2024 12:01:07 -0500 Subject: tests: Merge unit test executables into one --- GNUmakefile | 27 +++++++++------ tests/alloc.c | 6 ++-- tests/bfstd.c | 5 +-- tests/bit.c | 5 +-- tests/main.c | 106 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/tests.h | 26 ++++++++++++++ tests/trie.c | 5 +-- tests/xtime.c | 95 ++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xtimegm.c | 94 ------------------------------------------------- 9 files changed, 256 insertions(+), 113 deletions(-) create mode 100644 tests/main.c create mode 100644 tests/tests.h create mode 100644 tests/xtime.c delete mode 100644 tests/xtimegm.c diff --git a/GNUmakefile b/GNUmakefile index 66af797..34c7f37 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -269,32 +269,39 @@ LIBBFS := \ # The main executable $(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) -# Standalone unit tests -UNITS := alloc bfstd bit trie xtimegm -UNIT_TESTS := $(UNITS:%=$(BIN)/tests/%) -UNIT_CHECKS := $(UNITS:%=check-%) - # Testing utilities TEST_UTILS := $(BIN)/tests/mksock $(BIN)/tests/xtouch -TESTS := $(UNIT_TESTS) $(TEST_UTILS) +$(BIN)/tests/mksock: $(OBJ)/tests/mksock.o $(LIBBFS) + +$(BIN)/tests/xtouch: $(OBJ)/tests/xtouch.o $(LIBBFS) + +# All test binaries +TESTS := $(BIN)/tests/units $(TEST_UTILS) + +$(BIN)/tests/units: \ + $(OBJ)/tests/alloc.o \ + $(OBJ)/tests/bfstd.o \ + $(OBJ)/tests/bit.o \ + $(OBJ)/tests/main.o \ + $(OBJ)/tests/trie.o \ + $(OBJ)/tests/xtime.o \ + $(LIBBFS) tests: $(TESTS) .PHONY: tests -$(TESTS): $(BIN)/tests/%: $(OBJ)/tests/%.o $(LIBBFS) - # The different search strategies that we test STRATEGIES := bfs dfs ids eds STRATEGY_CHECKS := $(STRATEGIES:%=check-%) # All the different checks we run -CHECKS := $(UNIT_CHECKS) $(STRATEGY_CHECKS) +CHECKS := check-units $(STRATEGY_CHECKS) check: $(CHECKS) .PHONY: check $(CHECKS) -$(UNIT_CHECKS): check-%: $(BIN)/tests/% +check-units: $(BIN)/tests/units $< JOBS := $(filter -j%,$(MAKEFLAGS)) diff --git a/tests/alloc.c b/tests/alloc.c index 37b70bf..e14f131 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -1,13 +1,14 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "tests.h" #include "../src/alloc.h" #include "../src/diag.h" #include #include #include -int main(void) { +bool check_alloc(void) { // Check sizeof_flex() struct flexible { alignas(64) int foo[8]; @@ -44,6 +45,5 @@ int main(void) { } varena_destroy(&varena); - - return EXIT_SUCCESS; + return true; } diff --git a/tests/bfstd.c b/tests/bfstd.c index c386279..d385c6b 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,6 +1,7 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "tests.h" #include "../src/bfstd.h" #include "../src/config.h" #include "../src/diag.h" @@ -34,7 +35,7 @@ static void check_wordesc(const char *str, const char *exp, enum wesc_flags flag bfs_verify(strcmp(buf, exp) == 0, "wordesc(%s) == %s (!= %s)", str, buf, exp); } -int main(void) { +bool check_bfstd(void) { // Try to set a UTF-8 locale if (!setlocale(LC_ALL, "C.UTF-8")) { setlocale(LC_ALL, ""); @@ -69,5 +70,5 @@ int main(void) { check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY); } - return EXIT_SUCCESS; + return true; } diff --git a/tests/bit.c b/tests/bit.c index f9071be..1c6a4de 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -1,6 +1,7 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "tests.h" #include "../src/bit.h" #include "../src/diag.h" #include @@ -54,7 +55,7 @@ bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); #define verify_eq(a, b) \ bfs_verify((a) == (b), "(0x%jX) %s != %s (0x%jX)", (uintmax_t)(a), #a, #b, (uintmax_t)(b)) -int main(void) { +bool check_bit(void) { verify_eq(bswap((uint8_t)0x12), 0x12); verify_eq(bswap((uint16_t)0x1234), 0x3412); verify_eq(bswap((uint32_t)0x12345678), 0x78563412); @@ -121,5 +122,5 @@ int main(void) { bfs_verify(!has_single_bit(UINT32_MAX)); bfs_verify(has_single_bit((uint32_t)1 << (UINT_WIDTH - 1))); - return EXIT_SUCCESS; + return true; } diff --git a/tests/main.c b/tests/main.c new file mode 100644 index 0000000..b7292b4 --- /dev/null +++ b/tests/main.c @@ -0,0 +1,106 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Entry point for unit tests. + */ + +#include "tests.h" +#include "../src/bfstd.h" +#include "../src/color.h" +#include "../src/config.h" +#include "../src/diag.h" +#include +#include +#include +#include + +/** + * Test context. + */ +struct test_ctx { + /** Number of command line arguments. */ + int argc; + /** The arguments themselves. */ + char **argv; + + /** Parsed colors. */ + struct colors *colors; + /** Colorized output stream. */ + CFILE *cout; + + /** Eventual exit status. */ + int ret; +}; + +/** Initialize the test context. */ +static int test_init(struct test_ctx *ctx, int argc, char **argv) { + ctx->argc = argc; + ctx->argv = argv; + + ctx->colors = parse_colors(); + ctx->cout = cfwrap(stdout, ctx->colors, false); + if (!ctx->cout) { + ctx->ret = EXIT_FAILURE; + return -1; + } + + ctx->ret = EXIT_SUCCESS; + return 0; +} + +/** Finalize the test context. */ +static int test_fini(struct test_ctx *ctx) { + if (ctx->cout) { + cfclose(ctx->cout); + } + + free_colors(ctx->colors); + + return ctx->ret; +} + +/** Check if a test case is enabled for this run. */ +static bool should_run(const struct test_ctx *ctx, const char *test) { + // Run all tests by default + if (ctx->argc < 2) { + return true; + } + + // With args, run only specified tests + for (int i = 1; i < ctx->argc; ++i) { + if (strcmp(test, ctx->argv[i]) == 0) { + return true; + } + } + + return false; +} + +/** Run a test if it's enabled. */ +static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) { + if (should_run(ctx, test)) { + if (fn()) { + cfprintf(ctx->cout, "${grn}[PASS]${rs} ${bld}%s${rs}\n", test); + } else { + cfprintf(ctx->cout, "${red}[FAIL]${rs} ${bld}%s${rs}\n", test); + ctx->ret = EXIT_FAILURE; + } + } +} + +int main(int argc, char *argv[]) { + struct test_ctx ctx; + if (test_init(&ctx, argc, argv) != 0) { + goto done; + } + + run_test(&ctx, "alloc", check_alloc); + run_test(&ctx, "bfstd", check_bfstd); + run_test(&ctx, "bit", check_bit); + run_test(&ctx, "trie", check_trie); + run_test(&ctx, "xtime", check_xtime); + +done: + return test_fini(&ctx); +} diff --git a/tests/tests.h b/tests/tests.h new file mode 100644 index 0000000..34c58b7 --- /dev/null +++ b/tests/tests.h @@ -0,0 +1,26 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Unit tests. + */ + +#include "../src/config.h" + +/** Unit test function type. */ +typedef bool test_fn(void); + +/** Memory allocation tests. */ +bool check_alloc(void); + +/** Standard library wrapper tests. */ +bool check_bfstd(void); + +/** Bit manipulation tests. */ +bool check_bit(void); + +/** Trie tests. */ +bool check_trie(void); + +/** Time tests. */ +bool check_xtime(void); diff --git a/tests/trie.c b/tests/trie.c index 656fd85..f94d7c8 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -1,6 +1,7 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "tests.h" #include "../src/trie.h" #include "../src/config.h" #include "../src/diag.h" @@ -38,7 +39,7 @@ const char *keys[] = { const size_t nkeys = countof(keys); -int main(void) { +bool check_trie(void) { struct trie trie; trie_init(&trie); @@ -130,5 +131,5 @@ int main(void) { free(longstr); trie_destroy(&trie); - return EXIT_SUCCESS; + return true; } diff --git a/tests/xtime.c b/tests/xtime.c new file mode 100644 index 0000000..53ecbc4 --- /dev/null +++ b/tests/xtime.c @@ -0,0 +1,95 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "tests.h" +#include "../src/xtime.h" +#include "../src/config.h" +#include +#include +#include +#include + +static bool tm_equal(const struct tm *tma, const struct tm *tmb) { + if (tma->tm_year != tmb->tm_year) { + return false; + } + if (tma->tm_mon != tmb->tm_mon) { + return false; + } + if (tma->tm_mday != tmb->tm_mday) { + return false; + } + if (tma->tm_hour != tmb->tm_hour) { + return false; + } + if (tma->tm_min != tmb->tm_min) { + return false; + } + if (tma->tm_sec != tmb->tm_sec) { + return false; + } + if (tma->tm_wday != tmb->tm_wday) { + return false; + } + if (tma->tm_yday != tmb->tm_yday) { + return false; + } + if (tma->tm_isdst != tmb->tm_isdst) { + return false; + } + + return true; +} + +static void tm_print(FILE *file, const struct tm *tm) { + fprintf(file, "Y%d M%d D%d h%d m%d s%d wd%d yd%d%s\n", + tm->tm_year, tm->tm_mon, tm->tm_mday, + tm->tm_hour, tm->tm_min, tm->tm_sec, + tm->tm_wday, tm->tm_yday, + tm->tm_isdst ? (tm->tm_isdst < 0 ? " (DST?)" : " (DST)") : ""); +} + +bool check_xtime(void) { + if (setenv("TZ", "UTC0", true) != 0) { + perror("setenv()"); + return false; + } + + struct tm tm = { + .tm_isdst = -1, + }; + + for (tm.tm_year = 10; tm.tm_year <= 200; tm.tm_year += 10) + for (tm.tm_mon = -3; tm.tm_mon <= 15; tm.tm_mon += 3) + for (tm.tm_mday = -31; tm.tm_mday <= 61; tm.tm_mday += 4) + for (tm.tm_hour = -1; tm.tm_hour <= 24; tm.tm_hour += 5) + for (tm.tm_min = -1; tm.tm_min <= 60; tm.tm_min += 31) + for (tm.tm_sec = -60; tm.tm_sec <= 120; tm.tm_sec += 5) { + struct tm tma = tm, tmb = tm; + time_t ta, tb; + ta = mktime(&tma); + if (xtimegm(&tmb, &tb) != 0) { + tb = -1; + } + + bool fail = false; + if (ta != tb) { + printf("Mismatch: %jd != %jd\n", (intmax_t)ta, (intmax_t)tb); + fail = true; + } + if (ta != -1 && !tm_equal(&tma, &tmb)) { + printf("mktime(): "); + tm_print(stdout, &tma); + printf("xtimegm(): "); + tm_print(stdout, &tmb); + fail = true; + } + if (fail) { + printf("Input: "); + tm_print(stdout, &tm); + return false; + } + } + + return true; +} diff --git a/tests/xtimegm.c b/tests/xtimegm.c deleted file mode 100644 index 973b2eb..0000000 --- a/tests/xtimegm.c +++ /dev/null @@ -1,94 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include "../src/xtime.h" -#include "../src/config.h" -#include -#include -#include -#include - -static bool tm_equal(const struct tm *tma, const struct tm *tmb) { - if (tma->tm_year != tmb->tm_year) { - return false; - } - if (tma->tm_mon != tmb->tm_mon) { - return false; - } - if (tma->tm_mday != tmb->tm_mday) { - return false; - } - if (tma->tm_hour != tmb->tm_hour) { - return false; - } - if (tma->tm_min != tmb->tm_min) { - return false; - } - if (tma->tm_sec != tmb->tm_sec) { - return false; - } - if (tma->tm_wday != tmb->tm_wday) { - return false; - } - if (tma->tm_yday != tmb->tm_yday) { - return false; - } - if (tma->tm_isdst != tmb->tm_isdst) { - return false; - } - - return true; -} - -static void tm_print(FILE *file, const struct tm *tm) { - fprintf(file, "Y%d M%d D%d h%d m%d s%d wd%d yd%d%s\n", - tm->tm_year, tm->tm_mon, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec, - tm->tm_wday, tm->tm_yday, - tm->tm_isdst ? (tm->tm_isdst < 0 ? " (DST?)" : " (DST)") : ""); -} - -int main(void) { - if (setenv("TZ", "UTC0", true) != 0) { - perror("setenv()"); - return EXIT_FAILURE; - } - - struct tm tm = { - .tm_isdst = -1, - }; - - for (tm.tm_year = 10; tm.tm_year <= 200; tm.tm_year += 10) - for (tm.tm_mon = -3; tm.tm_mon <= 15; tm.tm_mon += 3) - for (tm.tm_mday = -31; tm.tm_mday <= 61; tm.tm_mday += 4) - for (tm.tm_hour = -1; tm.tm_hour <= 24; tm.tm_hour += 5) - for (tm.tm_min = -1; tm.tm_min <= 60; tm.tm_min += 31) - for (tm.tm_sec = -60; tm.tm_sec <= 120; tm.tm_sec += 5) { - struct tm tma = tm, tmb = tm; - time_t ta, tb; - ta = mktime(&tma); - if (xtimegm(&tmb, &tb) != 0) { - tb = -1; - } - - bool fail = false; - if (ta != tb) { - printf("Mismatch: %jd != %jd\n", (intmax_t)ta, (intmax_t)tb); - fail = true; - } - if (ta != -1 && !tm_equal(&tma, &tmb)) { - printf("mktime(): "); - tm_print(stdout, &tma); - printf("xtimegm(): "); - tm_print(stdout, &tmb); - fail = true; - } - if (fail) { - printf("Input: "); - tm_print(stdout, &tm); - return EXIT_FAILURE; - } - } - - return EXIT_SUCCESS; -} -- cgit v1.2.3 From d6cae04b54c1d58223e1719101b7c54d348e8d80 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 13 Jan 2024 11:44:09 -0500 Subject: tests.h: Add a header guard --- tests/tests.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/tests.h b/tests/tests.h index 34c58b7..d2f3611 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -5,6 +5,9 @@ * Unit tests. */ +#ifndef BFS_TESTS_H +#define BFS_TESTS_H + #include "../src/config.h" /** Unit test function type. */ @@ -24,3 +27,5 @@ bool check_trie(void); /** Time tests. */ bool check_xtime(void); + +#endif // BFS_TESTS_H -- cgit v1.2.3 From e9588c49d5539ded993f720fc6855d6fa878c997 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 13 Jan 2024 12:42:42 -0500 Subject: bfstd: New {error,errno}_is_like() functions We used to have is_nonexistence_error() to consistently treat ENOENT and ENOTDIR the same. Recently, we started considering EFAULT the same as ENAMETOOLONG on DragonFly BSD to work around a kernel bug. Unify both of these behind a more generic interface. --- src/bfstd.c | 23 +++++++++++++++++++++-- src/bfstd.h | 18 ++++++++++++++++-- src/bftw.c | 18 +++--------------- src/eval.c | 2 +- src/stat.c | 2 +- tests/xtouch.c | 17 +++-------------- 6 files changed, 45 insertions(+), 35 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index c0e61cb..15e8667 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -36,8 +36,27 @@ # include #endif -bool is_nonexistence_error(int error) { - return error == ENOENT || errno == ENOTDIR; +bool error_is_like(int error, int category) { + if (error == category) { + return true; + } + + switch (category) { + case ENOENT: + return error == ENOTDIR; + +#if __DragonFly__ + // https://twitter.com/tavianator/status/1742991411203485713 + case ENAMETOOLONG: + return error == EFAULT; +#endif + } + + return false; +} + +bool errno_is_like(int category) { + return error_is_like(errno, category); } char *xdirname(const char *path) { diff --git a/src/bfstd.h b/src/bfstd.h index 0fcb892..8953b9b 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -45,9 +45,23 @@ // #include /** - * Return whether an error code is due to a path not existing. + * Check if an error code is "like" another one. For example, ENOTDIR is + * like ENOENT because they can both be triggered by non-existent paths. + * + * @param error + * The error code to check. + * @param category + * The category to test for. Known categories include ENOENT and + * ENAMETOOLONG. + * @return + * Whether the error belongs to the given category. + */ +bool error_is_like(int error, int category); + +/** + * Equivalent to error_is_like(errno, category). */ -bool is_nonexistence_error(int error); +bool errno_is_like(int category); #include diff --git a/src/bftw.c b/src/bftw.c index 355cb54..49f07df 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -68,7 +68,7 @@ const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags } } else { ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); - if (!ret && (flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(errno)) { + if (!ret && (flags & BFS_STAT_TRYFOLLOW) && errno_is_like(ENOENT)) { ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); } } @@ -81,7 +81,7 @@ const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat return ftwbuf->lstat_cache.buf; } else if (ftwbuf->stat_cache.buf) { return ftwbuf->stat_cache.buf; - } else if ((flags & BFS_STAT_TRYFOLLOW) && is_nonexistence_error(ftwbuf->stat_cache.error)) { + } else if ((flags & BFS_STAT_TRYFOLLOW) && error_is_like(ftwbuf->stat_cache.error, ENOENT)) { return ftwbuf->lstat_cache.buf; } else { return NULL; @@ -739,22 +739,10 @@ static int bftw_file_open(struct bftw_state *state, struct bftw_file *file, cons } int fd = bftw_file_openat(state, file, base, at_path); - if (fd >= 0) { + if (fd >= 0 || !errno_is_like(ENAMETOOLONG)) { return fd; } - switch (errno) { - case ENAMETOOLONG: -#if __DragonFly__ - // https://twitter.com/tavianator/status/1742991411203485713 - case EFAULT: -#endif - break; - - default: - return -1; - } - // Handle ENAMETOOLONG by manually traversing the path component-by-component struct bftw_list parents; SLIST_INIT(&parents); diff --git a/src/eval.c b/src/eval.c index 130b366..1a814b3 100644 --- a/src/eval.c +++ b/src/eval.c @@ -80,7 +80,7 @@ static void eval_error(struct bfs_eval *state, const char *format, ...) { */ static bool eval_should_ignore(const struct bfs_eval *state, int error) { return state->ctx->ignore_races - && is_nonexistence_error(error) + && error_is_like(error, ENOENT) && state->ftwbuf->depth > 0; } diff --git a/src/stat.c b/src/stat.c index d7387c6..91aa092 100644 --- a/src/stat.c +++ b/src/stat.c @@ -260,7 +260,7 @@ static int bfs_stat_tryfollow(int at_fd, const char *at_path, int at_flags, int if (ret != 0 && (bfs_flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW - && is_nonexistence_error(errno)) + && errno_is_like(ENOENT)) { at_flags |= AT_SYMLINK_NOFOLLOW; ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf); diff --git a/tests/xtouch.c b/tests/xtouch.c index ed8bbee..6099128 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -68,22 +68,11 @@ static int open_parent(const struct args *args, const char **path) { goto done; } - switch (errno) { - case ENAMETOOLONG: -#if __DragonFly__ - // https://twitter.com/tavianator/status/1742991411203485713 - case EFAULT: -#endif - break; - - case ENOENT: - if (args->flags & CREATE_PARENTS) { - break; - } else { + if (errno == ENOENT) { + if (!(args->flags & CREATE_PARENTS)) { goto err; } - - default: + } else if (!errno_is_like(ENAMETOOLONG)) { goto err; } -- cgit v1.2.3 From e766015d60c927aae03b8e43d956c6976c16b2ba Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 13 Jan 2024 15:54:46 -0500 Subject: ioq: Use the negative errno convention --- src/bfstd.c | 9 +++++++++ src/bfstd.h | 10 ++++++++++ src/bftw.c | 2 +- src/ioq.c | 32 +++++++++++--------------------- src/ioq.h | 6 ++---- 5 files changed, 33 insertions(+), 26 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 15e8667..0a9b87c 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -59,6 +59,15 @@ bool errno_is_like(int category) { return error_is_like(errno, category); } +int try(int ret) { + if (ret >= 0) { + return ret; + } else { + bfs_assert(errno > 0, "errno should be positive, was %d\n", errno); + return -errno; + } +} + char *xdirname(const char *path) { size_t i = xbaseoff(path); diff --git a/src/bfstd.h b/src/bfstd.h index 8953b9b..d160c88 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -63,6 +63,16 @@ bool error_is_like(int error, int category); */ bool errno_is_like(int category); +/** + * Apply the "negative errno" convention. + * + * @param ret + * The return value of the attempted operation. + * @return + * ret, if non-negative, otherwise -errno. + */ +int try(int ret); + #include #ifndef O_EXEC diff --git a/src/bftw.c b/src/bftw.c index 49f07df..952b090 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -621,7 +621,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { } dir = ent->opendir.dir; - if (ent->ret == 0) { + if (ent->result >= 0) { bftw_file_set_dir(cache, file, dir); } else { bftw_freedir(cache, dir); diff --git a/src/ioq.c b/src/ioq.c index 2739338..89ebb3e 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -455,42 +455,35 @@ static bool ioq_check_cancel(struct ioq *ioq, struct ioq_ent *ent) { return false; } - ent->ret = -1; - ent->error = EINTR; + ent->result = -EINTR; ioqq_push(ioq->ready, ent); return true; } /** Handle a single request synchronously. */ static void ioq_handle(struct ioq *ioq, struct ioq_ent *ent) { - int ret; - switch (ent->op) { case IOQ_CLOSE: - ret = xclose(ent->close.fd); + ent->result = try(xclose(ent->close.fd)); break; case IOQ_OPENDIR: - ret = bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path, ent->opendir.flags); - if (ret == 0) { + ent->result = try(bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path, ent->opendir.flags)); + if (ent->result >= 0) { bfs_polldir(ent->opendir.dir); } break; case IOQ_CLOSEDIR: - ret = bfs_closedir(ent->closedir.dir); + ent->result = try(bfs_closedir(ent->closedir.dir)); break; default: bfs_bug("Unknown ioq_op %d", (int)ent->op); - ret = -1; - errno = ENOSYS; + ent->result = -ENOSYS; break; } - ent->ret = ret; - ent->error = ret == 0 ? 0 : errno; - ioqq_push(ioq->ready, ent); } @@ -603,24 +596,21 @@ static void ioq_ring_reap(struct ioq_ring_state *state) { } struct ioq_ent *ent = io_uring_cqe_get_data(cqe); - ent->ret = cqe->res >= 0 ? cqe->res : -1; - ent->error = cqe->res < 0 ? -cqe->res : 0; + ent->result = cqe->res; io_uring_cqe_seen(ring, cqe); --state->submitted; - if (ent->op == IOQ_OPENDIR && ent->ret >= 0) { - int fd = ent->ret; + if (ent->op == IOQ_OPENDIR && ent->result >= 0) { + int fd = ent->result; if (ioq_check_cancel(ioq, ent)) { xclose(fd); continue; } - ent->ret = bfs_opendir(ent->opendir.dir, fd, NULL, ent->opendir.flags); - if (ent->ret == 0) { + ent->result = try(bfs_opendir(ent->opendir.dir, fd, NULL, ent->opendir.flags)); + if (ent->result >= 0) { // TODO: io_uring_prep_getdents() bfs_polldir(ent->opendir.dir); - } else { - ent->error = errno; } } diff --git a/src/ioq.h b/src/ioq.h index 87727cb..e1e5052 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -36,10 +36,8 @@ struct ioq_ent { /** The I/O operation. */ enum ioq_op op; - /** The return value of the operation. */ - int ret; - /** The error code, if the operation failed. */ - int error; + /** The return value (on success) or negative error code (on failure). */ + int result; /** Arbitrary user data. */ void *ptr; -- cgit v1.2.3 From ef03769f3f69f3d6d9d3427b1d84ec00a1c3cc36 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 13 Jan 2024 16:14:06 -0500 Subject: tests: Properly filter escape sequences with --verbose=tests --- tests/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run.sh b/tests/run.sh index 41df716..4a159f9 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -151,7 +151,7 @@ run_tests() { fi percent=$((100 * ran / total)) - printf "$TEST_FMT" $percent "$TEST" + color printf "$TEST_FMT" $percent "$TEST" mkdir -p "$TMP/$TEST" OUT="$TMP/$TEST.out" -- cgit v1.2.3 From eaf38feeb43cb2e6558b685d51f36ae775315339 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Jan 2024 13:55:23 -0500 Subject: fsade: Mark bfs_acl_tag_type() as maybe_unused It's not used on at least macOS, which doesn't have ACL_{USER,GROUP}_OBJ or ACL_OTHER. --- src/fsade.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/fsade.c b/src/fsade.c index d6ef4b8..ee17416 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -149,6 +149,7 @@ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { } /** Unified interface for acl_get_tag_type(). */ +attr(maybe_unused) static int bfs_acl_tag_type(acl_entry_t entry, acl_tag_t *tag) { #if __DragonFly__ *tag = entry->ae_tag; -- cgit v1.2.3 From 6f5c265e84f565fd37ea2ad38d7f588ca08b0f03 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Jan 2024 13:57:33 -0500 Subject: xspawn: Check for _POSIX_SPAWN in bfs_spawn_addfchdir() --- src/xspawn.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xspawn.c b/src/xspawn.c index 98f1f65..8d6108b 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -186,7 +186,7 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { # define BFS_POSIX_SPAWN_FCHDIR posix_spawn_file_actions_addfchdir_np #endif -#ifdef BFS_POSIX_SPAWN_FCHDIR +#if _POSIX_SPAWN > 0 && defined(BFS_POSIX_SPAWN_FCHDIR) if (ctx->flags & BFS_SPAWN_USE_POSIX) { errno = BFS_POSIX_SPAWN_FCHDIR(&ctx->actions, fd); if (errno != 0) { -- cgit v1.2.3 From 40e195c1bd60737964bb2b8ab25780a02003f8e1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Jan 2024 13:58:39 -0500 Subject: tests/posix: Avoid catastrophic backtracking Using -path 'deep/*/*/.../*' to simulate -mindepth 18 falls off a performance cliff on systems that use backtracking for fnmatch(). This was observed on macOS 12.4. Instead, just use -type f. --- tests/posix/nogroup_ulimit.sh | 7 +------ tests/posix/nouser_ulimit.sh | 6 +----- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/tests/posix/nogroup_ulimit.sh b/tests/posix/nogroup_ulimit.sh index 2186321..5b4c440 100644 --- a/tests/posix/nogroup_ulimit.sh +++ b/tests/posix/nogroup_ulimit.sh @@ -1,7 +1,2 @@ ulimit -n 16 - -# -mindepth 18, but POSIX -path="*/*/*/*/*/*" -path="$path/$path/$path" -bfs_diff deep -path "deep/$path" -nogroup - +bfs_diff deep -type f -nogroup diff --git a/tests/posix/nouser_ulimit.sh b/tests/posix/nouser_ulimit.sh index be0a65f..e1400f0 100644 --- a/tests/posix/nouser_ulimit.sh +++ b/tests/posix/nouser_ulimit.sh @@ -1,6 +1,2 @@ ulimit -n 16 - -# -mindepth 18, but POSIX -path="*/*/*/*/*/*" -path="$path/$path/$path" -bfs_diff deep -path "deep/$path" -nouser +bfs_diff deep -type f -nouser -- cgit v1.2.3 From 3fa8f1a22ad120322bbc68823ca9759d8939b131 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 Jan 2024 11:06:28 -0500 Subject: stat: Expose bfs_stat{,x}_convert() --- src/stat.c | 183 +++++++++++++++++++++++++++++-------------------------------- src/stat.h | 41 +++++++++++--- 2 files changed, 121 insertions(+), 103 deletions(-) diff --git a/src/stat.c b/src/stat.c index 91aa092..8da1466 100644 --- a/src/stat.c +++ b/src/stat.c @@ -13,20 +13,12 @@ #include #include -#if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30) -# define BFS_HAS_LIBC_STATX true -#elif __linux__ +#if BFS_USE_STATX && !BFS_HAS_LIBC_STATX # include # include # include #endif -#ifndef BFS_USE_STATX -# if BFS_HAS_LIBC_STATX || defined(SYS_statx) -# define BFS_USE_STATX true -# endif -#endif - const char *bfs_stat_field_name(enum bfs_stat_field field) { switch (field) { case BFS_STAT_DEV: @@ -65,56 +57,53 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { return "???"; } -/** - * Convert a struct stat to a struct bfs_stat. - */ -static void bfs_stat_convert(const struct stat *statbuf, struct bfs_stat *buf) { - buf->mask = 0; +void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { + dest->mask = 0; - buf->dev = statbuf->st_dev; - buf->mask |= BFS_STAT_DEV; + dest->dev = src->st_dev; + dest->mask |= BFS_STAT_DEV; - buf->ino = statbuf->st_ino; - buf->mask |= BFS_STAT_INO; + dest->ino = src->st_ino; + dest->mask |= BFS_STAT_INO; - buf->mode = statbuf->st_mode; - buf->mask |= BFS_STAT_TYPE | BFS_STAT_MODE; + dest->mode = src->st_mode; + dest->mask |= BFS_STAT_TYPE | BFS_STAT_MODE; - buf->nlink = statbuf->st_nlink; - buf->mask |= BFS_STAT_NLINK; + dest->nlink = src->st_nlink; + dest->mask |= BFS_STAT_NLINK; - buf->gid = statbuf->st_gid; - buf->mask |= BFS_STAT_GID; + dest->gid = src->st_gid; + dest->mask |= BFS_STAT_GID; - buf->uid = statbuf->st_uid; - buf->mask |= BFS_STAT_UID; + dest->uid = src->st_uid; + dest->mask |= BFS_STAT_UID; - buf->size = statbuf->st_size; - buf->mask |= BFS_STAT_SIZE; + dest->size = src->st_size; + dest->mask |= BFS_STAT_SIZE; - buf->blocks = statbuf->st_blocks; - buf->mask |= BFS_STAT_BLOCKS; + dest->blocks = src->st_blocks; + dest->mask |= BFS_STAT_BLOCKS; - buf->rdev = statbuf->st_rdev; - buf->mask |= BFS_STAT_RDEV; + dest->rdev = src->st_rdev; + dest->mask |= BFS_STAT_RDEV; #if BSD - buf->attrs = statbuf->st_flags; - buf->mask |= BFS_STAT_ATTRS; + dest->attrs = src->st_flags; + dest->mask |= BFS_STAT_ATTRS; #endif - buf->atime = statbuf->st_atim; - buf->mask |= BFS_STAT_ATIME; + dest->atime = src->st_atim; + dest->mask |= BFS_STAT_ATIME; - buf->ctime = statbuf->st_ctim; - buf->mask |= BFS_STAT_CTIME; + dest->ctime = src->st_ctim; + dest->mask |= BFS_STAT_CTIME; - buf->mtime = statbuf->st_mtim; - buf->mask |= BFS_STAT_MTIME; + dest->mtime = src->st_mtim; + dest->mask |= BFS_STAT_MTIME; #if __APPLE__ || __FreeBSD__ || __NetBSD__ - buf->btime = statbuf->st_birthtim; - buf->mask |= BFS_STAT_BTIME; + dest->btime = src->st_birthtim; + dest->mask |= BFS_STAT_BTIME; #endif } @@ -125,7 +114,7 @@ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bf struct stat statbuf; int ret = fstatat(at_fd, at_path, &statbuf, at_flags); if (ret == 0) { - bfs_stat_convert(&statbuf, buf); + bfs_stat_convert(buf, &statbuf); } return ret; } @@ -150,82 +139,86 @@ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int return ret; } -/** - * bfs_stat() implementation backed by statx(). - */ -static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) { - unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; - struct statx xbuf; - int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); - if (ret != 0) { - return ret; - } - +int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src) { // Callers shouldn't have to check anything except the times const unsigned int guaranteed = STATX_BASIC_STATS & ~(STATX_ATIME | STATX_CTIME | STATX_MTIME); - if ((xbuf.stx_mask & guaranteed) != guaranteed) { + if ((src->stx_mask & guaranteed) != guaranteed) { errno = ENOTSUP; return -1; } - buf->mask = 0; + dest->mask = 0; - buf->dev = xmakedev(xbuf.stx_dev_major, xbuf.stx_dev_minor); - buf->mask |= BFS_STAT_DEV; + dest->dev = xmakedev(src->stx_dev_major, src->stx_dev_minor); + dest->mask |= BFS_STAT_DEV; - buf->ino = xbuf.stx_ino; - buf->mask |= BFS_STAT_INO; + dest->ino = src->stx_ino; + dest->mask |= BFS_STAT_INO; - buf->mode = xbuf.stx_mode; - buf->mask |= BFS_STAT_TYPE; - buf->mask |= BFS_STAT_MODE; + dest->mode = src->stx_mode; + dest->mask |= BFS_STAT_TYPE; + dest->mask |= BFS_STAT_MODE; - buf->nlink = xbuf.stx_nlink; - buf->mask |= BFS_STAT_NLINK; + dest->nlink = src->stx_nlink; + dest->mask |= BFS_STAT_NLINK; - buf->gid = xbuf.stx_gid; - buf->mask |= BFS_STAT_GID; + dest->gid = src->stx_gid; + dest->mask |= BFS_STAT_GID; - buf->uid = xbuf.stx_uid; - buf->mask |= BFS_STAT_UID; + dest->uid = src->stx_uid; + dest->mask |= BFS_STAT_UID; - buf->size = xbuf.stx_size; - buf->mask |= BFS_STAT_SIZE; + dest->size = src->stx_size; + dest->mask |= BFS_STAT_SIZE; - buf->blocks = xbuf.stx_blocks; - buf->mask |= BFS_STAT_BLOCKS; + dest->blocks = src->stx_blocks; + dest->mask |= BFS_STAT_BLOCKS; - buf->rdev = xmakedev(xbuf.stx_rdev_major, xbuf.stx_rdev_minor); - buf->mask |= BFS_STAT_RDEV; + dest->rdev = xmakedev(src->stx_rdev_major, src->stx_rdev_minor); + dest->mask |= BFS_STAT_RDEV; - buf->attrs = xbuf.stx_attributes; - buf->mask |= BFS_STAT_ATTRS; + dest->attrs = src->stx_attributes; + dest->mask |= BFS_STAT_ATTRS; - if (xbuf.stx_mask & STATX_ATIME) { - buf->atime.tv_sec = xbuf.stx_atime.tv_sec; - buf->atime.tv_nsec = xbuf.stx_atime.tv_nsec; - buf->mask |= BFS_STAT_ATIME; + if (src->stx_mask & STATX_ATIME) { + dest->atime.tv_sec = src->stx_atime.tv_sec; + dest->atime.tv_nsec = src->stx_atime.tv_nsec; + dest->mask |= BFS_STAT_ATIME; } - if (xbuf.stx_mask & STATX_BTIME) { - buf->btime.tv_sec = xbuf.stx_btime.tv_sec; - buf->btime.tv_nsec = xbuf.stx_btime.tv_nsec; - buf->mask |= BFS_STAT_BTIME; + if (src->stx_mask & STATX_BTIME) { + dest->btime.tv_sec = src->stx_btime.tv_sec; + dest->btime.tv_nsec = src->stx_btime.tv_nsec; + dest->mask |= BFS_STAT_BTIME; } - if (xbuf.stx_mask & STATX_CTIME) { - buf->ctime.tv_sec = xbuf.stx_ctime.tv_sec; - buf->ctime.tv_nsec = xbuf.stx_ctime.tv_nsec; - buf->mask |= BFS_STAT_CTIME; + if (src->stx_mask & STATX_CTIME) { + dest->ctime.tv_sec = src->stx_ctime.tv_sec; + dest->ctime.tv_nsec = src->stx_ctime.tv_nsec; + dest->mask |= BFS_STAT_CTIME; } - if (xbuf.stx_mask & STATX_MTIME) { - buf->mtime.tv_sec = xbuf.stx_mtime.tv_sec; - buf->mtime.tv_nsec = xbuf.stx_mtime.tv_nsec; - buf->mask |= BFS_STAT_MTIME; + if (src->stx_mask & STATX_MTIME) { + dest->mtime.tv_sec = src->stx_mtime.tv_sec; + dest->mtime.tv_nsec = src->stx_mtime.tv_nsec; + dest->mask |= BFS_STAT_MTIME; } - return ret; + return 0; +} + +/** + * bfs_stat() implementation backed by statx(). + */ +static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) { + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; + struct statx xbuf; + int ret = bfs_statx(at_fd, at_path, at_flags, mask, &xbuf); + if (ret != 0) { + return ret; + } + + return bfs_statx_convert(buf, &xbuf); } #endif // BFS_USE_STATX @@ -306,7 +299,7 @@ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct b struct stat statbuf; if (fstat(at_fd, &statbuf) == 0) { - bfs_stat_convert(&statbuf, buf); + bfs_stat_convert(buf, &statbuf); return 0; } else { return -1; diff --git a/src/stat.h b/src/stat.h index e08dd4d..68a5c13 100644 --- a/src/stat.h +++ b/src/stat.h @@ -13,13 +13,34 @@ #define BFS_STAT_H #include "config.h" +#include #include #include +#if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30) +# define BFS_HAS_LIBC_STATX true +#elif __linux__ +# include +#endif + +#ifndef BFS_USE_STATX +# ifdef STATX_BASIC_STATS +# define BFS_USE_STATX true +# endif +#endif + #if BFS_USE_SYS_PARAM_H # include #endif +#ifdef DEV_BSIZE +# define BFS_STAT_BLKSIZE DEV_BSIZE +#elif defined(S_BLKSIZE) +# define BFS_STAT_BLKSIZE S_BLKSIZE +#else +# define BFS_STAT_BLKSIZE 512 +#endif + /** * bfs_stat field bitmask. */ @@ -60,14 +81,6 @@ enum bfs_stat_flags { BFS_STAT_NOSYNC = 1 << 2, }; -#ifdef DEV_BSIZE -# define BFS_STAT_BLKSIZE DEV_BSIZE -#elif defined(S_BLKSIZE) -# define BFS_STAT_BLKSIZE S_BLKSIZE -#else -# define BFS_STAT_BLKSIZE 512 -#endif - /** * Facade over struct stat. */ @@ -124,6 +137,18 @@ struct bfs_stat { */ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct bfs_stat *buf); +/** + * Convert struct stat to struct bfs_stat. + */ +void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src); + +#if BFS_USE_STATX +/** + * Convert struct statx to struct bfs_stat. + */ +int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src); +#endif + /** * Get a particular time field from a bfs_stat() buffer. */ -- cgit v1.2.3 From cb45babbeff337df9e0d96a251db2b9b325f9d18 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 Jan 2024 12:32:38 -0500 Subject: stat: Expose bfs_{fstatat,statx}_flags() --- src/stat.c | 58 +++++++++++++++++++++++++++++++++++++--------------------- src/stat.h | 10 ++++++++++ 2 files changed, 47 insertions(+), 21 deletions(-) diff --git a/src/stat.c b/src/stat.c index 8da1466..4e7e7e2 100644 --- a/src/stat.c +++ b/src/stat.c @@ -57,6 +57,20 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { return "???"; } +int bfs_fstatat_flags(enum bfs_stat_flags flags) { + int ret = 0; + + if (flags & BFS_STAT_NOFOLLOW) { + ret |= AT_SYMLINK_NOFOLLOW; + } + +#if defined(AT_NO_AUTOMOUNT) && (!__GNU__ || __GLIBC_PREREQ(2, 35)) + ret |= AT_NO_AUTOMOUNT; +#endif + + return ret; +} + void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { dest->mask = 0; @@ -139,6 +153,16 @@ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int return ret; } +int bfs_statx_flags(enum bfs_stat_flags flags) { + int ret = bfs_fstatat_flags(flags); + + if (flags & BFS_STAT_NOSYNC) { + ret |= AT_STATX_DONT_SYNC; + } + + return ret; +} + int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src) { // Callers shouldn't have to check anything except the times const unsigned int guaranteed = STATX_BASIC_STATS & ~(STATX_ATIME | STATX_CTIME | STATX_MTIME); @@ -226,12 +250,12 @@ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, struct b /** * Calls the stat() implementation with explicit flags. */ -static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x_flags, struct bfs_stat *buf) { +static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, struct bfs_stat *buf) { #if BFS_USE_STATX static atomic bool has_statx = true; if (load(&has_statx, relaxed)) { - int ret = bfs_statx_impl(at_fd, at_path, at_flags | x_flags, buf); + int ret = bfs_statx_impl(at_fd, at_path, at_flags, buf); // EPERM is commonly returned in a seccomp() sandbox that does // not allow statx() if (ret != 0 && (errno == ENOSYS || errno == EPERM)) { @@ -240,6 +264,8 @@ static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x return ret; } } + + at_flags &= ~AT_STATX_DONT_SYNC; #endif return bfs_stat_impl(at_fd, at_path, at_flags, buf); @@ -248,39 +274,29 @@ static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, int x /** * Implements the BFS_STAT_TRYFOLLOW retry logic. */ -static int bfs_stat_tryfollow(int at_fd, const char *at_path, int at_flags, int x_flags, enum bfs_stat_flags bfs_flags, struct bfs_stat *buf) { - int ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf); +static int bfs_stat_tryfollow(int at_fd, const char *at_path, int at_flags, enum bfs_stat_flags bfs_flags, struct bfs_stat *buf) { + int ret = bfs_stat_explicit(at_fd, at_path, at_flags, buf); if (ret != 0 && (bfs_flags & (BFS_STAT_NOFOLLOW | BFS_STAT_TRYFOLLOW)) == BFS_STAT_TRYFOLLOW && errno_is_like(ENOENT)) { at_flags |= AT_SYMLINK_NOFOLLOW; - ret = bfs_stat_explicit(at_fd, at_path, at_flags, x_flags, buf); + ret = bfs_stat_explicit(at_fd, at_path, at_flags, buf); } return ret; } int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct bfs_stat *buf) { - int at_flags = 0; - if (flags & BFS_STAT_NOFOLLOW) { - at_flags |= AT_SYMLINK_NOFOLLOW; - } - -#if defined(AT_NO_AUTOMOUNT) && (!__GNU__ || __GLIBC_PREREQ(2, 35)) - at_flags |= AT_NO_AUTOMOUNT; -#endif - - int x_flags = 0; -#ifdef AT_STATX_DONT_SYNC - if (flags & BFS_STAT_NOSYNC) { - x_flags |= AT_STATX_DONT_SYNC; - } +#if BFS_USE_STATX + int at_flags = bfs_statx_flags(flags); +#else + int at_flags = bfs_fstatat_flags(flags); #endif if (at_path) { - return bfs_stat_tryfollow(at_fd, at_path, at_flags, x_flags, flags, buf); + return bfs_stat_tryfollow(at_fd, at_path, at_flags, flags, buf); } // Check __GNU__ to work around https://lists.gnu.org/archive/html/bug-hurd/2021-12/msg00001.html @@ -288,7 +304,7 @@ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct b static atomic bool has_at_ep = true; if (load(&has_at_ep, relaxed)) { at_flags |= AT_EMPTY_PATH; - int ret = bfs_stat_explicit(at_fd, "", at_flags, x_flags, buf); + int ret = bfs_stat_explicit(at_fd, "", at_flags, buf); if (ret != 0 && errno == EINVAL) { store(&has_at_ep, false, relaxed); } else { diff --git a/src/stat.h b/src/stat.h index 68a5c13..17b7fe1 100644 --- a/src/stat.h +++ b/src/stat.h @@ -137,12 +137,22 @@ struct bfs_stat { */ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct bfs_stat *buf); +/** + * Convert bfs_stat_flags to fstatat() flags. + */ +int bfs_fstatat_flags(enum bfs_stat_flags flags); + /** * Convert struct stat to struct bfs_stat. */ void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src); #if BFS_USE_STATX +/** + * Convert bfs_stat_flags to statx() flags. + */ +int bfs_statx_flags(enum bfs_stat_flags flags); + /** * Convert struct statx to struct bfs_stat. */ -- cgit v1.2.3 From 58de21264be085b96c52d48db2cddd49ce40bde3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Jan 2024 12:11:18 -0500 Subject: ioq: Refactor to take advantage of -Wswitch --- src/ioq.c | 162 +++++++++++++++++++++++++++++++++++--------------------------- 1 file changed, 91 insertions(+), 71 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 89ebb3e..3d32dfe 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -460,34 +460,39 @@ static bool ioq_check_cancel(struct ioq *ioq, struct ioq_ent *ent) { return true; } -/** Handle a single request synchronously. */ -static void ioq_handle(struct ioq *ioq, struct ioq_ent *ent) { +/** Dispatch a single request synchronously. */ +static void ioq_dispatch_sync(struct ioq *ioq, struct ioq_ent *ent) { switch (ent->op) { - case IOQ_CLOSE: - ent->result = try(xclose(ent->close.fd)); - break; + case IOQ_CLOSE: + ent->result = try(xclose(ent->close.fd)); + return; - case IOQ_OPENDIR: - ent->result = try(bfs_opendir(ent->opendir.dir, ent->opendir.dfd, ent->opendir.path, ent->opendir.flags)); - if (ent->result >= 0) { - bfs_polldir(ent->opendir.dir); + case IOQ_OPENDIR: { + struct ioq_opendir *args = &ent->opendir; + ent->result = try(bfs_opendir(args->dir, args->dfd, args->path, args->flags)); + if (ent->result >= 0) { + bfs_polldir(args->dir); + } + return; } - break; - case IOQ_CLOSEDIR: - ent->result = try(bfs_closedir(ent->closedir.dir)); - break; - - default: - bfs_bug("Unknown ioq_op %d", (int)ent->op); - ent->result = -ENOSYS; - break; + case IOQ_CLOSEDIR: + ent->result = try(bfs_closedir(ent->closedir.dir)); + return; } + bfs_bug("Unknown ioq_op %d", (int)ent->op); + ent->result = -ENOSYS; +} + +/** Complete a single request synchronously. */ +static void ioq_complete(struct ioq *ioq, struct ioq_ent *ent) { + ioq_dispatch_sync(ioq, ent); ioqq_push(ioq->ready, ent); } #if BFS_USE_LIBURING + /** io_uring worker state. */ struct ioq_ring_state { /** The I/O queue. */ @@ -520,35 +525,54 @@ static struct ioq_ent *ioq_ring_pop(struct ioq_ring_state *state) { return ret; } -/** Prep a single SQE. */ -static void ioq_prep_sqe(struct io_uring_sqe *sqe, struct ioq_ent *ent) { - switch (ent->op) { - case IOQ_CLOSE: - io_uring_prep_close(sqe, ent->close.fd); - break; +/** Dispatch a single request asynchronously. */ +static struct io_uring_sqe *ioq_dispatch_async(struct io_uring *ring, struct ioq_ent *ent) { + struct io_uring_sqe *sqe = NULL; - case IOQ_OPENDIR: - io_uring_prep_openat(sqe, ent->opendir.dfd, ent->opendir.path, O_RDONLY | O_CLOEXEC | O_DIRECTORY, 0); - break; + switch (ent->op) { + case IOQ_CLOSE: + sqe = io_uring_get_sqe(ring); + io_uring_prep_close(sqe, ent->close.fd); + return sqe; + + case IOQ_OPENDIR: { + sqe = io_uring_get_sqe(ring); + struct ioq_opendir *args = &ent->opendir; + int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; + io_uring_prep_openat(sqe, args->dfd, args->path, flags, 0); + return sqe; + } + case IOQ_CLOSEDIR: #if BFS_USE_UNWRAPDIR - case IOQ_CLOSEDIR: - io_uring_prep_close(sqe, bfs_unwrapdir(ent->closedir.dir)); - break; + sqe = io_uring_get_sqe(ring); + io_uring_prep_close(sqe, bfs_unwrapdir(ent->closedir.dir)); #endif + return sqe; + } - default: - bfs_bug("Unknown ioq_op %d", (int)ent->op); - io_uring_prep_nop(sqe); - break; + bfs_bug("Unknown ioq_op %d", (int)ent->op); + return NULL; +} + +/** Prep a single SQE. */ +static void ioq_prep_sqe(struct ioq_ring_state *state, struct ioq_ent *ent) { + struct ioq *ioq = state->ioq; + if (ioq_check_cancel(ioq, ent)) { + return; } - io_uring_sqe_set_data(sqe, ent); + struct io_uring_sqe *sqe = ioq_dispatch_async(state->ring, ent); + if (sqe) { + io_uring_sqe_set_data(sqe, ent); + ++state->prepped; + } else { + ioq_complete(ioq, ent); + } } /** Prep a batch of SQEs. */ static bool ioq_ring_prep(struct ioq_ring_state *state) { - struct ioq *ioq = state->ioq; struct io_uring *ring = state->ring; while (io_uring_sq_space_left(ring)) { @@ -557,28 +581,42 @@ static bool ioq_ring_prep(struct ioq_ring_state *state) { break; } + ioq_prep_sqe(state, ent); + } + + return state->prepped || state->submitted; +} + +/** Reap a single CQE. */ +static void ioq_reap_cqe(struct ioq_ring_state *state, struct io_uring_cqe *cqe) { + struct ioq *ioq = state->ioq; + struct io_uring *ring = state->ring; + + struct ioq_ent *ent = io_uring_cqe_get_data(cqe); + ent->result = cqe->res; + io_uring_cqe_seen(ring, cqe); + --state->submitted; + + if (ent->op == IOQ_OPENDIR && ent->result >= 0) { + int fd = ent->result; if (ioq_check_cancel(ioq, ent)) { - continue; + xclose(fd); + return; } -#if !BFS_USE_UNWRAPDIR - if (ent->op == IOQ_CLOSEDIR) { - ioq_handle(ioq, ent); - continue; + struct ioq_opendir *args = &ent->opendir; + ent->result = try(bfs_opendir(args->dir, fd, NULL, args->flags)); + if (ent->result >= 0) { + // TODO: io_uring_prep_getdents() + bfs_polldir(args->dir); } -#endif - - struct io_uring_sqe *sqe = io_uring_get_sqe(ring); - ioq_prep_sqe(sqe, ent); - ++state->prepped; } - return state->prepped || state->submitted; + ioqq_push(ioq->ready, ent); } -/** Reap a batch of SQEs. */ +/** Reap a batch of CQEs. */ static void ioq_ring_reap(struct ioq_ring_state *state) { - struct ioq *ioq = state->ioq; struct io_uring *ring = state->ring; while (state->prepped) { @@ -595,26 +633,7 @@ static void ioq_ring_reap(struct ioq_ring_state *state) { continue; } - struct ioq_ent *ent = io_uring_cqe_get_data(cqe); - ent->result = cqe->res; - io_uring_cqe_seen(ring, cqe); - --state->submitted; - - if (ent->op == IOQ_OPENDIR && ent->result >= 0) { - int fd = ent->result; - if (ioq_check_cancel(ioq, ent)) { - xclose(fd); - continue; - } - - ent->result = try(bfs_opendir(ent->opendir.dir, fd, NULL, ent->opendir.flags)); - if (ent->result >= 0) { - // TODO: io_uring_prep_getdents() - bfs_polldir(ent->opendir.dir); - } - } - - ioqq_push(ioq->ready, ent); + ioq_reap_cqe(state, cqe); } } @@ -629,7 +648,8 @@ static void ioq_ring_work(struct ioq_thread *thread) { ioq_ring_reap(&state); } } -#endif + +#endif // BFS_USE_LIBURING /** Synchronous syscall loop. */ static void ioq_sync_work(struct ioq_thread *thread) { @@ -642,7 +662,7 @@ static void ioq_sync_work(struct ioq_thread *thread) { } if (!ioq_check_cancel(ioq, ent)) { - ioq_handle(ioq, ent); + ioq_complete(ioq, ent); } } } -- cgit v1.2.3 From 18597572f62ab18824df6bab6c30d2b645921969 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 23 Aug 2023 13:10:10 -0400 Subject: ioq: Implement ioq_stat() --- src/bftw.c | 3 ++ src/ioq.c | 100 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------- src/ioq.h | 31 +++++++++++++++++++ 3 files changed, 124 insertions(+), 10 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 952b090..02dd051 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -631,6 +631,9 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { SLIST_APPEND(&state->to_read, file, to_read); } break; + + case IOQ_STAT: + break; } ioq_free(ioq, ent); diff --git a/src/ioq.c b/src/ioq.c index 3d32dfe..3172f0a 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -131,6 +131,7 @@ #include "diag.h" #include "dir.h" #include "sanity.h" +#include "stat.h" #include "thread.h" #include #include @@ -432,6 +433,10 @@ struct ioq { /** ioq_ent arena. */ struct arena ents; +#if BFS_USE_LIBURING && BFS_USE_STATX + /** struct statx arena. */ + struct arena xbufs; +#endif /** Pending I/O requests. */ struct ioqq *pending; @@ -479,6 +484,12 @@ static void ioq_dispatch_sync(struct ioq *ioq, struct ioq_ent *ent) { case IOQ_CLOSEDIR: ent->result = try(bfs_closedir(ent->closedir.dir)); return; + + case IOQ_STAT: { + struct ioq_stat *args = &ent->stat; + ent->result = try(bfs_stat(args->dfd, args->path, args->flags, args->buf)); + return; + } } bfs_bug("Unknown ioq_op %d", (int)ent->op); @@ -549,6 +560,17 @@ static struct io_uring_sqe *ioq_dispatch_async(struct io_uring *ring, struct ioq io_uring_prep_close(sqe, bfs_unwrapdir(ent->closedir.dir)); #endif return sqe; + + case IOQ_STAT: { +#if BFS_USE_STATX + sqe = io_uring_get_sqe(ring); + struct ioq_stat *args = &ent->stat; + int flags = bfs_statx_flags(args->flags); + unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; + io_uring_prep_statx(sqe, args->dfd, args->path, flags, mask, args->xbuf); +#endif + return sqe; + } } bfs_bug("Unknown ioq_op %d", (int)ent->op); @@ -597,21 +619,41 @@ static void ioq_reap_cqe(struct ioq_ring_state *state, struct io_uring_cqe *cqe) io_uring_cqe_seen(ring, cqe); --state->submitted; - if (ent->op == IOQ_OPENDIR && ent->result >= 0) { - int fd = ent->result; - if (ioq_check_cancel(ioq, ent)) { - xclose(fd); - return; + if (ent->result < 0) { + goto push; + } + + switch (ent->op) { + case IOQ_OPENDIR: { + int fd = ent->result; + if (ioq_check_cancel(ioq, ent)) { + xclose(fd); + return; + } + + struct ioq_opendir *args = &ent->opendir; + ent->result = try(bfs_opendir(args->dir, fd, NULL, args->flags)); + if (ent->result >= 0) { + // TODO: io_uring_prep_getdents() + bfs_polldir(args->dir); + } + + break; } - struct ioq_opendir *args = &ent->opendir; - ent->result = try(bfs_opendir(args->dir, fd, NULL, args->flags)); - if (ent->result >= 0) { - // TODO: io_uring_prep_getdents() - bfs_polldir(args->dir); +#if BFS_USE_STATX + case IOQ_STAT: { + struct ioq_stat *args = &ent->stat; + ent->result = try(bfs_statx_convert(args->buf, args->xbuf)); + break; } +#endif + + default: + break; } +push: ioqq_push(ioq->ready, ent); } @@ -689,8 +731,13 @@ 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 + ARENA_INIT(&ioq->xbufs, struct statx); +#endif + ioq->pending = ioqq_create(depth); if (!ioq->pending) { goto fail; @@ -807,6 +854,30 @@ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr) { return 0; } +int ioq_stat(struct ioq *ioq, int dfd, const char *path, enum bfs_stat_flags flags, struct bfs_stat *buf, void *ptr) { + struct ioq_ent *ent = ioq_request(ioq, IOQ_STAT, ptr); + if (!ent) { + return -1; + } + + struct ioq_stat *args = &ent->stat; + args->dfd = dfd; + args->path = path; + args->flags = flags; + args->buf = buf; + +#if BFS_USE_LIBURING && BFS_USE_STATX + args->xbuf = arena_alloc(&ioq->xbufs); + if (!args->xbuf) { + ioq_free(ioq, ent); + return -1; + } +#endif + + ioqq_push(ioq->pending, ent); + return 0; +} + struct ioq_ent *ioq_pop(struct ioq *ioq, bool block) { if (ioq->size == 0) { return NULL; @@ -819,6 +890,12 @@ 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 (ent->op == IOQ_STAT) { + arena_free(&ioq->xbufs, ent->stat.xbuf); + } +#endif + arena_free(&ioq->ents, ent); } @@ -848,6 +925,9 @@ void ioq_destroy(struct ioq *ioq) { ioqq_destroy(ioq->ready); ioqq_destroy(ioq->pending); +#if BFS_USE_LIBURING && BFS_USE_STATX + arena_destroy(&ioq->xbufs); +#endif arena_destroy(&ioq->ents); free(ioq); diff --git a/src/ioq.h b/src/ioq.h index e1e5052..77aabaa 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -10,6 +10,7 @@ #include "config.h" #include "dir.h" +#include "stat.h" #include /** @@ -27,6 +28,8 @@ enum ioq_op { IOQ_OPENDIR, /** ioq_closedir(). */ IOQ_CLOSEDIR, + /** ioq_stat(). */ + IOQ_STAT, }; /** @@ -59,6 +62,14 @@ struct ioq_ent { struct ioq_closedir { struct bfs_dir *dir; } closedir; + /** ioq_stat() args. */ + struct ioq_stat { + int dfd; + const char *path; + enum bfs_stat_flags flags; + struct bfs_stat *buf; + void *xbuf; + } stat; }; }; @@ -127,6 +138,26 @@ int ioq_opendir(struct ioq *ioq, struct bfs_dir *dir, int dfd, const char *path, */ int ioq_closedir(struct ioq *ioq, struct bfs_dir *dir, void *ptr); +/** + * Asynchronous bfs_stat(). + * + * @param ioq + * The I/O queue. + * @param dfd + * The base file descriptor. + * @param path + * The path to stat, relative to dfd. + * @param flags + * Flags that affect the lookup. + * @param buf + * A place to store the stat buffer, if successful. + * @param ptr + * An arbitrary pointer to associate with the request. + * @return + * 0 on success, or -1 on failure. + */ +int ioq_stat(struct ioq *ioq, int dfd, const char *path, enum bfs_stat_flags flags, struct bfs_stat *buf, void *ptr); + /** * Pop a response from the queue. * -- cgit v1.2.3 From 5a6cec3dff25ae3ea84a632c3b84adf68c24fce4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Jan 2024 15:00:16 -0500 Subject: eval: Squelch an uninitialized variable warning --- src/eval.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/eval.c b/src/eval.c index 1a814b3..6dc73d8 100644 --- a/src/eval.c +++ b/src/eval.c @@ -21,6 +21,7 @@ #include "mtab.h" #include "printf.h" #include "pwcache.h" +#include "sanity.h" #include "stat.h" #include "trie.h" #include "xregex.h" @@ -1084,7 +1085,8 @@ bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) { * Evaluate the comma operator. */ bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) { - bool ret; + bool ret = uninit(ret, false); + for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { ret = eval_expr(child, state); if (state->quit) { -- cgit v1.2.3 From 6f0091981e38210e36ee830694f61cb695f2b99b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 30 Jan 2024 12:49:28 -0500 Subject: list: Return the next cursor from SLIST_INSERT() --- src/list.h | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/list.h b/src/list.h index 02a7ba2..61d0e5b 100644 --- a/src/list.h +++ b/src/list.h @@ -275,6 +275,8 @@ * The item to insert. * @param node (optional) * If specified, use item->node.next rather than item->next. + * @return + * A cursor for the next item. */ #define SLIST_INSERT(list, cursor, ...) \ SLIST_INSERT_(list, cursor, __VA_ARGS__, ) @@ -282,11 +284,12 @@ #define SLIST_INSERT_(list, cursor, item, ...) \ SLIST_INSERT__((list), (cursor), (item), LIST_NEXT_(__VA_ARGS__)) -#define SLIST_INSERT__(list, cursor, item, next) LIST_VOID_( \ - bfs_assert(!SLIST_ATTACHED__(list, item, next)), \ - item->next = *cursor, \ - *cursor = item, \ - list->tail = item->next ? list->tail : &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, \ + &item->next) /** * Add an item to the tail of a singly-linked list. @@ -302,7 +305,7 @@ SLIST_APPEND_(list, __VA_ARGS__, ) #define SLIST_APPEND_(list, item, ...) \ - SLIST_INSERT_(list, (list)->tail, item, __VA_ARGS__) + LIST_VOID_(SLIST_INSERT_(list, (list)->tail, item, __VA_ARGS__)) /** * Add an item to the head of a singly-linked list. @@ -318,7 +321,7 @@ SLIST_PREPEND_(list, __VA_ARGS__, ) #define SLIST_PREPEND_(list, item, ...) \ - SLIST_INSERT_(list, &(list)->head, item, __VA_ARGS__) + LIST_VOID_(SLIST_INSERT_(list, &(list)->head, item, __VA_ARGS__)) /** * Add an entire singly-linked list to the tail of another. -- cgit v1.2.3 From 43ec427535e2d21a3d8ec36d98889f1bc0515938 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 26 Jan 2024 13:32:26 -0500 Subject: bftw: New bftw_queue abstraction --- src/bftw.c | 366 ++++++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 292 insertions(+), 74 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 02dd051..ce08dba 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -9,6 +9,8 @@ * * - struct bftw_list: A linked list of bftw_file's. * + * - struct bftw_queue: A multi-stage queue of bftw_file's. + * * - struct bftw_cache: An LRU list of bftw_file's with open file descriptors, * used for openat() to minimize the amount of path re-traversals. * @@ -125,7 +127,7 @@ struct bftw_file { /** The next file to open/close/visit. */ struct bftw_file *next; /** The next directory to read. */ - struct { struct bftw_file *next; } to_read; + struct { struct bftw_file *next; } ready; /** LRU list node. */ struct { @@ -170,6 +172,254 @@ struct bftw_list { struct bftw_file **tail; }; +/** + * bftw_queue flags. + */ +enum bftw_qflags { + /** Track the sync/async service balance. */ + BFTW_QBALANCE = 1 << 0, + /** Buffer files before adding them to the queue. */ + BFTW_QBUFFER = 1 << 1, + /** Use LIFO (stack/DFS) ordering. */ + BFTW_QLIFO = 1 << 2, + /** Maintain a strict order. */ + BFTW_QORDER = 1 << 3, +}; + +/** + * A queue of bftw_file's that may be serviced asynchronously. + * + * A bftw_queue comprises three linked lists each tracking different stages. + * When BFTW_QBUFFER is set, files are initially pushed to the buffer: + * + * ╔═══╗ ╔═══╦═══╗ + * buffer: ║ 𝘩 ║ ║ 𝘩 ║ 𝘪 ║ + * ╠═══╬═══╦═══╗ ╠═══╬═══╬═══╗ + * waiting: ║ e ║ f ║ g ║ → ║ e ║ f ║ g ║ + * ╠═══╬═══╬═══╬═══╗ ╠═══╬═══╬═══╬═══╗ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ + * ╚═══╩═══╩═══╩═══╝ ╚═══╩═══╩═══╩═══╝ + * + * When bftw_queue_flush() is called, the files in the buffer are appended to + * the waiting list (or prepended, if BFTW_QLIFO is set): + * + * ╔═╗ + * buffer: ║ ║ + * ╠═╩═╦═══╦═══╦═══╦═══╗ + * waiting: ║ e ║ f ║ g ║ h ║ i ║ + * ╠═══╬═══╬═══╬═══╬═══╝ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ + * ╚═══╩═══╩═══╩═══╝ + * + * Using the buffer gives a more natural ordering for BFTW_QLIFO, and allows + * files to be sorted before adding them to the waiting list. If BFTW_QBUFFER + * is not set, files are pushed directly to the waiting list instead. + * + * Files on the waiting list are waiting to be "serviced" asynchronously by the + * ioq (for example, by an ioq_opendir() or ioq_stat() call). While they are + * being serviced, they are detached from the queue by bftw_queue_detach() and + * are not tracked by the queue at all: + * + * ╔═╗ + * buffer: ║ ║ + * ╠═╩═╦═══╦═══╗ ⎛ ┌───┬───┐ ⎞ + * waiting: ║ g ║ h ║ i ║ ⎜ ioq: │ 𝓮 │ 𝓯 │ ⎟ + * ╠═══╬═══╬═══╬═══╗ ⎝ └───┴───┘ ⎠ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ + * ╚═══╩═══╩═══╩═══╝ + * + * When their async service is complete, files are reattached to the queue by + * bftw_queue_attach(), this time on the ready list: + * + * ╔═╗ + * buffer: ║ ║ + * ╠═╩═╦═══╦═══╗ ⎛ ┌───┐ ⎞ + * waiting: ║ g ║ h ║ i ║ ⎜ ioq: │ 𝓮 │ ⎟ + * ╠═══╬═══╬═══╬═══╦═══╗ ⎝ └───┘ ⎠ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ 𝕗 ║ + * ╚═══╩═══╩═══╩═══╩═══╝ + * + * Files are added to the ready list in the order they are finished by the ioq. + * bftw_queue_pop() pops a file from the ready list if possible. Otherwise, it + * pops from the waiting list, and the file must be serviced synchronously. + * + * However, if BFTW_QORDER is set, files must be popped in the exact order they + * are added to the waiting list (to maintain sorted order). In this case, + * files are added to the waiting and ready lists at the same time. The + * file->ioqueued flag is set while it is in-service, so that bftw() can wait + * for it to be truly ready before using it. + * + * ╔═╗ + * buffer: ║ ║ + * ╠═╩═╦═══╦═══╗ ⎛ ┌───┐ ⎞ + * waiting: ║ g ║ h ║ i ║ ⎜ ioq: │ 𝓮 │ ⎟ + * ╠═══╬═══╬═══╬═══╦═══╦═══╦═══╦═══╦═══╗ ⎝ └───┘ ⎠ + * ready: ║ 𝕒 ║ 𝕓 ║ 𝕔 ║ 𝕕 ║ 𝓮 ║ 𝕗 ║ g ║ h ║ i ║ + * ╚═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╩═══╝ + * + * If BFTW_QBALANCE is set, queue->imbalance tracks the delta between async + * service (negative) and synchronous service (positive). The queue is + * considered "balanced" when this number is non-negative. Only a balanced + * queue will perform any async service, ensuring work is fairly distributed + * between the main thread and the ioq. + * + * BFTW_QBALANCE is only set for single-threaded ioqs. When an ioq has multiple + * threads, it is faster to wait for the ioq to complete an operation than it is + * to perform it on the main thread. + */ +struct bftw_queue { + /** Queue flags. */ + enum bftw_qflags flags; + /** A buffer of files to be enqueued together. */ + struct bftw_list buffer; + /** A list of files which are waiting to be serviced. */ + struct bftw_list waiting; + /** A list of already-serviced files. */ + struct bftw_list ready; + /** Tracks the imbalance between synchronous and async service. */ + unsigned long imbalance; +}; + +/** Initialize a queue. */ +static void bftw_queue_init(struct bftw_queue *queue, enum bftw_qflags flags) { + queue->flags = flags; + SLIST_INIT(&queue->buffer); + SLIST_INIT(&queue->waiting); + SLIST_INIT(&queue->ready); + queue->imbalance = 0; +} + +/** Add a file to the queue. */ +static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { + if (queue->flags & BFTW_QBUFFER) { + SLIST_APPEND(&queue->buffer, file); + } else if (queue->flags & BFTW_QLIFO) { + SLIST_PREPEND(&queue->waiting, file); + if (queue->flags & BFTW_QORDER) { + SLIST_PREPEND(&queue->ready, file, ready); + } + } else { + SLIST_APPEND(&queue->waiting, file); + if (queue->flags & BFTW_QORDER) { + SLIST_APPEND(&queue->ready, file, ready); + } + } +} + +/** Add any buffered files to the queue. */ +static void bftw_queue_flush(struct bftw_queue *queue) { + if (!(queue->flags & BFTW_QBUFFER)) { + return; + } + + if (queue->flags & BFTW_QORDER) { + // When sorting, add files to the ready list at the same time + // (and in the same order) as they are added to the waiting list + struct bftw_file **cursor = (queue->flags & BFTW_QLIFO) + ? &queue->ready.head + : queue->ready.tail; + for_slist (struct bftw_file, file, &queue->buffer) { + cursor = SLIST_INSERT(&queue->ready, cursor, file, ready); + } + } + + if (queue->flags & BFTW_QLIFO) { + SLIST_EXTEND(&queue->buffer, &queue->waiting); + } + + SLIST_EXTEND(&queue->waiting, &queue->buffer); +} + +/** Update the queue imbalance. */ +static void bftw_queue_balance(struct bftw_queue *queue, long delta) { + queue->imbalance += delta; +} + +/** Check if the queue is properly balanced for async work. */ +static bool bftw_queue_balanced(const struct bftw_queue *queue) { + if (queue->flags & BFTW_QBALANCE) { + return (long)queue->imbalance >= 0; + } else { + return true; + } +} + +/** Detatch the next waiting file to service it asynchronously. */ +static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file) { + if (file == SLIST_HEAD(&queue->buffer)) { + // To maintain order, we can't detach any files until they're + // added to the waiting/ready lists + bfs_assert(!(queue->flags & BFTW_QORDER)); + SLIST_POP(&queue->buffer); + } else if (file == SLIST_HEAD(&queue->waiting)) { + SLIST_POP(&queue->waiting); + } else { + bfs_bug("Detached file was not buffered or waiting"); + } + + file->ioqueued = true; + bftw_queue_balance(queue, -1); +} + +/** Reattach a serviced file to the queue. */ +static void bftw_queue_attach(struct bftw_queue *queue, struct bftw_file *file) { + file->ioqueued = false; + + if (!(queue->flags & BFTW_QORDER)) { + SLIST_APPEND(&queue->ready, file, ready); + } +} + +/** Get the next waiting file. */ +static struct bftw_file *bftw_queue_waiting(const struct bftw_queue *queue) { + if (!(queue->flags & BFTW_QBUFFER)) { + return SLIST_HEAD(&queue->waiting); + } + + if (queue->flags & BFTW_QORDER) { + // Don't detach files until they're on the waiting/ready lists + return SLIST_HEAD(&queue->waiting); + } + + const struct bftw_list *prefix = &queue->waiting; + const struct bftw_list *suffix = &queue->buffer; + if (queue->flags & BFTW_QLIFO) { + prefix = &queue->buffer; + suffix = &queue->waiting; + } + + struct bftw_file *file = SLIST_HEAD(prefix); + if (!file) { + file = SLIST_HEAD(suffix); + } + return file; +} + +/** Get the next ready file. */ +static struct bftw_file *bftw_queue_ready(const struct bftw_queue *queue) { + return SLIST_HEAD(&queue->ready); +} + +/** Pop a file from the queue. */ +static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { + // Don't pop until we've had a chance to sort the buffer + bfs_assert(SLIST_EMPTY(&queue->buffer)); + + struct bftw_file *file = SLIST_POP(&queue->ready, ready); + + if (!file || file == SLIST_HEAD(&queue->waiting)) { + // If no files are ready, try the waiting list. Or, if + // BFTW_QORDER is set, we may need to pop from both lists. + file = SLIST_POP(&queue->waiting); + if (file) { + // This file will be serviced synchronously + bftw_queue_balance(queue, +1); + } + } + + return file; +} + /** * A cache of open directories. */ @@ -354,7 +604,7 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil } SLIST_ITEM_INIT(file); - SLIST_ITEM_INIT(file, to_read); + SLIST_ITEM_INIT(file, ready); LIST_ITEM_INIT(file, lru); file->refcount = 1; @@ -430,17 +680,11 @@ struct bftw_state { struct ioq *ioq; /** The number of I/O threads. */ size_t nthreads; - /** Tracks the imbalance between main thread and background I/O. */ - long imbalance; - - /** A batch of directories to open. */ - struct bftw_list dir_batch; - /** The queue of directories to open. */ - struct bftw_list to_open; - /** The queue of directories to read. */ - struct bftw_list to_read; + /** The queue of unpinned directories to unwrap. */ struct bftw_list to_close; + /** The queue of directories to open/read. */ + struct bftw_queue dirq; /** A batch of files to enqueue. */ struct bftw_list file_batch; @@ -543,13 +787,22 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg state->dir_flags |= BFS_DIR_WHITEOUTS; } - state->imbalance = 0; - - SLIST_INIT(&state->dir_batch); - SLIST_INIT(&state->to_open); - SLIST_INIT(&state->to_read); SLIST_INIT(&state->to_close); + enum bftw_qflags qflags = 0; + if (state->strategy != BFTW_BFS && !(state->flags & BFTW_BUFFER)) { + // For a depth-first, unbuffered search, queue directories in + // LIFO order + qflags |= BFTW_QBUFFER | BFTW_QLIFO; + } + if (state->flags & BFTW_SORT) { + qflags |= BFTW_QORDER; + } + if (nthreads == 1) { + qflags |= BFTW_QBALANCE; + } + bftw_queue_init(&state->dirq, qflags); + SLIST_INIT(&state->file_batch); SLIST_INIT(&state->to_visit); @@ -575,12 +828,6 @@ static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, boo } } -/** Adjust the I/O queue balance. */ -static void bftw_ioq_balance(struct bftw_state *state, long delta) { - // Avoid signed overflow - state->imbalance = (unsigned long)state->imbalance + (unsigned long)delta; -} - /** Pop a response from the I/O queue. */ static int bftw_ioq_pop(struct bftw_state *state, bool block) { struct ioq *ioq = state->ioq; @@ -612,7 +859,6 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { case IOQ_OPENDIR: file = ent->ptr; - file->ioqueued = false; ++cache->capacity; parent = file->parent; @@ -627,9 +873,7 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { bftw_freedir(cache, dir); } - if (!(state->flags & BFTW_SORT)) { - SLIST_APPEND(&state->to_read, file, to_read); - } + bftw_queue_attach(&state->dirq, file); break; case IOQ_STAT: @@ -647,19 +891,14 @@ static int bftw_ioq_reserve(struct bftw_state *state) { return -1; } - // With only one background thread, we should balance I/O between it and - // the main thread. With more than one background thread, it's faster - // to wait on background I/O than it is to do it on the main thread. - bool balance = state->nthreads <= 1; - if (balance && state->imbalance < 0) { - return -1; - } - if (ioq_capacity(ioq) > 0) { return 0; } - if (bftw_ioq_pop(state, !balance) < 0) { + // With more than one background thread, it's faster to wait on + // background I/O than it is to do it on the main thread + bool block = state->nthreads > 1; + if (bftw_ioq_pop(state, block) < 0) { return -1; } @@ -892,9 +1131,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { goto free; } - file->ioqueued = true; --cache->capacity; - bftw_ioq_balance(state, -1); return 0; free: @@ -908,39 +1145,26 @@ fail: } /** Open a batch of directories asynchronously. */ -static void bftw_ioq_opendirs(struct bftw_state *state, struct bftw_list *queue) { - for_slist (struct bftw_file, dir, queue) { - if (bftw_ioq_opendir(state, dir) != 0) { +static void bftw_ioq_opendirs(struct bftw_state *state) { + while (bftw_queue_balanced(&state->dirq)) { + struct bftw_file *dir = bftw_queue_waiting(&state->dirq); + if (!dir) { + break; + } + + if (bftw_ioq_opendir(state, dir) == 0) { + bftw_queue_detach(&state->dirq, dir); + } else { break; } - SLIST_POP(queue); } } /** Push a directory onto the queue. */ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bfs_assert(file->type == BFS_DIR); - - struct bftw_list *queue; - if (state->strategy == BFTW_BFS || (state->flags & BFTW_BUFFER)) { - // In breadth-first mode, or if we're already buffering files, - // we can push directly to the to_open queue - queue = &state->to_open; - } else { - // For a depth-first, unbuffered search, add directories to a - // batch, then push the patch to the front of the queue - queue = &state->dir_batch; - } - - SLIST_APPEND(queue, file); - - if (state->flags & BFTW_SORT) { - // When sorting, directories are kept in order on the to_read - // list; otherwise, they are only added once they are open - SLIST_APPEND(&state->to_read, file, to_read); - } - - bftw_ioq_opendirs(state, queue); + bftw_queue_push(&state->dirq, file); + bftw_ioq_opendirs(state); } /** Pop a directory to read from the queue. */ @@ -956,9 +1180,9 @@ static bool bftw_pop_dir(struct bftw_state *state) { return false; } } else { - while (SLIST_EMPTY(&state->to_read)) { + while (!bftw_queue_ready(&state->dirq)) { // Block if we have no other files/dirs to visit, or no room in the cache - bool have_dirs = !SLIST_EMPTY(&state->to_open); + bool have_dirs = bftw_queue_waiting(&state->dirq); bool have_room = cache->capacity > 0 && cache->dirlimit > 0; bool block = !(have_dirs || have_files) || !have_room; @@ -968,10 +1192,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { } } - struct bftw_file *file = SLIST_POP(&state->to_read, to_read); - if (!file || file == state->to_open.head) { - file = SLIST_POP(&state->to_open); - } + struct bftw_file *file = bftw_queue_pop(&state->dirq); if (!file) { return false; } @@ -1051,8 +1272,6 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f return NULL; } - bftw_ioq_balance(state, +1); - if (bfs_opendir(dir, fd, NULL, state->dir_flags) != 0) { bftw_freedir(cache, dir); return NULL; @@ -1428,14 +1647,13 @@ static void bftw_batch_finish(struct bftw_state *state) { } if (state->strategy != BFTW_BFS) { - SLIST_EXTEND(&state->dir_batch, &state->to_open); SLIST_EXTEND(&state->file_batch, &state->to_visit); } - SLIST_EXTEND(&state->to_open, &state->dir_batch); SLIST_EXTEND(&state->to_visit, &state->file_batch); - bftw_ioq_opendirs(state, &state->to_open); + bftw_queue_flush(&state->dirq); + bftw_ioq_opendirs(state); } /** Close the current directory. */ @@ -1525,7 +1743,7 @@ static int bftw_state_destroy(struct bftw_state *state) { state->ioq = NULL; } - SLIST_EXTEND(&state->to_open, &state->dir_batch); + bftw_queue_flush(&state->dirq); SLIST_EXTEND(&state->to_visit, &state->file_batch); do { bftw_gc(state, BFTW_VISIT_NONE); -- cgit v1.2.3 From 62c998c159b4502c116a0dba505b3d775b451954 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 29 Jan 2024 19:21:56 -0500 Subject: bftw: Use a bftw_queue for files too --- src/bftw.c | 57 +++++++++++++++++++++++++++++++-------------------------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index ce08dba..400307b 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -400,6 +400,13 @@ static struct bftw_file *bftw_queue_ready(const struct bftw_queue *queue) { return SLIST_HEAD(&queue->ready); } +/** Check if a queue is empty. */ +static bool bftw_queue_empty(const struct bftw_queue *queue) { + return SLIST_EMPTY(&queue->buffer) + && SLIST_EMPTY(&queue->waiting) + && SLIST_EMPTY(&queue->ready); +} + /** Pop a file from the queue. */ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { // Don't pop until we've had a chance to sort the buffer @@ -683,14 +690,11 @@ struct bftw_state { /** The queue of unpinned directories to unwrap. */ struct bftw_list to_close; + /** The queue of files to visit. */ + struct bftw_queue fileq; /** The queue of directories to open/read. */ struct bftw_queue dirq; - /** A batch of files to enqueue. */ - struct bftw_list file_batch; - /** The queue of files to visit. */ - struct bftw_list to_visit; - /** The current path. */ dchar *path; /** The current file. */ @@ -790,21 +794,26 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg SLIST_INIT(&state->to_close); enum bftw_qflags qflags = 0; - if (state->strategy != BFTW_BFS && !(state->flags & BFTW_BUFFER)) { - // For a depth-first, unbuffered search, queue directories in - // LIFO order + if (state->strategy != BFTW_BFS) { qflags |= BFTW_QBUFFER | BFTW_QLIFO; } + if (state->flags & BFTW_BUFFER) { + qflags |= BFTW_QBUFFER; + } if (state->flags & BFTW_SORT) { qflags |= BFTW_QORDER; } if (nthreads == 1) { qflags |= BFTW_QBALANCE; } - bftw_queue_init(&state->dirq, qflags); + bftw_queue_init(&state->fileq, qflags); - SLIST_INIT(&state->file_batch); - SLIST_INIT(&state->to_visit); + if (state->strategy == BFTW_BFS || (state->flags & BFTW_BUFFER)) { + // In breadth-first mode, or if we're already buffering files, + // directories can be queued in FIFO order + qflags &= ~(BFTW_QBUFFER | BFTW_QLIFO); + } + bftw_queue_init(&state->dirq, qflags); state->path = NULL; state->file = NULL; @@ -1172,10 +1181,10 @@ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); struct bftw_cache *cache = &state->cache; - bool have_files = !SLIST_EMPTY(&state->to_visit); if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting + bool have_files = bftw_queue_ready(&state->fileq); if (state->strategy == BFTW_BFS && have_files) { return false; } @@ -1183,6 +1192,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { while (!bftw_queue_ready(&state->dirq)) { // Block if we have no other files/dirs to visit, or no room in the cache bool have_dirs = bftw_queue_waiting(&state->dirq); + bool have_files = !bftw_queue_empty(&state->fileq); bool have_room = cache->capacity > 0 && cache->dirlimit > 0; bool block = !(have_dirs || have_files) || !have_room; @@ -1208,7 +1218,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - state->file = SLIST_POP(&state->to_visit); + state->file = bftw_queue_pop(&state->fileq); return state->file; } @@ -1640,17 +1650,12 @@ static void bftw_list_sort(struct bftw_list *list) { SLIST_EXTEND(list, &right); } -/** Finish adding a batch of files. */ -static void bftw_batch_finish(struct bftw_state *state) { +/** Flush all the queue buffers. */ +static void bftw_flush(struct bftw_state *state) { if (state->flags & BFTW_SORT) { - bftw_list_sort(&state->file_batch); + bftw_list_sort(&state->fileq.buffer); } - - if (state->strategy != BFTW_BFS) { - SLIST_EXTEND(&state->file_batch, &state->to_visit); - } - - SLIST_EXTEND(&state->to_visit, &state->file_batch); + bftw_queue_flush(&state->fileq); bftw_queue_flush(&state->dirq); bftw_ioq_opendirs(state); @@ -1662,7 +1667,7 @@ static int bftw_closedir(struct bftw_state *state) { return -1; } - bftw_batch_finish(state); + bftw_flush(state); return 0; } @@ -1695,7 +1700,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - SLIST_APPEND(&state->file_batch, file); + bftw_queue_push(&state->fileq, file); return 0; } @@ -1744,7 +1749,7 @@ static int bftw_state_destroy(struct bftw_state *state) { } bftw_queue_flush(&state->dirq); - SLIST_EXTEND(&state->to_visit, &state->file_batch); + bftw_queue_flush(&state->fileq); do { bftw_gc(state, BFTW_VISIT_NONE); } while (bftw_pop_dir(state) || bftw_pop_file(state)); @@ -1766,7 +1771,7 @@ static int bftw_impl(struct bftw_state *state) { return -1; } } - bftw_batch_finish(state); + bftw_flush(state); while (true) { while (bftw_pop_dir(state)) { -- cgit v1.2.3 From 8ecd1dc4a60e063251963305b98888236b8d9825 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 30 Jan 2024 14:00:03 -0500 Subject: bftw: Optimize -s -j2 searches Maintaining balance and strict ordering at the same time forces too much work onto the main thread. --- src/bftw.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 400307b..ab930f1 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -802,8 +802,7 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg } if (state->flags & BFTW_SORT) { qflags |= BFTW_QORDER; - } - if (nthreads == 1) { + } else if (nthreads == 1) { qflags |= BFTW_QBALANCE; } bftw_queue_init(&state->fileq, qflags); -- cgit v1.2.3 From a726c7128ab5c6fffeb6844f3d819ada0369e6a8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 31 Jan 2024 19:51:51 -0500 Subject: opt: Charge eval_flags() for a stat() call --- src/opt.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/opt.c b/src/opt.c index 7203c61..28a2255 100644 --- a/src/opt.c +++ b/src/opt.c @@ -1218,6 +1218,7 @@ static struct bfs_expr *annotate_visit(struct bfs_opt *opt, struct bfs_expr *exp {eval_acl, STAT_COST}, {eval_capable, STAT_COST}, {eval_empty, 2 * STAT_COST}, // readdir() is worse than stat() + {eval_flags, STAT_COST}, {eval_fls, PRINT_COST}, {eval_fprint, PRINT_COST}, {eval_fprint0, PRINT_COST}, -- cgit v1.2.3 From 670ebd97fb431e830b1500b2e7e8013b121fb2c5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 31 Jan 2024 20:58:43 -0500 Subject: bftw: Actually stop if the callback returns BFTW_STOP Otherwise, bftw_ids() or bftw_eds() might keep going! Fixes: 5f16169 ("bftw: Share the bftw_state between iterations of ids/eds") --- src/bftw.c | 2 +- tests/bsd/s_quit.out | 1 + tests/bsd/s_quit.sh | 4 ++++ 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 tests/bsd/s_quit.out create mode 100644 tests/bsd/s_quit.sh diff --git a/src/bftw.c b/src/bftw.c index ab930f1..c9ba67e 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1791,7 +1791,7 @@ static int bftw_impl(struct bftw_state *state) { break; } if (bftw_visit(state, NULL) != 0) { - break; + return -1; } } diff --git a/tests/bsd/s_quit.out b/tests/bsd/s_quit.out new file mode 100644 index 0000000..5ea492b --- /dev/null +++ b/tests/bsd/s_quit.out @@ -0,0 +1 @@ +basic/j/foo diff --git a/tests/bsd/s_quit.sh b/tests/bsd/s_quit.sh new file mode 100644 index 0000000..6bd55ab --- /dev/null +++ b/tests/bsd/s_quit.sh @@ -0,0 +1,4 @@ +# Regression test: bfs -S ids -s -name foo -quit would not actually quit, +# ending up in a confused state and erroring/crashing + +bfs_diff -s basic -name foo -print -quit -- cgit v1.2.3 From b9bd5cbaf717f5613e9c093698c3af884586a975 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 12:37:50 -0500 Subject: bfstd: Don't shadow FreeBSD's fflags_t --- src/bfstd.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 0a9b87c..d19049f 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -639,12 +639,12 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * char *str_arg = (char *)*str; #if __OpenBSD__ - typedef uint32_t fflags_t; + typedef uint32_t bfs_fflags_t; #else - typedef unsigned long fflags_t; + typedef unsigned long bfs_fflags_t; #endif - fflags_t set_arg = 0; - fflags_t clear_arg = 0; + bfs_fflags_t set_arg = 0; + bfs_fflags_t clear_arg = 0; #if __NetBSD__ int ret = string_to_flags(&str_arg, &set_arg, &clear_arg); -- cgit v1.2.3 From bb060c513214aec07993946764180c17106b4344 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 09:28:36 -0500 Subject: bftw: Kill trivial bftw_queue_balance() helper --- src/bftw.c | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index c9ba67e..7b32db6 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -330,11 +330,6 @@ static void bftw_queue_flush(struct bftw_queue *queue) { SLIST_EXTEND(&queue->waiting, &queue->buffer); } -/** Update the queue imbalance. */ -static void bftw_queue_balance(struct bftw_queue *queue, long delta) { - queue->imbalance += delta; -} - /** Check if the queue is properly balanced for async work. */ static bool bftw_queue_balanced(const struct bftw_queue *queue) { if (queue->flags & BFTW_QBALANCE) { @@ -358,7 +353,7 @@ static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file) } file->ioqueued = true; - bftw_queue_balance(queue, -1); + --queue->imbalance; } /** Reattach a serviced file to the queue. */ @@ -420,7 +415,7 @@ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { file = SLIST_POP(&queue->waiting); if (file) { // This file will be serviced synchronously - bftw_queue_balance(queue, +1); + ++queue->imbalance; } } -- cgit v1.2.3 From 76ffc8d30cb1160d55d855d8ac630a2b9075fbcf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 09:38:11 -0500 Subject: bftw: Allow forcing bfs_dir allocation from the main thread When sorting, we can be forced to pop an unopened directory. If enough other directories are already open, that can lead to ENOMEM when we try to open it synchronously. To avoid this, force allocations from the main thread to be attempted even if they would go over the limit. Also, fix the accounting in bftw_allocdir() if allocation fails. --- src/bftw.c | 47 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 7b32db6..b3c0ba7 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -437,10 +437,13 @@ struct bftw_cache { /** bftw_file arena. */ struct varena files; + /** bfs_dir arena. */ struct arena dirs; /** Remaining bfs_dir capacity. */ - size_t dirlimit; + size_t dir_limit; + /** Excess force-allocated bfs_dirs. */ + size_t dir_excess; }; /** Initialize a cache. */ @@ -450,28 +453,48 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { cache->capacity = capacity; VARENA_INIT(&cache->files, struct bftw_file, name); + bfs_dir_arena(&cache->dirs); - cache->dirlimit = capacity - 1; - if (cache->dirlimit > 1024) { - cache->dirlimit = 1024; + cache->dir_limit = capacity - 1; + if (cache->dir_limit > 1024) { + cache->dir_limit = 1024; } + + cache->dir_excess = 0; } /** Allocate a directory. */ -static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache) { - if (cache->dirlimit == 0) { +static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache, bool force) { + size_t limit = cache->dir_limit; + size_t excess = cache->dir_excess; + + if (cache->dir_limit > 0) { + --cache->dir_limit; + } else if (force) { + ++cache->dir_excess; + } else { errno = ENOMEM; return NULL; } - --cache->dirlimit; - return arena_alloc(&cache->dirs); + struct bfs_dir *dir = arena_alloc(&cache->dirs); + if (!dir) { + cache->dir_limit = limit; + cache->dir_excess = excess; + } + + return dir; } /** Free a directory. */ static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) { - ++cache->dirlimit; + if (cache->dir_excess > 0) { + --cache->dir_excess; + } else { + ++cache->dir_limit; + } + arena_free(&cache->dirs, dir); } @@ -1125,7 +1148,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { goto unpin; } - struct bfs_dir *dir = bftw_allocdir(cache); + struct bfs_dir *dir = bftw_allocdir(cache, false); if (!dir) { goto unpin; } @@ -1187,7 +1210,7 @@ static bool bftw_pop_dir(struct bftw_state *state) { // Block if we have no other files/dirs to visit, or no room in the cache bool have_dirs = bftw_queue_waiting(&state->dirq); bool have_files = !bftw_queue_empty(&state->fileq); - bool have_room = cache->capacity > 0 && cache->dirlimit > 0; + bool have_room = cache->capacity > 0; bool block = !(have_dirs || have_files) || !have_room; if (bftw_ioq_pop(state, block) < 0) { @@ -1271,7 +1294,7 @@ static struct bfs_dir *bftw_file_opendir(struct bftw_state *state, struct bftw_f } struct bftw_cache *cache = &state->cache; - struct bfs_dir *dir = bftw_allocdir(cache); + struct bfs_dir *dir = bftw_allocdir(cache, true); if (!dir) { return NULL; } -- cgit v1.2.3 From 710c083ff02eb1cc5b8daa6778784f3d1cd3c08d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 09:46:04 -0500 Subject: bftw: Don't immediately pin open directories It is undesirable to close a directory that we haven't read yet to free up cache capacity, but it's worse to fail to open the next directory because too many upcoming directories are pinned. This could happen when sorting, because then we can't prioritize the already-opened ones. --- src/bftw.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index b3c0ba7..e29bd27 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -659,8 +659,6 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, file->fd = bfs_dirfd(dir); bftw_cache_add(cache, file); } - - bftw_cache_pin(cache, file); } /** Free a bftw_file. */ @@ -1318,7 +1316,7 @@ static int bftw_opendir(struct bftw_state *state) { struct bftw_file *file = state->file; state->dir = file->dir; if (state->dir) { - return 0; + goto pin; } if (bftw_build_path(state, NULL) != 0) { @@ -1328,8 +1326,11 @@ static int bftw_opendir(struct bftw_state *state) { state->dir = bftw_file_opendir(state, file, state->path); if (!state->dir) { state->direrror = errno; + return 0; } +pin: + bftw_cache_pin(&state->cache, file); return 0; } @@ -1577,7 +1578,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; struct bftw_file *file = state->file; - if (file && file->dir) { + if (file && state->dir) { bftw_unpin_dir(state, file, true); } state->dir = NULL; -- cgit v1.2.3 From 85e8344637ac4ac28e3638764f28d411781b8e96 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 09:54:47 -0500 Subject: bftw: Always block in bftw_pop_dir() with multiple threads --- src/bftw.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index e29bd27..debf92c 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1191,27 +1191,38 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bftw_ioq_opendirs(state); } +/** Check if we should block on the ioq when popping a directory. */ +static bool bftw_block_for_dir(const struct bftw_state *state) { + // Always block with more than one background thread + if (state->nthreads > 1) { + return true; + } + + // Block if the cache is full + if (state->cache.capacity == 0) { + return true; + } + + // Block if we have no other files/dirs to visit + if (!bftw_queue_waiting(&state->dirq) && bftw_queue_empty(&state->fileq)) { + return true; + } + + return false; +} + /** Pop a directory to read from the queue. */ static bool bftw_pop_dir(struct bftw_state *state) { bfs_assert(!state->file); - struct bftw_cache *cache = &state->cache; - if (state->flags & BFTW_SORT) { // Keep strict breadth-first order when sorting - bool have_files = bftw_queue_ready(&state->fileq); - if (state->strategy == BFTW_BFS && have_files) { + if (state->strategy == BFTW_BFS && bftw_queue_ready(&state->fileq)) { return false; } } else { while (!bftw_queue_ready(&state->dirq)) { - // Block if we have no other files/dirs to visit, or no room in the cache - bool have_dirs = bftw_queue_waiting(&state->dirq); - bool have_files = !bftw_queue_empty(&state->fileq); - bool have_room = cache->capacity > 0; - bool block = !(have_dirs || have_files) || !have_room; - - if (bftw_ioq_pop(state, block) < 0) { + if (bftw_ioq_pop(state, bftw_block_for_dir(state)) < 0) { break; } } -- cgit v1.2.3 From 31bede02d5dbbc8e9e60d3af9fc4a749ad89fa11 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 12:42:57 -0500 Subject: ci/freebsd: Use the system compiler Release builds work now on FreeBSD 14. --- .github/workflows/ci.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dbf77c9..72037e3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -79,7 +79,6 @@ jobs: bash \ expect \ gmake \ - llvm16 \ oniguruma \ sudo \ tcl-wrapper @@ -88,7 +87,7 @@ jobs: run: | chown -R action:action . - sudo -u action gmake -j$(nproc) distcheck CC=clang16 JOBS=-j$(nproc) + sudo -u action gmake -j$(nproc) distcheck JOBS=-j$(nproc) openbsd: name: OpenBSD -- cgit v1.2.3 From f8b22f20147d872aef9fa1037139c39e4dd0e687 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 16:03:05 -0500 Subject: tests: Use variable redirections to dup std{out,err} Previously, we hardcoded file descriptors 3 and 4 for duplicating stdandard output/error respectively. In preparation for keeping inherited FDs open, switch to using bash's variable redirection feature to dynamically assign FDs. This feature is only available from bash 4.1 onwards, so this marks the end of our support for bash 3. macOS users will need to install a modern bash version to run our tests. --- .github/workflows/ci.yml | 4 +++- tests/run.sh | 26 +++++++------------------- tests/util.sh | 17 ++++++++--------- 3 files changed, 18 insertions(+), 29 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 72037e3..ff0ff6f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -52,7 +52,9 @@ jobs: - name: Install dependencies run: | - brew install expect + brew install \ + bash \ + expect - name: Run tests run: | diff --git a/tests/run.sh b/tests/run.sh index 4a159f9..85d961f 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -28,7 +28,7 @@ debug_err() { callers | while read -r line func file; do if [ "$func" = source ]; then local cmd="$(awk "NR == $line" "$file" 2>/dev/null)" || : - debug "$file" $line "${RED}error $ret${RST}" "$cmd" >&4 + debug "$file" $line "${RED}error $ret${RST}" "$cmd" >&$DUPERR break fi done @@ -73,21 +73,9 @@ bg_test() { return $ret } -# Wait for any background job to complete -if ((BASH_VERSINFO[0] > 4 || (BASH_VERSINFO[0] == 4 && BASH_VERSINFO[1] >= 3))); then - wait_any() { - wait -n - } -else - wait_any() { - read -ra jobs < <(jobs -p) - wait ${jobs[0]} - } -fi - # Wait for a background test to finish wait_test() { - wait_any + wait -n ret=$? ((BG--)) @@ -197,7 +185,7 @@ skip() { caller | { read -r line file printf "${BOL}" - debug "$file" $line "" "$(awk "NR == $line" "$file")" >&3 + debug "$file" $line "" "$(awk "NR == $line" "$file")" >&$DUPOUT } fi @@ -252,8 +240,8 @@ bfs_verbose() { ( # Close some fds to make room for the pipe, # even with extremely low ulimit -n - exec >&- 4>&- - exec >&3 3>&- + exec >&- {DUPERR}>&- + exec >&$DUPOUT {DUPOUT}>&- color bfs_verbose_impl "$@" ) fi @@ -306,7 +294,7 @@ invoke_bfs() { local ret=0 # Close the logging fds - "${BFS[@]}" "$@" 3>&- 4>&- || ret=$? + "${BFS[@]}" "$@" {DUPOUT}>&- {DUPERR}>&- || ret=$? # Allow bfs to fail, but not crash if ((ret > 125)); then @@ -391,7 +379,7 @@ diff_output() { if ((UPDATE)); then cp "$OUT" "$GOLD" else - $DIFF -u "$GOLD" "$OUT" >&4 + $DIFF -u "$GOLD" "$OUT" >&$DUPERR fi } diff --git a/tests/util.sh b/tests/util.sh index 5bd3328..686cc77 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -68,7 +68,7 @@ stdenv() { # Close stdin so bfs doesn't think we're interactive # dup() the standard fds for logging even when redirected - exec &1 4>&2 + exec &1 {DUPERR}>&2 } # Drop root priviliges or bail @@ -159,19 +159,18 @@ defer() { # Pop a single command from the defer stack and run it pop_defer() { - local i=$((${#DEFER_CMDS[@]} - 1)) - local cmd="${DEFER_CMDS[$i]}" - local file="${DEFER_FILES[$i]}" - local line="${DEFER_LINES[$i]}" - unset "DEFER_CMDS[$i]" - unset "DEFER_FILES[$i]" - unset "DEFER_LINES[$i]" + local cmd="${DEFER_CMDS[-1]}" + local file="${DEFER_FILES[-1]}" + local line="${DEFER_LINES[-1]}" + unset "DEFER_CMDS[-1]" + unset "DEFER_FILES[-1]" + unset "DEFER_LINES[-1]" local ret=0 eval "$cmd" || ret=$? if ((ret != 0)); then - debug "$file" $line "${RED}error $ret${RST}" "defer $cmd" >&4 + debug "$file" $line "${RED}error $ret${RST}" "defer $cmd" >&$DUPERR fi return $ret -- cgit v1.2.3 From c4334184502c25a41b5cab060681229b2b570c0a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 16:14:19 -0500 Subject: tests: Don't clobber inherited FDs Rather than attempting to close any unexpected FDs, just count them and adjust our ulimit -n calls to account for them. --- .github/workflows/ci.yml | 1 + tests/bfs/deep_strict.sh | 2 +- tests/common/execdir_ulimit.sh | 2 +- tests/gnu/execdir_ulimit.out | 32 ++++++++++++++++---------------- tests/gnu/execdir_ulimit.sh | 2 +- tests/gnu/printf_u_g_ulimit.sh | 2 +- tests/posix/deep.sh | 2 +- tests/posix/exec_ulimit.out | 32 ++++++++++++++++---------------- tests/posix/exec_ulimit.sh | 2 +- tests/posix/nogroup_ulimit.sh | 2 +- tests/posix/nouser_ulimit.sh | 2 +- tests/util.sh | 17 +++++------------ 12 files changed, 46 insertions(+), 52 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ff0ff6f..8e96526 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -86,6 +86,7 @@ jobs: tcl-wrapper pw useradd -n action -m -G wheel -s /usr/local/bin/bash echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/local/etc/sudoers + mount -t fdescfs none /dev/fd run: | chown -R action:action . diff --git a/tests/bfs/deep_strict.sh b/tests/bfs/deep_strict.sh index 50c8f05..22453c0 100644 --- a/tests/bfs/deep_strict.sh +++ b/tests/bfs/deep_strict.sh @@ -1,3 +1,3 @@ # Not even enough fds to keep the root open -ulimit -n 7 +ulimit -n $((NOPENFD + 4)) bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash {} \; diff --git a/tests/common/execdir_ulimit.sh b/tests/common/execdir_ulimit.sh index 90c93c1..122c282 100644 --- a/tests/common/execdir_ulimit.sh +++ b/tests/common/execdir_ulimit.sh @@ -2,5 +2,5 @@ cd "$TEST" mkdir -p a/b/c/d/e/f/g/h/i/j/k/l/m/n/o/p/q/r/s/t/u/v/w/x/y/z mkdir -p a/b/c/d/e/f/g/h/i/j/k/l/m/0/1/2/3/4/5/6/7/8/9/A/B/C -ulimit -n 13 +ulimit -n $((NOPENFD + 10)) bfs_diff . -execdir echo {} \; diff --git a/tests/gnu/execdir_ulimit.out b/tests/gnu/execdir_ulimit.out index 7931f9a..6749f7d 100644 --- a/tests/gnu/execdir_ulimit.out +++ b/tests/gnu/execdir_ulimit.out @@ -1,16 +1,16 @@ -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 ./0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE diff --git a/tests/gnu/execdir_ulimit.sh b/tests/gnu/execdir_ulimit.sh index 3245dad..e14e716 100644 --- a/tests/gnu/execdir_ulimit.sh +++ b/tests/gnu/execdir_ulimit.sh @@ -1,2 +1,2 @@ -ulimit -Sn 16 +ulimit -Sn 64 bfs_diff deep -type f -execdir bash -c 'printf "%d %s\n" $(ulimit -Sn) "$1"' bash {} \; diff --git a/tests/gnu/printf_u_g_ulimit.sh b/tests/gnu/printf_u_g_ulimit.sh index 390ad48..c621b9b 100644 --- a/tests/gnu/printf_u_g_ulimit.sh +++ b/tests/gnu/printf_u_g_ulimit.sh @@ -1,2 +1,2 @@ -ulimit -n 16 +ulimit -n $((NOPENFD + 13)) [ "$(invoke_bfs deep -printf '%u %g\n' | uniq)" = "$(id -un) $(id -gn)" ] diff --git a/tests/posix/deep.sh b/tests/posix/deep.sh index 431705e..36a88c0 100644 --- a/tests/posix/deep.sh +++ b/tests/posix/deep.sh @@ -1,2 +1,2 @@ -ulimit -n 16 +ulimit -n $((NOPENFD + 13)) bfs_diff deep -type f -exec bash -c 'echo "${1:0:6}/.../${1##*/} (${#1})"' bash {} \; diff --git a/tests/posix/exec_ulimit.out b/tests/posix/exec_ulimit.out index 587ece2..144169e 100644 --- a/tests/posix/exec_ulimit.out +++ b/tests/posix/exec_ulimit.out @@ -1,16 +1,16 @@ -16 deep/0/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/1/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/2/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/3/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/4/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/5/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/6/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/7/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/8/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/9/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/A/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/B/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/C/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/D/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/E/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE -16 deep/F/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/0/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/1/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/2/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/3/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/4/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/5/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/6/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/7/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/8/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/9/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/A/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/B/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/C/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/D/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/E/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE +64 deep/F/.../0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDEF0123456789ABCDE diff --git a/tests/posix/exec_ulimit.sh b/tests/posix/exec_ulimit.sh index 8ac8ff1..655fbec 100644 --- a/tests/posix/exec_ulimit.sh +++ b/tests/posix/exec_ulimit.sh @@ -1,2 +1,2 @@ -ulimit -Sn 16 +ulimit -Sn 64 bfs_diff deep -type f -exec bash -c 'printf "%d %s\n" $(ulimit -Sn) "${1:0:6}/.../${1##*/}"' bash {} \; diff --git a/tests/posix/nogroup_ulimit.sh b/tests/posix/nogroup_ulimit.sh index 5b4c440..a39dd1f 100644 --- a/tests/posix/nogroup_ulimit.sh +++ b/tests/posix/nogroup_ulimit.sh @@ -1,2 +1,2 @@ -ulimit -n 16 +ulimit -n $((NOPENFD + 13)) bfs_diff deep -type f -nogroup diff --git a/tests/posix/nouser_ulimit.sh b/tests/posix/nouser_ulimit.sh index e1400f0..a94b8c5 100644 --- a/tests/posix/nouser_ulimit.sh +++ b/tests/posix/nouser_ulimit.sh @@ -1,2 +1,2 @@ -ulimit -n 16 +ulimit -n $((NOPENFD + 13)) bfs_diff deep -type f -nouser diff --git a/tests/util.sh b/tests/util.sh index 686cc77..ec24958 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -48,23 +48,16 @@ stdenv() { export MallocNanoZone=0 fi - # Close non-standard inherited fds + # Count the inherited FDs if [ -d /proc/self/fd ]; then local fds=/proc/self/fd else local fds=/dev/fd fi - - for fd in "$fds"/*; do - if [ ! -e "$fd" ]; then - continue - fi - - local fd="${fd##*/}" - if ((fd > 2)); then - eval "exec ${fd}<&-" - fi - done + # We use ls $fds on purpose, rather than e.g. ($fds/*), to avoid counting + # internal bash fds that are not exposed to spawned processes + NOPENFD=$(ls -1q "$fds/" 2>/dev/null | wc -l) + NOPENFD=$((NOPENFD > 3 ? NOPENFD - 1 : 3)) # Close stdin so bfs doesn't think we're interactive # dup() the standard fds for logging even when redirected -- cgit v1.2.3 From 4d0d84f935159c395ccf3b95d5727a3553003b68 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 12:11:41 -0500 Subject: tests: Implement jobserver inheritance --- .github/workflows/ci.yml | 12 ++--- GNUmakefile | 7 +-- tests/getopts.sh | 22 ++++++--- tests/run.sh | 119 +++++++++++++++++++++++++++++++++++++---------- tests/tests.mk | 7 +++ tests/util.sh | 12 +++-- 6 files changed, 132 insertions(+), 47 deletions(-) create mode 100644 tests/tests.mk diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e96526..5ca0a75 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -59,7 +59,7 @@ jobs: - name: Run tests run: | jobs=$(sysctl -n hw.ncpu) - make -j$jobs distcheck JOBS=-j$jobs + make -j$jobs distcheck freebsd: name: FreeBSD @@ -90,7 +90,7 @@ jobs: run: | chown -R action:action . - sudo -u action gmake -j$(nproc) distcheck JOBS=-j$(nproc) + sudo -u action gmake -j$(nproc) distcheck openbsd: name: OpenBSD @@ -120,7 +120,7 @@ jobs: run: | chown -R action:action . jobs=$(sysctl -n hw.ncpu) - doas -u action gmake -j$jobs check JOBS=-j$jobs TEST_FLAGS="--sudo=doas --verbose=skipped" + doas -u action gmake -j$jobs check TEST_FLAGS="--sudo=doas --verbose=skipped" netbsd: name: NetBSD @@ -153,7 +153,7 @@ jobs: PATH="/sbin:/usr/sbin:$PATH" chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action gmake -j$jobs check CC=clang LDFLAGS="-rpath /usr/pkg/lib" JOBS=-j$jobs TEST_FLAGS="--sudo --verbose=skipped" + sudo -u action gmake -j$jobs check CC=clang LDFLAGS="-rpath /usr/pkg/lib" TEST_FLAGS="--sudo --verbose=skipped" dragonflybsd: name: DragonFly BSD @@ -184,7 +184,7 @@ jobs: run: | chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action gmake -j$jobs check JOBS=-j$jobs TEST_FLAGS="--sudo --verbose=skipped" + sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" omnios: name: OmniOS @@ -216,4 +216,4 @@ jobs: PATH="/usr/xpg4/bin:$PATH" chown -R action:staff . jobs=$(getconf NPROCESSORS_ONLN) - sudo -u action gmake -j$jobs check LDFLAGS="-Wl,-rpath,/opt/ooce/lib/amd64" JOBS=-j$jobs TEST_FLAGS="--sudo --verbose=skipped" + sudo -u action gmake -j$jobs check LDFLAGS="-Wl,-rpath,/opt/ooce/lib/amd64" TEST_FLAGS="--sudo --verbose=skipped" diff --git a/GNUmakefile b/GNUmakefile index 34c7f37..e01e93a 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -304,13 +304,8 @@ check: $(CHECKS) check-units: $(BIN)/tests/units $< -JOBS := $(filter -j%,$(MAKEFLAGS)) -ifndef JOBS - JOBS := -j1 -endif - $(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(TEST_UTILS) - ./tests/tests.sh $(JOBS) --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) + +./tests/tests.sh --make="$(MAKE)" --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) # Custom test flags for distcheck DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" diff --git a/tests/getopts.sh b/tests/getopts.sh index ac75140..5214e9f 100644 --- a/tests/getopts.sh +++ b/tests/getopts.sh @@ -10,6 +10,7 @@ if command -v nproc &>/dev/null; then else JOBS=1 fi +MAKE= PATTERNS=() SUDO=() STOP=0 @@ -24,15 +25,19 @@ VERBOSE_TESTS=0 usage() { local pad=$(printf "%*s" ${#0} "") color cat </dev/null)" || : - debug "$file" $line "${RED}error $ret${RST}" "$cmd" >&$DUPERR + debug "$file" $line "${RED}error $ret${RST}" >&$DUPERR break fi done } -# Run a single test -run_test() ( +# Source a test +source_test() ( set -eE trap debug_err ERR + + if ((${#MAKE[@]})); then + # Close the jobserver pipes + exec {READY_PIPE}<&- {DONE_PIPE}>&- + fi + cd "$TMP" source "$@" ) -# Run a test in the background -bg_test() { +# Run a test +run_test() { if ((VERBOSE_ERRORS)); then - run_test "$1" + source_test "$1" else - run_test "$1" 2>"$TMP/$TEST.err" + source_test "$1" 2>"$TMP/$TEST.err" fi ret=$? + if ((${#MAKE[@]})); then + # Write one byte to the done pipe + printf . >&$DONE_PIPE + fi + case $ret in 0) if ((VERBOSE_TESTS)); then @@ -73,28 +83,87 @@ bg_test() { return $ret } -# Wait for a background test to finish -wait_test() { - wait -n - ret=$? +# Count the tests running in the background +BG=0 + +# Run a test in the background +bg_test() { + run_test "$1" & + ((++BG)) +} + +# Reap a finished background test +reap_test() { ((BG--)) - case $ret in + case "$1" in 0) ((++passed)) - return 0 ;; $EX_SKIP) ((++skipped)) - return 0 ;; *) ((++failed)) - return $ret ;; esac } +# Wait for a background test to finish +wait_test() { + local pid + wait -n -ppid + ret=$? + if [ -z "${pid:-}" ]; then + debug "${BASH_SOURCE[0]}" $((LINENO - 3)) "${RED}error $ret${RST}" >&$DUPERR + exit 1 + fi + + reap_test $ret +} + +# Wait until we're ready to run another test +wait_ready() { + if ((${#MAKE[@]})); then + # We'd like to parse the output of jobs -n, but we can't run it in a + # subshell or we won't get the right output + jobs -n >"$TMP/jobs" + while read -r job status ret foo; do + case "$status" in + Done) + reap_test 0 + ;; + Exit) + reap_test $ret + ;; + esac + done <"$TMP/jobs" + + # Read one byte from the ready pipe + read -r -N1 -u$READY_PIPE + elif ((BG >= JOBS)); then + wait_test + fi +} + +# Run make as a co-process to use its job control +comake() { + coproc { + # We can't just use std{in,out}, due to + # https://www.gnu.org/software/make/manual/html_node/Parallel-Input.html + exec {DONE_PIPE}<&0 {READY_PIPE}>&1 + exec "${MAKE[@]}" -s \ + -f "$TESTS/tests.mk" \ + DONE=$DONE_PIPE \ + READY=$READY_PIPE \ + "${TEST_CASES[@]/#/tests/}" \ + /dev/null + } + + # coproc pipes aren't inherited by subshells, so dup them + exec {READY_PIPE}<&${COPROC[0]} {DONE_PIPE}>&${COPROC[1]} +} + # Run all the tests run_tests() { if ((VERBOSE_TESTS)); then @@ -125,17 +194,17 @@ run_tests() { TEST_FMT="." fi - BG=0 + if ((${#MAKE[@]})); then + comake + fi # Turn off set -e (but turn it back on in run_test) set +e for TEST in "${TEST_CASES[@]}"; do - if ((BG >= JOBS)); then - wait_test - if (($? && STOP)); then - break - fi + wait_ready + if (($? && STOP)); then + break fi percent=$((100 * ran / total)) @@ -144,8 +213,8 @@ run_tests() { mkdir -p "$TMP/$TEST" OUT="$TMP/$TEST.out" - bg_test "$TESTS/$TEST.sh" & - ((++BG, ++ran)) + bg_test "$TESTS/$TEST.sh" + ((++ran)) done while ((BG > 0)); do @@ -185,7 +254,7 @@ skip() { caller | { read -r line file printf "${BOL}" - debug "$file" $line "" "$(awk "NR == $line" "$file")" >&$DUPOUT + debug "$file" $line "" >&$DUPOUT } fi diff --git a/tests/tests.mk b/tests/tests.mk new file mode 100644 index 0000000..5bf4f6c --- /dev/null +++ b/tests/tests.mk @@ -0,0 +1,7 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# GNU makefile that exposes make's job control to tests.sh + +tests/%: + bash -c 'printf . >&$(READY) && read -r -N1 -u$(DONE)' diff --git a/tests/util.sh b/tests/util.sh index ec24958..7dba9fb 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -113,9 +113,13 @@ callers() { # Print a message including path, line number, and command debug() { - local file="${1/#*\/tests\//tests/}" - set -- "$file" "${@:2}" - color printf "${BLD}%s:%d:${RST} %s\n %s\n" "$@" + local file="$1" + local line="$2" + local msg="$3" + local cmd="$(awk "NR == $line" "$file" 2>/dev/null)" || : + file="${file/#*\/tests\//tests/}" + + color printf "${BLD}%s:%d:${RST} %s\n %s\n" "$file" "$line" "$msg" "$cmd" } ## Deferred cleanup @@ -163,7 +167,7 @@ pop_defer() { eval "$cmd" || ret=$? if ((ret != 0)); then - debug "$file" $line "${RED}error $ret${RST}" "defer $cmd" >&$DUPERR + debug "$file" $line "${RED}error $ret${RST}" >&$DUPERR fi return $ret -- cgit v1.2.3 From f090d34d559824b8ce11b5f4c4c55ac1e82a7ef3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 1 Feb 2024 12:17:39 -0500 Subject: tests: Run more integration tests by default The traversal behaviour of -j1, -j2, and -jN (N >= 3) are all different enough to be worth running the whole test suite against them. Sorting (-s) is another dimension worth testing. Having these tests run automatically would have caught some recently-fixed bugs earlier. --- GNUmakefile | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index e01e93a..87a5753 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -291,12 +291,12 @@ $(BIN)/tests/units: \ tests: $(TESTS) .PHONY: tests -# The different search strategies that we test -STRATEGIES := bfs dfs ids eds -STRATEGY_CHECKS := $(STRATEGIES:%=check-%) +# The different flag combinations we check +INTEGRATIONS := default dfs ids eds j1 j2 j3 s +INTEGRATION_CHECKS := $(INTEGRATIONS:%=check-%) # All the different checks we run -CHECKS := check-units $(STRATEGY_CHECKS) +CHECKS := check-units $(INTEGRATION_CHECKS) check: $(CHECKS) .PHONY: check $(CHECKS) @@ -304,8 +304,14 @@ check: $(CHECKS) check-units: $(BIN)/tests/units $< -$(STRATEGY_CHECKS): check-%: $(BIN)/bfs $(TEST_UTILS) - +./tests/tests.sh --make="$(MAKE)" --bfs="$(BIN)/bfs -S $*" $(TEST_FLAGS) +check-default: $(BIN)/bfs $(TEST_UTILS) + +./tests/tests.sh --make="$(MAKE)" --bfs="$<" $(TEST_FLAGS) + +check-dfs check-ids check-eds: check-%: $(BIN)/bfs $(TEST_UTILS) + +./tests/tests.sh --make="$(MAKE)" --bfs="$< -S $*" $(TEST_FLAGS) + +check-j1 check-j2 check-j3 check-s: check-%: $(BIN)/bfs $(TEST_UTILS) + +./tests/tests.sh --make="$(MAKE)" --bfs="$< -$*" $(TEST_FLAGS) # Custom test flags for distcheck DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" -- cgit v1.2.3 From 8c7040e03a1cabf4b9dffad7c99633819e50ea5b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 3 Feb 2024 15:28:02 -0500 Subject: ci/macos: Try the new M1 runner --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5ca0a75..6276627 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,7 +45,7 @@ jobs: macos: name: macOS - runs-on: macos-13 + runs-on: macos-14 steps: - uses: actions/checkout@v4 -- cgit v1.2.3 From ef8e954d8b6910fc2ea08baa8dbe3fbfb6ad7c01 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 4 Feb 2024 11:46:31 -0500 Subject: build: Rename test targets --- GNUmakefile | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 87a5753..637a63e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -288,21 +288,18 @@ $(BIN)/tests/units: \ $(OBJ)/tests/xtime.o \ $(LIBBFS) +# Build all the test binaries tests: $(TESTS) .PHONY: tests +# Run the unit tests +unit-tests: $(BIN)/tests/units + $< +.PHONY: unit-tests + # The different flag combinations we check INTEGRATIONS := default dfs ids eds j1 j2 j3 s -INTEGRATION_CHECKS := $(INTEGRATIONS:%=check-%) - -# All the different checks we run -CHECKS := check-units $(INTEGRATION_CHECKS) - -check: $(CHECKS) -.PHONY: check $(CHECKS) - -check-units: $(BIN)/tests/units - $< +INTEGRATION_TESTS := $(INTEGRATIONS:%=check-%) check-default: $(BIN)/bfs $(TEST_UTILS) +./tests/tests.sh --make="$(MAKE)" --bfs="$<" $(TEST_FLAGS) @@ -313,6 +310,14 @@ check-dfs check-ids check-eds: check-%: $(BIN)/bfs $(TEST_UTILS) check-j1 check-j2 check-j3 check-s: check-%: $(BIN)/bfs $(TEST_UTILS) +./tests/tests.sh --make="$(MAKE)" --bfs="$< -$*" $(TEST_FLAGS) +# Run the integration tests +integration-tests: $(INTEGRATION_TESTS) +.PHONY: integration-tests + +# Run all the tests +check: unit-tests integration-tests +.PHONY: check + # Custom test flags for distcheck DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" -- cgit v1.2.3 From dab66643f54d0f79c212db9e68d8545b98a2d653 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Feb 2024 13:52:44 -0500 Subject: tests: Fix --stop --- tests/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run.sh b/tests/run.sh index 0b975b9..ccd2ac1 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -203,7 +203,7 @@ run_tests() { for TEST in "${TEST_CASES[@]}"; do wait_ready - if (($? && STOP)); then + if ((STOP && failed > 0)); then break fi -- cgit v1.2.3 From 10b6da04521cf3f65f3c47bece8e2e5e6e664d6d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Feb 2024 13:54:01 -0500 Subject: mtab: Take the basename directly in bfs_might_be_mount() This avoids some hot xbaseoff() calls. --- src/bftw.c | 2 +- src/mtab.c | 3 +-- src/mtab.h | 6 +++--- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index debf92c..d392aed 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -1390,7 +1390,7 @@ static bool bftw_need_stat(const struct bftw_state *state) { // need to stat() to get the correct type. We don't need to // check for directories because they can only be mounted over // by other directories. - if (bfs_might_be_mount(state->mtab, ftwbuf->path)) { + if (bfs_might_be_mount(state->mtab, ftwbuf->path + ftwbuf->nameoff)) { return true; } #endif diff --git a/src/mtab.c b/src/mtab.c index cc726a2..c76f403 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -271,8 +271,7 @@ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statb } } -bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *path) { - const char *name = path + xbaseoff(path); +bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *name) { return trie_find_str(&mtab->names, name); } diff --git a/src/mtab.h b/src/mtab.h index ca4372c..d99d78f 100644 --- a/src/mtab.h +++ b/src/mtab.h @@ -43,12 +43,12 @@ const char *bfs_fstype(const struct bfs_mtab *mtab, const struct bfs_stat *statb * * @param mtab * The current mount table. - * @param path - * The path to check. + * @param name + * The name of the file to check. * @return * Whether the named file could be a mount point. */ -bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *path); +bool bfs_might_be_mount(const struct bfs_mtab *mtab, const char *name); /** * Free a mount table. -- cgit v1.2.3 From 89ecb2a08467cd8aa6ba70f8519df494652cac96 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Feb 2024 14:02:55 -0500 Subject: bftw: stat() files asynchronously --- src/bftw.c | 677 +++++++++++++++++++++++++++++++++------------- src/bftw.h | 20 +- src/eval.c | 20 +- tests/bfs/execdir_plus.sh | 2 +- 4 files changed, 507 insertions(+), 212 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index d392aed..664651c 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -36,58 +36,130 @@ #include #include +/** Initialize a bftw_stat cache. */ +static void bftw_stat_init(struct bftw_stat *bufs, struct bfs_stat *stat_buf, struct bfs_stat *lstat_buf) { + bufs->stat_buf = stat_buf; + bufs->lstat_buf = lstat_buf; + bufs->stat_err = -1; + bufs->lstat_err = -1; +} + +/** Fill a bftw_stat cache from another one. */ +static void bftw_stat_fill(struct bftw_stat *dest, const struct bftw_stat *src) { + if (dest->stat_err < 0 && src->stat_err >= 0) { + dest->stat_buf = src->stat_buf; + dest->stat_err = src->stat_err; + } + + if (dest->lstat_err < 0 && src->lstat_err >= 0) { + dest->lstat_buf = src->lstat_buf; + dest->lstat_err = src->lstat_err; + } +} + +/** Cache a bfs_stat() result. */ +static void bftw_stat_cache(struct bftw_stat *bufs, enum bfs_stat_flags flags, const struct bfs_stat *buf, int err) { + if (flags & BFS_STAT_NOFOLLOW) { + bufs->lstat_buf = buf; + bufs->lstat_err = err; + if (err || !S_ISLNK(buf->mode)) { + // Non-link, so share stat info + bufs->stat_buf = buf; + bufs->stat_err = err; + } + } else if (flags & BFS_STAT_TRYFOLLOW) { + if (err) { + bufs->stat_err = err; + } else if (S_ISLNK(buf->mode)) { + bufs->lstat_buf = buf; + bufs->lstat_err = err; + bufs->stat_err = ENOENT; + } else { + bufs->stat_buf = buf; + bufs->stat_err = err; + } + } else { + bufs->stat_buf = buf; + bufs->stat_err = err; + } +} + /** Caching bfs_stat(). */ -static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, struct bftw_stat *cache, enum bfs_stat_flags flags) { - if (!cache->buf) { - if (cache->error) { - errno = cache->error; - } else if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, &cache->storage) == 0) { - cache->buf = &cache->storage; +static const struct bfs_stat *bftw_stat_impl(struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + struct bftw_stat *bufs = &ftwbuf->stat_bufs; + struct bfs_stat *buf; + + if (flags & BFS_STAT_NOFOLLOW) { + buf = (struct bfs_stat *)bufs->lstat_buf; + if (bufs->lstat_err == 0) { + return buf; + } else if (bufs->lstat_err > 0) { + errno = bufs->lstat_err; + return NULL; + } + } else { + buf = (struct bfs_stat *)bufs->stat_buf; + if (bufs->stat_err == 0) { + return buf; + } else if (bufs->stat_err > 0) { + errno = bufs->stat_err; + return NULL; + } + } + + struct bfs_stat *ret; + int err; + if (bfs_stat(ftwbuf->at_fd, ftwbuf->at_path, flags, buf) == 0) { + ret = buf; + err = 0; #ifdef S_IFWHT - } else if (errno == ENOENT && ftwbuf->type == BFS_WHT) { - // This matches the behavior of FTS_WHITEOUT on BSD - memset(&cache->storage, 0, sizeof(cache->storage)); - cache->storage.mode = S_IFWHT; - cache->buf = &cache->storage; + } else if (errno == ENOENT && ftwbuf->type == BFS_WHT) { + // This matches the behavior of FTS_WHITEOUT on BSD + ret = memset(buf, 0, sizeof(*buf)); + ret->mode = S_IFWHT; + err = 0; #endif - } else { - cache->error = errno; - } + } else { + ret = NULL; + err = errno; } - return cache->buf; + bftw_stat_cache(bufs, flags, ret, err); + return ret; } const struct bfs_stat *bftw_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { struct BFTW *mutbuf = (struct BFTW *)ftwbuf; const struct bfs_stat *ret; - if (flags & BFS_STAT_NOFOLLOW) { - ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); - if (ret && !S_ISLNK(ret->mode) && !mutbuf->stat_cache.buf) { - // Non-link, so share stat info - mutbuf->stat_cache.buf = ret; + if (flags & BFS_STAT_TRYFOLLOW) { + ret = bftw_stat_impl(mutbuf, BFS_STAT_FOLLOW); + if (!ret && errno_is_like(ENOENT)) { + ret = bftw_stat_impl(mutbuf, BFS_STAT_NOFOLLOW); } } else { - ret = bftw_stat_impl(mutbuf, &mutbuf->stat_cache, BFS_STAT_FOLLOW); - if (!ret && (flags & BFS_STAT_TRYFOLLOW) && errno_is_like(ENOENT)) { - ret = bftw_stat_impl(mutbuf, &mutbuf->lstat_cache, BFS_STAT_NOFOLLOW); - } + ret = bftw_stat_impl(mutbuf, flags); } return ret; } const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { + const struct bftw_stat *bufs = &ftwbuf->stat_bufs; + if (flags & BFS_STAT_NOFOLLOW) { - return ftwbuf->lstat_cache.buf; - } else if (ftwbuf->stat_cache.buf) { - return ftwbuf->stat_cache.buf; - } else if ((flags & BFS_STAT_TRYFOLLOW) && error_is_like(ftwbuf->stat_cache.error, ENOENT)) { - return ftwbuf->lstat_cache.buf; - } else { - return NULL; + if (bufs->lstat_err == 0) { + return bufs->lstat_buf; + } + } else if (bufs->stat_err == 0) { + return bufs->stat_buf; + } else if ((flags & BFS_STAT_TRYFOLLOW) && error_is_like(bufs->stat_err, ENOENT)) { + if (bufs->lstat_err == 0) { + return bufs->lstat_buf; + } } + + return NULL; } enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { @@ -156,6 +228,9 @@ struct bftw_file { /** The inode number, for cycle detection. */ ino_t ino; + /** Cached bfs_stat() info. */ + struct bftw_stat stat_bufs; + /** The offset of this file in the full path. */ size_t nameoff; /** The length of the file's name. */ @@ -276,6 +351,10 @@ struct bftw_queue { struct bftw_list waiting; /** A list of already-serviced files. */ struct bftw_list ready; + /** The current size of the queue. */ + size_t size; + /** The number of files currently in-service. */ + size_t ioqueued; /** Tracks the imbalance between synchronous and async service. */ unsigned long imbalance; }; @@ -286,6 +365,8 @@ static void bftw_queue_init(struct bftw_queue *queue, enum bftw_qflags flags) { SLIST_INIT(&queue->buffer); SLIST_INIT(&queue->waiting); SLIST_INIT(&queue->ready); + queue->size = 0; + queue->ioqueued = 0; queue->imbalance = 0; } @@ -304,6 +385,8 @@ static void bftw_queue_push(struct bftw_queue *queue, struct bftw_file *file) { SLIST_APPEND(&queue->ready, file, ready); } } + + ++queue->size; } /** Add any buffered files to the queue. */ @@ -339,8 +422,19 @@ static bool bftw_queue_balanced(const struct bftw_queue *queue) { } } -/** Detatch the next waiting file to service it asynchronously. */ -static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file) { +/** Update the queue balance for (a)sync service. */ +static void bftw_queue_rebalance(struct bftw_queue *queue, bool async) { + if (async) { + --queue->imbalance; + } else { + ++queue->imbalance; + } +} + +/** Detatch the next waiting file. */ +static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file, bool async) { + bfs_assert(!file->ioqueued); + if (file == SLIST_HEAD(&queue->buffer)) { // To maintain order, we can't detach any files until they're // added to the waiting/ready lists @@ -352,19 +446,34 @@ static void bftw_queue_detach(struct bftw_queue *queue, struct bftw_file *file) bfs_bug("Detached file was not buffered or waiting"); } - file->ioqueued = true; - --queue->imbalance; + if (async) { + file->ioqueued = true; + ++queue->ioqueued; + bftw_queue_rebalance(queue, true); + } } /** Reattach a serviced file to the queue. */ -static void bftw_queue_attach(struct bftw_queue *queue, struct bftw_file *file) { - file->ioqueued = false; +static void bftw_queue_attach(struct bftw_queue *queue, struct bftw_file *file, bool async) { + if (async) { + bfs_assert(file->ioqueued); + file->ioqueued = false; + --queue->ioqueued; + } else { + bfs_assert(!file->ioqueued); + } if (!(queue->flags & BFTW_QORDER)) { SLIST_APPEND(&queue->ready, file, ready); } } +/** Make a file ready immediately. */ +static void bftw_queue_skip(struct bftw_queue *queue, struct bftw_file *file) { + bftw_queue_detach(queue, file, false); + bftw_queue_attach(queue, file, false); +} + /** Get the next waiting file. */ static struct bftw_file *bftw_queue_waiting(const struct bftw_queue *queue) { if (!(queue->flags & BFTW_QBUFFER)) { @@ -395,13 +504,6 @@ static struct bftw_file *bftw_queue_ready(const struct bftw_queue *queue) { return SLIST_HEAD(&queue->ready); } -/** Check if a queue is empty. */ -static bool bftw_queue_empty(const struct bftw_queue *queue) { - return SLIST_EMPTY(&queue->buffer) - && SLIST_EMPTY(&queue->waiting) - && SLIST_EMPTY(&queue->ready); -} - /** Pop a file from the queue. */ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { // Don't pop until we've had a chance to sort the buffer @@ -413,10 +515,10 @@ static struct bftw_file *bftw_queue_pop(struct bftw_queue *queue) { // If no files are ready, try the waiting list. Or, if // BFTW_QORDER is set, we may need to pop from both lists. file = SLIST_POP(&queue->waiting); - if (file) { - // This file will be serviced synchronously - ++queue->imbalance; - } + } + + if (file) { + --queue->size; } return file; @@ -444,6 +546,9 @@ struct bftw_cache { size_t dir_limit; /** Excess force-allocated bfs_dirs. */ size_t dir_excess; + + /** bfs_stat arena. */ + struct arena stat_bufs; }; /** Initialize a cache. */ @@ -462,6 +567,8 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { } cache->dir_excess = 0; + + ARENA_INIT(&cache->stat_bufs, struct bfs_stat); } /** Allocate a directory. */ @@ -603,8 +710,9 @@ static void bftw_cache_destroy(struct bftw_cache *cache) { bfs_assert(LIST_EMPTY(cache)); bfs_assert(!cache->target); - varena_destroy(&cache->files); + arena_destroy(&cache->stat_bufs); arena_destroy(&cache->dirs); + varena_destroy(&cache->files); } /** Create a new bftw_file. */ @@ -642,6 +750,8 @@ static struct bftw_file *bftw_file_new(struct bftw_cache *cache, struct bftw_fil file->dev = -1; file->ino = -1; + bftw_stat_init(&file->stat_bufs, NULL, NULL); + file->namelen = namelen; memcpy(file->name, name, namelen + 1); @@ -661,6 +771,21 @@ static void bftw_file_set_dir(struct bftw_cache *cache, struct bftw_file *file, } } +/** Free a file's cached stat() buffers. */ +static void bftw_stat_recycle(struct bftw_cache *cache, struct bftw_file *file) { + struct bftw_stat *bufs = &file->stat_bufs; + + struct bfs_stat *stat_buf = (struct bfs_stat *)bufs->stat_buf; + struct bfs_stat *lstat_buf = (struct bfs_stat *)bufs->lstat_buf; + if (stat_buf) { + arena_free(&cache->stat_bufs, stat_buf); + } else if (lstat_buf) { + arena_free(&cache->stat_bufs, lstat_buf); + } + + bftw_stat_init(bufs, NULL, NULL); +} + /** Free a bftw_file. */ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bfs_assert(file->refcount == 0); @@ -669,6 +794,8 @@ static void bftw_file_free(struct bftw_cache *cache, struct bftw_file *file) { bftw_file_close(cache, file); } + bftw_stat_recycle(cache, file); + varena_free(&cache->files, file, file->namelen + 1); } @@ -729,6 +856,10 @@ struct bftw_state { /** Extra data about the current file. */ struct BFTW ftwbuf; + /** stat() buffer storage. */ + struct bfs_stat stat_buf; + /** lstat() buffer storage. */ + struct bfs_stat lstat_buf; }; /** Check if we have to buffer files before visiting them. */ @@ -755,6 +886,11 @@ static bool bftw_must_buffer(const struct bftw_state *state) { return true; } + if ((state->flags & BFTW_STAT) && state->nthreads > 1) { + // We will be buffering every file anyway for ioq_stat() + return true; + } + return false; } @@ -841,19 +977,32 @@ static int bftw_state_init(struct bftw_state *state, const struct bftw_args *arg return 0; } -/** Unpin a directory, and possibly queue it for unwrapping. */ -static void bftw_unpin_dir(struct bftw_state *state, struct bftw_file *file, bool force) { - bftw_cache_unpin(&state->cache, file); +/** Queue a directory for unwrapping. */ +static void bftw_delayed_unwrap(struct bftw_state *state, struct bftw_file *file) { + bfs_assert(file->dir); - if (file->dir && (force || file->pincount == 0)) { - if (!SLIST_ATTACHED(&state->to_close, file)) { - SLIST_APPEND(&state->to_close, file); - } + if (!SLIST_ATTACHED(&state->to_close, file, ready)) { + SLIST_APPEND(&state->to_close, file, ready); + } +} + +/** Unpin a file's parent. */ +static void bftw_unpin_parent(struct bftw_state *state, struct bftw_file *file, bool unwrap) { + struct bftw_file *parent = file->parent; + if (!parent) { + return; + } + + bftw_cache_unpin(&state->cache, parent); + + if (unwrap && parent->dir && parent->pincount == 0) { + bftw_delayed_unwrap(state, parent); } } /** Pop a response from the I/O queue. */ static int bftw_ioq_pop(struct bftw_state *state, bool block) { + struct bftw_cache *cache = &state->cache; struct ioq *ioq = state->ioq; if (!ioq) { return -1; @@ -864,10 +1013,10 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { return -1; } - struct bftw_cache *cache = &state->cache; - struct bftw_file *file; - struct bftw_file *parent; - struct bfs_dir *dir; + struct bftw_file *file = ent->ptr; + if (file) { + bftw_unpin_parent(state, file, true); + } enum ioq_op op = ent->op; switch (op) { @@ -877,30 +1026,30 @@ static int bftw_ioq_pop(struct bftw_state *state, bool block) { case IOQ_CLOSEDIR: ++cache->capacity; - dir = ent->closedir.dir; - bftw_freedir(cache, dir); + bftw_freedir(cache, ent->closedir.dir); break; case IOQ_OPENDIR: - file = ent->ptr; - ++cache->capacity; - parent = file->parent; - if (parent) { - bftw_unpin_dir(state, parent, false); - } - dir = ent->opendir.dir; if (ent->result >= 0) { - bftw_file_set_dir(cache, file, dir); + bftw_file_set_dir(cache, file, ent->opendir.dir); } else { - bftw_freedir(cache, dir); + bftw_freedir(cache, ent->opendir.dir); } - bftw_queue_attach(&state->dirq, file); + bftw_queue_attach(&state->dirq, file, true); break; case IOQ_STAT: + if (ent->result >= 0) { + bftw_stat_cache(&file->stat_bufs, ent->stat.flags, ent->stat.buf, 0); + } else { + arena_free(&cache->stat_bufs, ent->stat.buf); + bftw_stat_cache(&file->stat_bufs, ent->stat.flags, NULL, -ent->result); + } + + bftw_queue_attach(&state->fileq, file, true); break; } @@ -1125,21 +1274,34 @@ static int bftw_unwrapdir(struct bftw_state *state, struct bftw_file *file) { return bftw_ioq_closedir(state, dir); } +/** Try to pin a file's parent. */ +static int bftw_pin_parent(struct bftw_state *state, struct bftw_file *file) { + struct bftw_file *parent = file->parent; + if (!parent) { + return AT_FDCWD; + } + + int fd = parent->fd; + if (fd < 0) { + bfs_static_assert(AT_FDCWD != -1); + return -1; + } + + bftw_cache_pin(&state->cache, parent); + return fd; +} + /** Open a directory asynchronously. */ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { + struct bftw_cache *cache = &state->cache; + if (bftw_ioq_reserve(state) != 0) { goto fail; } - int dfd = AT_FDCWD; - struct bftw_cache *cache = &state->cache; - struct bftw_file *parent = file->parent; - if (parent) { - dfd = parent->fd; - if (dfd < 0) { - goto fail; - } - bftw_cache_pin(cache, parent); + int dfd = bftw_pin_parent(state, file); + if (dfd < 0 && dfd != AT_FDCWD) { + goto fail; } if (bftw_cache_reserve(state) != 0) { @@ -1161,9 +1323,7 @@ static int bftw_ioq_opendir(struct bftw_state *state, struct bftw_file *file) { free: bftw_freedir(cache, dir); unpin: - if (parent) { - bftw_cache_unpin(cache, parent); - } + bftw_unpin_parent(state, file, false); fail: return -1; } @@ -1177,7 +1337,7 @@ static void bftw_ioq_opendirs(struct bftw_state *state) { } if (bftw_ioq_opendir(state, dir) == 0) { - bftw_queue_detach(&state->dirq, dir); + bftw_queue_detach(&state->dirq, dir, true); } else { break; } @@ -1191,24 +1351,36 @@ static void bftw_push_dir(struct bftw_state *state, struct bftw_file *file) { bftw_ioq_opendirs(state); } -/** Check if we should block on the ioq when popping a directory. */ -static bool bftw_block_for_dir(const struct bftw_state *state) { - // Always block with more than one background thread - if (state->nthreads > 1) { - return true; +/** Pop a file from a queue, then activate it. */ +static bool bftw_pop(struct bftw_state *state, struct bftw_queue *queue) { + if (queue->size == 0) { + return false; } - // Block if the cache is full - if (state->cache.capacity == 0) { - return true; + while (!bftw_queue_ready(queue) && queue->ioqueued > 0) { + bool block = true; + if (bftw_queue_waiting(queue) && state->nthreads == 1) { + // With only one background thread, balance the work + // between it and the main thread + block = false; + } + + if (bftw_ioq_pop(state, block) < 0) { + break; + } } - // Block if we have no other files/dirs to visit - if (!bftw_queue_waiting(&state->dirq) && bftw_queue_empty(&state->fileq)) { - return true; + struct bftw_file *file = bftw_queue_pop(queue); + if (!file) { + return false; } - return false; + while (file->ioqueued) { + bftw_ioq_pop(state, true); + } + + state->file = file; + return true; } /** Pop a directory to read from the queue. */ @@ -1220,32 +1392,143 @@ static bool bftw_pop_dir(struct bftw_state *state) { if (state->strategy == BFTW_BFS && bftw_queue_ready(&state->fileq)) { return false; } + } else if (!bftw_queue_ready(&state->dirq)) { + // Don't block if we have files ready to visit + if (bftw_queue_ready(&state->fileq)) { + return false; + } + } + + return bftw_pop(state, &state->dirq); +} + +/** Figure out bfs_stat() flags. */ +static enum bfs_stat_flags bftw_stat_flags(const struct bftw_state *state, size_t depth) { + enum bftw_flags mask = BFTW_FOLLOW_ALL; + if (depth == 0) { + mask |= BFTW_FOLLOW_ROOTS; + } + + if (state->flags & mask) { + return BFS_STAT_TRYFOLLOW; } else { - while (!bftw_queue_ready(&state->dirq)) { - if (bftw_ioq_pop(state, bftw_block_for_dir(state)) < 0) { - break; - } + return BFS_STAT_NOFOLLOW; + } +} + +/** Check if a stat() call is necessary. */ +static bool bftw_must_stat(const struct bftw_state *state, size_t depth, enum bfs_type type, const char *name) { + if (state->flags & BFTW_STAT) { + return true; + } + + switch (type) { + case BFS_UNKNOWN: + return true; + + case BFS_DIR: + return state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS); + + case BFS_LNK: + if (!(bftw_stat_flags(state, depth) & BFS_STAT_NOFOLLOW)) { + return true; + } + fallthru; + + default: +#if __linux__ + if (state->mtab && bfs_might_be_mount(state->mtab, name)) { + return true; } +#endif + return false; } +} - struct bftw_file *file = bftw_queue_pop(&state->dirq); - if (!file) { +/** stat() a file asynchronously. */ +static int bftw_ioq_stat(struct bftw_state *state, struct bftw_file *file) { + if (bftw_ioq_reserve(state) != 0) { + goto fail; + } + + int dfd = bftw_pin_parent(state, file); + if (dfd < 0 && dfd != AT_FDCWD) { + goto fail; + } + + struct bftw_cache *cache = &state->cache; + struct bfs_stat *buf = arena_alloc(&cache->stat_bufs); + if (!buf) { + goto unpin; + } + + enum bfs_stat_flags flags = bftw_stat_flags(state, file->depth); + if (ioq_stat(state->ioq, dfd, file->name, flags, buf, file) != 0) { + goto free; + } + + return 0; + +free: + arena_free(&cache->stat_bufs, buf); +unpin: + bftw_unpin_parent(state, file, false); +fail: + return -1; +} + +/** 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 + if (file->depth == 0) { return false; } - while (file->ioqueued) { - bftw_ioq_pop(state, true); +#ifdef S_IFWHT + // ioq_stat() does not do whiteout emulation like bftw_stat_impl() + if (file->type == BFS_WHT) { + return false; } +#endif - state->file = file; - return true; + return bftw_must_stat(state, file->depth, file->type, file->name); +} + +/** Call stat() on files that need it. */ +static void bftw_stat_files(struct bftw_state *state) { + while (true) { + struct bftw_file *file = bftw_queue_waiting(&state->fileq); + if (!file) { + break; + } + + if (!bftw_should_ioq_stat(state, file)) { + bftw_queue_skip(&state->fileq, file); + continue; + } + + if (!bftw_queue_balanced(&state->fileq)) { + break; + } + + if (bftw_ioq_stat(state, file) == 0) { + bftw_queue_detach(&state->fileq, file, true); + } else { + break; + } + } +} + +/** Push a file onto the queue. */ +static void bftw_push_file(struct bftw_state *state, struct bftw_file *file) { + bftw_queue_push(&state->fileq, file); + bftw_stat_files(state); } /** Pop a file to visit from the queue. */ static bool bftw_pop_file(struct bftw_state *state) { bfs_assert(!state->file); - state->file = bftw_queue_pop(&state->fileq); - return state->file; + return bftw_pop(state, &state->fileq); } /** Build the path to the current file. */ @@ -1334,6 +1617,8 @@ static int bftw_opendir(struct bftw_state *state) { return -1; } + bftw_queue_rebalance(&state->dirq, false); + state->dir = bftw_file_opendir(state, file, state->path); if (!state->dir) { state->direrror = errno; @@ -1364,47 +1649,6 @@ static int bftw_readdir(struct bftw_state *state) { return ret; } -/** Check if a stat() call is needed for this visit. */ -static bool bftw_need_stat(const struct bftw_state *state) { - if (state->flags & BFTW_STAT) { - return true; - } - - const struct BFTW *ftwbuf = &state->ftwbuf; - if (ftwbuf->type == BFS_UNKNOWN) { - return true; - } - - if (ftwbuf->type == BFS_LNK && !(ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - return true; - } - - if (ftwbuf->type == BFS_DIR) { - if (state->flags & (BFTW_DETECT_CYCLES | BFTW_SKIP_MOUNTS | BFTW_PRUNE_MOUNTS)) { - return true; - } -#if __linux__ - } else if (state->mtab) { - // Linux fills in d_type from the underlying inode, even when - // the directory entry is a bind mount point. In that case, we - // need to stat() to get the correct type. We don't need to - // check for directories because they can only be mounted over - // by other directories. - if (bfs_might_be_mount(state->mtab, ftwbuf->path + ftwbuf->nameoff)) { - return true; - } -#endif - } - - return false; -} - -/** Initialize bftw_stat cache. */ -static void bftw_stat_init(struct bftw_stat *cache) { - cache->buf = NULL; - cache->error = 0; -} - /** Open a file if necessary. */ static int bftw_ensure_open(struct bftw_state *state, struct bftw_file *file, const char *path) { int ret = file->fd; @@ -1436,9 +1680,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { ftwbuf->error = state->direrror; ftwbuf->at_fd = AT_FDCWD; ftwbuf->at_path = ftwbuf->path; - ftwbuf->stat_flags = BFS_STAT_NOFOLLOW; - bftw_stat_init(&ftwbuf->lstat_cache); - bftw_stat_init(&ftwbuf->stat_cache); + bftw_stat_init(&ftwbuf->stat_bufs, &state->stat_buf, &state->lstat_buf); struct bftw_file *parent = NULL; if (de) { @@ -1451,6 +1693,7 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { ftwbuf->depth = file->depth; ftwbuf->type = file->type; ftwbuf->nameoff = file->nameoff; + bftw_stat_fill(&ftwbuf->stat_bufs, &file->stat_bufs); } if (parent) { @@ -1468,22 +1711,15 @@ static void bftw_init_ftwbuf(struct bftw_state *state, enum bftw_visit visit) { ftwbuf->nameoff = xbaseoff(ftwbuf->path); } + ftwbuf->stat_flags = bftw_stat_flags(state, ftwbuf->depth); + if (ftwbuf->error != 0) { ftwbuf->type = BFS_ERROR; return; } - int follow_flags = BFTW_FOLLOW_ALL; - if (ftwbuf->depth == 0) { - follow_flags |= BFTW_FOLLOW_ROOTS; - } - bool follow = state->flags & follow_flags; - if (follow) { - ftwbuf->stat_flags = BFS_STAT_TRYFOLLOW; - } - const struct bfs_stat *statbuf = NULL; - if (bftw_need_stat(state)) { + if (bftw_must_stat(state, ftwbuf->depth, ftwbuf->type, ftwbuf->path + ftwbuf->nameoff)) { statbuf = bftw_stat(ftwbuf, ftwbuf->stat_flags); if (statbuf) { ftwbuf->type = bfs_mode_to_type(statbuf->mode); @@ -1522,6 +1758,11 @@ static bool bftw_is_mount(struct bftw_state *state, const char *name) { return statbuf && statbuf->dev != parent->dev; } +/** Check if bfs_stat() was called from the main thread. */ +static bool bftw_stat_was_sync(const struct bftw_state *state, const struct bfs_stat *buf) { + return buf == &state->stat_buf || buf == &state->lstat_buf; +} + /** Invoke the callback. */ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *name, enum bftw_visit visit) { if (visit == BFTW_POST && !(state->flags & BFTW_POST_ORDER)) { @@ -1541,31 +1782,43 @@ static enum bftw_action bftw_call_back(struct bftw_state *state, const char *nam return BFTW_STOP; } + enum bftw_action ret = BFTW_PRUNE; if ((state->flags & BFTW_SKIP_MOUNTS) && bftw_is_mount(state, name)) { - return BFTW_PRUNE; + goto done; } - enum bftw_action ret = state->callback(ftwbuf, state->ptr); + ret = state->callback(ftwbuf, state->ptr); switch (ret) { case BFTW_CONTINUE: - if (visit != BFTW_PRE) { - return BFTW_PRUNE; - } - if (ftwbuf->type != BFS_DIR) { - return BFTW_PRUNE; - } - if ((state->flags & BFTW_PRUNE_MOUNTS) && bftw_is_mount(state, name)) { - return BFTW_PRUNE; + if (visit != BFTW_PRE || ftwbuf->type != BFS_DIR) { + ret = BFTW_PRUNE; + } else if (state->flags & BFTW_PRUNE_MOUNTS) { + if (bftw_is_mount(state, name)) { + ret = BFTW_PRUNE; + } } - fallthru; + break; + case BFTW_PRUNE: case BFTW_STOP: - return ret; + break; default: state->error = EINVAL; return BFTW_STOP; } + +done: + if (state->fileq.flags & BFTW_QBALANCE) { + // Detect any main-thread stat() calls to rebalance the queue + const struct bfs_stat *buf = bftw_cached_stat(ftwbuf, BFS_STAT_FOLLOW); + const struct bfs_stat *lbuf = bftw_cached_stat(ftwbuf, BFS_STAT_NOFOLLOW); + if (bftw_stat_was_sync(state, buf) || bftw_stat_was_sync(state, lbuf)) { + bftw_queue_rebalance(&state->fileq, false); + } + } + + return ret; } /** @@ -1589,8 +1842,13 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { int ret = 0; struct bftw_file *file = state->file; - if (file && state->dir) { - bftw_unpin_dir(state, file, true); + if (file) { + if (state->dir) { + bftw_cache_unpin(&state->cache, file); + } + if (file->dir) { + bftw_delayed_unwrap(state, file); + } } state->dir = NULL; state->de = NULL; @@ -1607,7 +1865,7 @@ static int bftw_gc(struct bftw_state *state, enum bftw_gc_flags flags) { } state->direrror = 0; - while ((file = SLIST_POP(&state->to_close))) { + while ((file = SLIST_POP(&state->to_close, ready))) { bftw_unwrapdir(state, file); } @@ -1685,6 +1943,7 @@ static void bftw_flush(struct bftw_state *state) { bftw_list_sort(&state->fileq.buffer); } bftw_queue_flush(&state->fileq); + bftw_stat_files(state); bftw_queue_flush(&state->dirq); bftw_ioq_opendirs(state); @@ -1704,22 +1963,46 @@ static int bftw_closedir(struct bftw_state *state) { static void bftw_save_ftwbuf(struct bftw_file *file, const struct BFTW *ftwbuf) { file->type = ftwbuf->type; - const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; - if (!statbuf || (ftwbuf->stat_flags & BFS_STAT_NOFOLLOW)) { - statbuf = ftwbuf->lstat_cache.buf; - } + const struct bfs_stat *statbuf = bftw_cached_stat(ftwbuf, ftwbuf->stat_flags); if (statbuf) { file->dev = statbuf->dev; file->ino = statbuf->ino; } } +/** Check if we should buffer a file instead of visiting it. */ +static bool bftw_buffer_file(const struct bftw_state *state, const struct bftw_file *file, const char *name) { + if (!name) { + // Already buffered + return false; + } + + if (state->flags & BFTW_BUFFER) { + return true; + } + + // If we need to call stat(), and can do it async, buffer this file + if (!state->ioq) { + return false; + } + + if (!bftw_queue_balanced(&state->fileq)) { + // stat() would run synchronously anyway + return false; + } + + size_t depth = file ? file->depth + 1 : 1; + enum bfs_type type = state->de ? state->de->type : BFS_UNKNOWN; + return bftw_must_stat(state, depth, type, name); +} + /** Visit and/or enqueue the current file. */ static int bftw_visit(struct bftw_state *state, const char *name) { + struct bftw_cache *cache = &state->cache; struct bftw_file *file = state->file; - if (name && (state->flags & BFTW_BUFFER)) { - file = bftw_file_new(&state->cache, file, name); + if (bftw_buffer_file(state, file, name)) { + file = bftw_file_new(cache, file, name); if (!file) { state->error = errno; return -1; @@ -1729,14 +2012,14 @@ static int bftw_visit(struct bftw_state *state, const char *name) { file->type = state->de->type; } - bftw_queue_push(&state->fileq, file); + bftw_push_file(state, file); return 0; } switch (bftw_call_back(state, name, BFTW_PRE)) { case BFTW_CONTINUE: if (name) { - file = bftw_file_new(&state->cache, state->file, name); + file = bftw_file_new(cache, state->file, name); } else { state->file = NULL; } @@ -1746,6 +2029,7 @@ static int bftw_visit(struct bftw_state *state, const char *name) { } bftw_save_ftwbuf(file, &state->ftwbuf); + bftw_stat_recycle(cache, file); bftw_push_dir(state, file); return 0; @@ -1757,10 +2041,22 @@ static int bftw_visit(struct bftw_state *state, const char *name) { } default: + if (file && !name) { + bftw_gc(state, BFTW_VISIT_NONE); + } return -1; } } +/** Drain a bftw_queue. */ +static void bftw_drain(struct bftw_state *state, struct bftw_queue *queue) { + bftw_queue_flush(queue); + + while (bftw_pop(state, queue)) { + bftw_gc(state, BFTW_VISIT_NONE); + } +} + /** * Dispose of the bftw() state. * @@ -1777,11 +2073,9 @@ static int bftw_state_destroy(struct bftw_state *state) { state->ioq = NULL; } - bftw_queue_flush(&state->dirq); - bftw_queue_flush(&state->fileq); - do { - bftw_gc(state, BFTW_VISIT_NONE); - } while (bftw_pop_dir(state) || bftw_pop_file(state)); + bftw_gc(state, BFTW_VISIT_NONE); + bftw_drain(state, &state->dirq); + bftw_drain(state, &state->fileq); ioq_destroy(ioq); @@ -1823,6 +2117,7 @@ static int bftw_impl(struct bftw_state *state) { if (bftw_visit(state, NULL) != 0) { return -1; } + bftw_flush(state); } return 0; diff --git a/src/bftw.h b/src/bftw.h index e325d14..2805361 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -26,12 +26,14 @@ enum bftw_visit { * Cached bfs_stat() info for a file. */ struct bftw_stat { - /** A pointer to the bfs_stat() buffer, if available. */ - const struct bfs_stat *buf; - /** Storage for the bfs_stat() buffer, if needed. */ - struct bfs_stat storage; - /** The cached error code, if any. */ - int error; + /** The bfs_stat(BFS_STAT_FOLLOW) buffer. */ + const struct bfs_stat *stat_buf; + /** The bfs_stat(BFS_STAT_NOFOLLOW) buffer. */ + const struct bfs_stat *lstat_buf; + /** The cached bfs_stat(BFS_STAT_FOLLOW) error. */ + int stat_err; + /** The cached bfs_stat(BFS_STAT_NOFOLLOW) error. */ + int lstat_err; }; /** @@ -62,10 +64,8 @@ struct BFTW { /** Flags for bfs_stat(). */ enum bfs_stat_flags stat_flags; - /** Cached bfs_stat() info for BFS_STAT_NOFOLLOW. */ - struct bftw_stat lstat_cache; - /** Cached bfs_stat() info for BFS_STAT_FOLLOW. */ - struct bftw_stat stat_cache; + /** Cached bfs_stat() info. */ + struct bftw_stat stat_bufs; }; /** diff --git a/src/eval.c b/src/eval.c index 6dc73d8..dfeaa1e 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1234,7 +1234,7 @@ static bool eval_file_unique(struct bfs_eval *state, struct trie *seen) { /** * Log a stat() call. */ -static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, const struct bftw_stat *cache, enum bfs_stat_flags flags) { +static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, enum bfs_stat_flags flags, int err) { bfs_debug_prefix(ctx, DEBUG_STAT); fprintf(stderr, "bfs_stat("); @@ -1254,10 +1254,10 @@ static void debug_stat(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf, con DEBUG_FLAG(flags, BFS_STAT_TRYFOLLOW); DEBUG_FLAG(flags, BFS_STAT_NOSYNC); - fprintf(stderr, ") == %d", cache->buf ? 0 : -1); + fprintf(stderr, ") == %d", err ? 0 : -1); - if (cache->error) { - fprintf(stderr, " [%d]", cache->error); + if (err) { + fprintf(stderr, " [%d]", err); } fprintf(stderr, "\n"); @@ -1271,14 +1271,14 @@ static void debug_stats(const struct bfs_ctx *ctx, const struct BFTW *ftwbuf) { return; } - const struct bfs_stat *statbuf = ftwbuf->stat_cache.buf; - if (statbuf || ftwbuf->stat_cache.error) { - debug_stat(ctx, ftwbuf, &ftwbuf->stat_cache, BFS_STAT_FOLLOW); + const struct bftw_stat *bufs = &ftwbuf->stat_bufs; + + if (bufs->stat_err >= 0) { + debug_stat(ctx, ftwbuf, BFS_STAT_FOLLOW, bufs->stat_err); } - const struct bfs_stat *lstatbuf = ftwbuf->lstat_cache.buf; - if ((lstatbuf && lstatbuf != statbuf) || ftwbuf->lstat_cache.error) { - debug_stat(ctx, ftwbuf, &ftwbuf->lstat_cache, BFS_STAT_NOFOLLOW); + if (bufs->lstat_err >= 0) { + debug_stat(ctx, ftwbuf, BFS_STAT_NOFOLLOW, bufs->lstat_err); } } diff --git a/tests/bfs/execdir_plus.sh b/tests/bfs/execdir_plus.sh index f66b898..6f24bdc 100644 --- a/tests/bfs/execdir_plus.sh +++ b/tests/bfs/execdir_plus.sh @@ -1,4 +1,4 @@ tree=$(invoke_bfs -D tree 2>&1 -quit) [[ "$tree" == *"-S dfs"* ]] && skip -bfs_diff basic -execdir "$TESTS/sort-args.sh" {} + +bfs_diff -j1 basic -execdir "$TESTS/sort-args.sh" {} + -- cgit v1.2.3 From 3b82c88d1950291b1b703f082df43ec1b9654eb5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Feb 2024 14:11:38 -0500 Subject: color: New API to check if stat() is necessary --- src/color.c | 5 +++++ src/color.h | 12 ++++++------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/color.c b/src/color.c index 1f10c04..8c32a68 100644 --- a/src/color.c +++ b/src/color.c @@ -764,6 +764,11 @@ static bool is_link_broken(const struct BFTW *ftwbuf) { } } +bool colors_need_stat(const struct colors *colors) { + return colors->setuid || colors->setgid || colors->executable || colors->multi_hard + || colors->sticky_other_writable || colors->other_writable || colors->sticky; +} + /** Get the color for a file. */ static const struct esc_seq *file_color(const struct colors *colors, const char *filename, const struct BFTW *ftwbuf, enum bfs_stat_flags flags) { enum bfs_type type = bftw_type(ftwbuf, flags); diff --git a/src/color.h b/src/color.h index 760aa9d..85633a4 100644 --- a/src/color.h +++ b/src/color.h @@ -19,17 +19,17 @@ struct colors; /** - * Parse a color table. - * - * @return The parsed color table. + * Parse the color table from the environment. */ struct colors *parse_colors(void); +/** + * Check if stat() info is required to color a file correctly. + */ +bool colors_need_stat(const struct colors *colors); + /** * Free a color table. - * - * @param colors - * The color table to free. */ void free_colors(struct colors *colors); -- cgit v1.2.3 From 6bb323d446e2500c5a20866b56335ac8633e1c23 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Feb 2024 14:20:02 -0500 Subject: ctx: Fill in ctx->threads earlier --- src/ctx.c | 16 ++++++++++++++++ src/eval.c | 21 ++------------------- src/parse.c | 6 ++---- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/src/ctx.c b/src/ctx.c index 1f3e72e..6c84f75 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -15,6 +15,21 @@ #include #include #include +#include + +/** Get the initial value for ctx->threads (-j). */ +static int bfs_nproc(void) { + long nproc = sysconf(_SC_NPROCESSORS_ONLN); + + if (nproc < 1) { + nproc = 1; + } else if (nproc > 8) { + // Not much speedup after 8 threads + nproc = 8; + } + + return nproc; +} struct bfs_ctx *bfs_ctx_new(void) { struct bfs_ctx *ctx = ZALLOC(struct bfs_ctx); @@ -28,6 +43,7 @@ struct bfs_ctx *bfs_ctx_new(void) { ctx->maxdepth = INT_MAX; ctx->flags = BFTW_RECOVER; ctx->strategy = BFTW_BFS; + ctx->threads = bfs_nproc(); ctx->optlevel = 3; trie_init(&ctx->files); diff --git a/src/eval.c b/src/eval.c index dfeaa1e..a4c0c11 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1515,19 +1515,6 @@ done: return ret; } -static int infer_nproc(void) { - long nproc = sysconf(_SC_NPROCESSORS_ONLN); - - if (nproc < 1) { - nproc = 1; - } else if (nproc > 8) { - // Not much speedup after 8 threads - nproc = 8; - } - - return nproc; -} - /** * Dump the bftw() flags for -D search. */ @@ -1614,12 +1601,8 @@ int bfs_eval(struct bfs_ctx *ctx) { reserve_fds(fdlimit); fdlimit = infer_fdlimit(ctx, fdlimit); - int nthreads; - if (ctx->threads > 0) { - nthreads = ctx->threads - 1; - } else { - nthreads = infer_nproc() - 1; - } + // -1 for the main thread + int nthreads = ctx->threads - 1; struct bftw_args bftw_args = { .paths = ctx->paths, diff --git a/src/parse.c b/src/parse.c index 4212196..3a78840 100644 --- a/src/parse.c +++ b/src/parse.c @@ -3452,14 +3452,12 @@ void bfs_ctx_dump(const struct bfs_ctx *ctx, enum debug_flags flag) { cfprintf(cerr, " ${cyn}-s${rs}"); } + cfprintf(cerr, " ${cyn}-j${bld}%d${rs}", ctx->threads); + if (ctx->optlevel != 3) { cfprintf(cerr, " ${cyn}-O${bld}%d${rs}", ctx->optlevel); } - if (ctx->threads > 0) { - cfprintf(cerr, " ${cyn}-j${bld}%d${rs}", ctx->threads); - } - cfprintf(cerr, " ${cyn}-S${rs} ${bld}%s${rs}", bftw_strategy_name(ctx->strategy)); enum debug_flags debug = ctx->debug; -- cgit v1.2.3 From 5f0958fa57770e13b8696b8ee6fc87890333d90b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 5 Feb 2024 15:13:38 -0500 Subject: opt: Enable BFTW_STAT when profitable --- src/expr.h | 2 ++ src/opt.c | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) diff --git a/src/expr.h b/src/expr.h index 4d607a4..349e052 100644 --- a/src/expr.h +++ b/src/expr.h @@ -110,6 +110,8 @@ struct bfs_expr { bool always_true; /** Whether this expression always evaluates to false. */ bool always_false; + /** Whether this expression uses stat(). */ + bool calls_stat; /** Estimated cost. */ float cost; diff --git a/src/opt.c b/src/opt.c index 28a2255..74145ac 100644 --- a/src/opt.c +++ b/src/opt.c @@ -1004,6 +1004,13 @@ static struct bfs_expr *annotate_fnmatch(struct bfs_opt *opt, struct bfs_expr *e return expr; } +/** Annotate -f?print. */ +static struct bfs_expr *annotate_fprint(struct bfs_opt *opt, struct bfs_expr *expr, const struct visitor *visitor) { + const struct colors *colors = expr->cfile->colors; + expr->calls_stat = colors && colors_need_stat(colors); + return expr; +} + /** Estimate probability for -x?type. */ static void estimate_type_probability(struct bfs_expr *expr) { unsigned int types = expr->num; @@ -1204,6 +1211,38 @@ static struct bfs_expr *annotate_visit(struct bfs_opt *opt, struct bfs_expr *exp } } + /** Table of stat-calling primaries. */ + static bfs_eval_fn *const calls_stat[] = { + eval_empty, + eval_flags, + eval_fls, + eval_fprintf, + eval_fstype, + eval_gid, + eval_inum, + eval_links, + eval_newer, + eval_nogroup, + eval_nouser, + eval_perm, + eval_samefile, + eval_size, + eval_sparse, + eval_time, + eval_uid, + eval_used, + eval_xattr, + eval_xattrname, + }; + + expr->calls_stat = false; + for (size_t i = 0; i < countof(calls_stat); ++i) { + if (expr->eval_fn == calls_stat[i]) { + expr->calls_stat = true; + break; + } + } + #define FAST_COST 40.0 #define FNMATCH_COST 400.0 #define STAT_COST 1000.0 @@ -1294,6 +1333,7 @@ static const struct visitor annotate = { {eval_access, annotate_access}, {eval_empty, annotate_empty}, {eval_exec, annotate_exec}, + {eval_fprint, annotate_fprint}, {eval_lname, annotate_fnmatch}, {eval_name, annotate_fnmatch}, {eval_path, annotate_fnmatch}, @@ -2130,6 +2170,43 @@ 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) { + return 1.0; + } + + float nostat_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; + + if (expr->eval_fn == eval_and) { + reached_odds *= child->probability; + } else if (expr->eval_fn == eval_or) { + reached_odds *= 1.0 - child->probability; + } + } + + return 1.0 - nostat_odds; +} + +/** Estimate the odds of calling stat(). */ +static float estimate_stat_odds(struct bfs_ctx *ctx) { + if (ctx->unique) { + return 1.0; + } + + float nostat_odds = 1.0 - expr_stat_odds(ctx->exclude); + + float reached_odds = 1.0 - ctx->exclude->probability; + float expr_odds = expr_stat_odds(ctx->expr); + nostat_odds *= 1.0 - reached_odds * expr_odds; + + return 1.0 - nostat_odds; +} + int bfs_optimize(struct bfs_ctx *ctx) { bfs_ctx_dump(ctx, DEBUG_OPT); @@ -2196,6 +2273,20 @@ int bfs_optimize(struct bfs_ctx *ctx) { opt_leave(&opt, "${blu}-maxdepth${rs} ${bld}%d${rs}\n", ctx->maxdepth); } + if (opt.level >= 3) { + // bfs_eval() can do lazy stat() calls, but only on one thread. + float lazy_cost = estimate_stat_odds(ctx); + // bftw() can do eager stat() calls in parallel + float eager_cost = 1.0 / ctx->threads; + + if (eager_cost <= lazy_cost) { + opt_enter(&opt, "lazy stat cost: ${ylw}%g${rs}\n", lazy_cost); + ctx->flags |= BFTW_STAT; + opt_leave(&opt, "eager stat cost: ${ylw}%g${rs}\n", eager_cost); + } + + } + opt_leave(&opt, NULL); return 0; -- cgit v1.2.3 From 66468fd13ce70d0f6f6388cd31d7f75196ccac31 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 6 Feb 2024 16:06:27 -0500 Subject: Release 3.1 --- GNUmakefile | 2 +- docs/CHANGELOG.md | 44 ++++++++++++++++++++++++++++++++++++++++++++ src/config.h | 2 +- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 637a63e..8b1f848 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 3.0.4 +VERSION := 3.1 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3b07f01..b70ed32 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,50 @@ 3.* === +3.1 +--- + +**February 6, 2024** + +### New features + +- On Linux, `bfs` now uses [io_uring](https://en.wikipedia.org/wiki/Io_uring) for async I/O + +- On all platforms, `bfs` can now perform `stat()` calls in parallel, accelerating queries like `-links`, `-newer`, and `-size`, as well as colorized output + +- On FreeBSD, `-type w` now works to find whiteouts like the system `find` + +### Changes + +- Improved `bfs -j2` performance ([`b2ab7a1`](https://github.com/tavianator/bfs/commit/b2ab7a151fca517f4879e76e626ec85ad3de97c7)) + +- Optimized `-exec` by using `posix_spawn()` when possible, which can avoid the overhead of `fork()` ([`95fbde1`](https://github.com/tavianator/bfs/commit/95fbde17a66377b6fbe7ff1f014301dbbf09270d)) + +- `-execdir` and `-okdir` are now rejected if `$PATH` contains a relative path, matching the behaviour of GNU find ([`163baf1`](https://github.com/tavianator/bfs/commit/163baf1c9af13be0ce705b133e41e0c3d6427398)) + +- Leading whitespace is no longer accepted in integer command line arguments like `-links ' 1'` ([`e0d7dc5`](https://github.com/tavianator/bfs/commit/e0d7dc5dfd7bdaa62b6bc18e9c1cce00bbe08577)) + +### Bug Fixes + +- `-quit` and `-exit` could be ignored in the iterative deepening modes (`-S {ids,eds}`). + This is now fixed ([`670ebd9`](https://github.com/tavianator/bfs/commit/670ebd97fb431e830b1500b2e7e8013b121fb2c5)). + The bug was introduced in version 3.0.3 (commit [`5f16169`]). + +- Fixed two possible errors in sort mode (`-s`): + - Too many open files ([`710c083`](https://github.com/tavianator/bfs/commit/710c083ff02eb1cc5b8daa6778784f3d1cd3c08d)) + - Out of memory ([`76ffc8d`](https://github.com/tavianator/bfs/commit/76ffc8d30cb1160d55d855d8ac630a2b9075fbcf)) + +- Fixed handling of FreeBSD union mounts ([`3ac3bee`](https://github.com/tavianator/bfs/commit/3ac3bee7b0d9c9be693415206efa664bf4a7d4a7)) + +- Fixed `NO_COLOR` handling when it's set to the empty string ([`79aee58`](https://github.com/tavianator/bfs/commit/79aee58a4621d01c4b1e98c332775f3b87213ddb)) + +- Fixed some portability issues: + - [OpenBSD](https://github.com/tavianator/bfs/compare/ee200c07643801c8b53e5b80df704ecbf77a884e...79f1521b0e628be72bed3a648f0ae90b62fc69b8) + - [NetBSD](https://github.com/tavianator/bfs/compare/683f2c41c72efcb82ce866e3dcc311ac9bd8b66d...6435684a7d515e18247ae1b3dd9ec8681fee22d0) + - [DragonFly BSD](https://github.com/tavianator/bfs/compare/08867473e75e8e20ca76c7fb181204839e28b271...45fb1d952c3b262278a3b22e9c7d60cca19a5407) + - [Illumos](https://github.com/tavianator/bfs/compare/4010140cb748cc4f7f57b0a3d514485796c665ce...ae94cdc00136685abe61d55e1e357caaa636d785) + + 3.0.4 ----- diff --git a/src/config.h b/src/config.h index 85ad57e..519a454 100644 --- a/src/config.h +++ b/src/config.h @@ -30,7 +30,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "3.0.4" +# define BFS_VERSION "3.1" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From 8de8764337c0baf489ad521295e9a2f185dc7111 Mon Sep 17 00:00:00 2001 From: Sam James Date: Wed, 7 Feb 2024 03:30:05 +0000 Subject: README.md: bfs is now in Gentoo as sys-apps/bfs --- README.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.md b/README.md index 79ffe65..9992938 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,9 @@ Installation Fedora Linux # dnf install bfs +Gentoo +# emerge sys-apps/bfs + GNU Guix # guix install bfs -- cgit v1.2.3 From 27924356725df884a5351db3e930171af2ea0488 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 12 Feb 2024 13:20:50 -0500 Subject: bench: Add a stat() benchmark --- bench/bench.sh | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 4 deletions(-) diff --git a/bench/bench.sh b/bench/bench.sh index 3436b19..e4b5511 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -17,6 +17,7 @@ declare -gA TAGS=( COMPLETE_DEFAULT=(linux rust chromium) EARLY_QUIT_DEFAULT=(chromium) +STAT_DEFAULT=(rust) PRINT_DEFAULT=(linux) STRATEGIES_DEFAULT=(rust) JOBS_DEFAULT=(rust) @@ -32,15 +33,19 @@ usage() { printf ' Run the default set of benchmarks\n\n' printf ' --complete[=CORPUS]\n' - printf ' Complete traversal benchmark. \n' + printf ' Complete traversal benchmark.\n' printf ' Default corpus is --complete="%s"\n\n' "${COMPLETE_DEFAULT[*]}" printf ' --early-quit[=CORPUS]\n' - printf ' Early quitting benchmark. \n' + printf ' Early quitting benchmark.\n' printf ' Default corpus is --early-quit=%s\n\n' "${EARLY_QUIT_DEFAULT[*]}" + printf ' --stat[=CORPUS]\n' + printf ' Traversal with stat().\n' + printf ' Default corpus is --stat=%s\n\n' "${STAT_DEFAULT[*]}" + printf ' --print[=CORPUS]\n' - printf ' Path printing benchmark. \n' + printf ' Path printing benchmark.\n' printf ' Default corpus is --print=%s\n\n' "${PRINT_DEFAULT[*]}" printf ' --strategies[=CORPUS]\n' @@ -111,6 +116,7 @@ setup() { COMPLETE=() EARLY_QUIT=() + STAT=() PRINT=() STRATEGIES=() JOBS=() @@ -159,6 +165,12 @@ setup() { --early-quit=*) read -ra EARLY_QUIT <<<"${arg#*=}" ;; + --stat) + STAT=("${STAT_DEFAULT[@]}") + ;; + --stat=*) + read -ra STAT <<<"${arg#*=}" + ;; --print) PRINT=("${PRINT_DEFAULT[@]}") ;; @@ -186,6 +198,7 @@ setup() { --default) COMPLETE=("${COMPLETE_DEFAULT[@]}") EARLY_QUIT=("${EARLY_QUIT_DEFAULT[@]}") + STAT=("${STAT_DEFAULT[@]}") PRINT=("${PRINT_DEFAULT[@]}") STRATEGIES=("${STRATEGIES_DEFAULT[@]}") JOBS=("${JOBS_DEFAULT[@]}") @@ -213,7 +226,7 @@ setup() { as-user mkdir -p bench/corpus declare -A cloned=() - for corpus in "${COMPLETE[@]}" "${EARLY_QUIT[@]}" "${PRINT[@]}" "${STRATEGIES[@]}" "${JOBS[@]}" "${EXEC[@]}"; do + for corpus in "${COMPLETE[@]}" "${EARLY_QUIT[@]}" "${STAT[@]}" "${PRINT[@]}" "${STRATEGIES[@]}" "${JOBS[@]}" "${EXEC[@]}"; do if ((cloned["$corpus"])); then continue fi @@ -264,6 +277,7 @@ setup() { export_array COMPLETE export_array EARLY_QUIT + export_array STAT export_array PRINT export_array STRATEGIES export_array JOBS @@ -394,6 +408,39 @@ bench-early-quit() { fi } +# Benchmark traversal with stat() +bench-stat-corpus() { + total=$(./bin/bfs "$2" -printf '.' | wc -c) + + subgroup "%s (%'d files)" "$1" "$total" + + cmds=() + for bfs in "${BFS[@]}"; do + cmds+=("$bfs $2 -size 1024G") + done + + for find in "${FIND[@]}"; do + cmds+=("$find $2 -size 1024G") + done + + for fd in "${FD[@]}"; do + cmds+=("$fd -u --search-path $2 --size 1024Gi") + done + + do-hyperfine "${cmds[@]}" +} + +# stat() benchmarks +bench-stat() { + if (($#)); then + group "Traversal with stat()" + + for corpus; do + bench-stat-corpus "$corpus ${TAGS[$corpus]}" "bench/corpus/$corpus" + done + fi +} + # Benchmark printing paths without colors bench-print-nocolor() { subsubgroup '%s' "$1" @@ -645,6 +692,7 @@ bench() { import_array COMPLETE import_array EARLY_QUIT + import_array STAT import_array PRINT import_array STRATEGIES import_array JOBS @@ -652,6 +700,7 @@ bench() { bench-complete "${COMPLETE[@]}" bench-early-quit "${EARLY_QUIT[@]}" + bench-stat "${STAT[@]}" bench-print "${PRINT[@]}" bench-strategies "${STRATEGIES[@]}" bench-jobs "${JOBS[@]}" -- cgit v1.2.3 From 1e5cd0a3d4585793c5a4d16ab60473a57e18af23 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 12 Feb 2024 13:24:11 -0500 Subject: ioq: Shrink the io_urings --- src/ioq.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 3172f0a..50550ed 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -764,10 +764,8 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { params.wq_fd = prev->ring.ring_fd; } - size_t entries = depth / nthreads; - if (entries < 16) { - entries = 16; - } + // Use a page for each SQE ring + size_t entries = 4096 / sizeof(struct io_uring_sqe); thread->ring_err = -io_uring_queue_init_params(entries, &thread->ring, ¶ms); } #endif -- cgit v1.2.3 From a98fe72db88350fcec030487208e6c50c9de1974 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 12 Feb 2024 13:26:10 -0500 Subject: ioq: Get rid of IOQ_STRIDE Benchmarks show it hurts more than it helps. --- src/ioq.c | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 50550ed..438601d 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -26,15 +26,11 @@ * * Pushes are implemented with an unconditional * - * fetch_add(&ioqq->head, IOQ_STRIDE) + * fetch_add(&ioqq->head, 1) * * which scales better on many architectures than compare-and-swap (see [1] for - * details). Pops are implemented similarly. We add IOQ_STRIDE rather than 1 - * so that successive queue elements are on different cache lines, but the - * exposition below uses 1 for simplicity. - * - * Since the fetch-and-adds are unconditional, non-blocking readers can get - * ahead of writers: + * details). Pops are implemented similarly. Since the fetch-and-adds are + * unconditional, non-blocking readers can get ahead of writers: * * Reader Writer * ──────────────── ────────────────────── @@ -204,17 +200,6 @@ struct ioqq { cache_align ioq_slot slots[]; }; -// If we assign slots sequentially, threads will likely be operating on -// consecutive slots. If these slots are in the same cache line, that will -// result in false sharing. We can mitigate this by assigning slots with a -// stride larger than a cache line e.g. 0, 9, 18, ..., 1, 10, 19, ... -// As long as the stride is relatively prime to circular buffer length, we'll -// still use every available slot. Since the length is a power of two, that -// means the stride must be odd. - -#define IOQ_STRIDE ((FALSE_SHARING_SIZE / sizeof(ioq_slot)) | 1) -bfs_static_assert(IOQ_STRIDE % 2 == 1); - /** Destroy an I/O command queue. */ static void ioqq_destroy(struct ioqq *ioqq) { if (!ioqq) { @@ -357,7 +342,7 @@ static bool ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent /** Push an entry onto the queue. */ static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { while (true) { - size_t i = fetch_add(&ioqq->head, IOQ_STRIDE, relaxed); + size_t i = fetch_add(&ioqq->head, 1, relaxed); ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; if (ioq_slot_push(ioqq, slot, ent)) { break; @@ -400,7 +385,7 @@ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool bloc /** Pop an entry from the queue. */ static struct ioq_ent *ioqq_pop(struct ioqq *ioqq, bool block) { - size_t i = fetch_add(&ioqq->tail, IOQ_STRIDE, relaxed); + size_t i = fetch_add(&ioqq->tail, 1, relaxed); ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; return ioq_slot_pop(ioqq, slot, block); } -- cgit v1.2.3 From 4ddbaf8a44e4bf46d3ebe3c5afc957ecd07cfa0a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 12 Feb 2024 14:36:52 -0500 Subject: sanity: Don't use self-init for uninit() Self-initialization like bool ret = ret; is a GCC trick to suppress uninitialized variable warnings, but it's not actually well-defined, and will trip a recent enough MemorySanitizer: src/eval.c:1088:13: runtime error: load of value 128, which is not a valid value for type 'bool' --- src/eval.c | 2 +- src/parse.c | 8 ++++---- src/sanity.h | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/eval.c b/src/eval.c index a4c0c11..6d7afad 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1085,7 +1085,7 @@ bool eval_or(const struct bfs_expr *expr, struct bfs_eval *state) { * Evaluate the comma operator. */ bool eval_comma(const struct bfs_expr *expr, struct bfs_eval *state) { - bool ret = uninit(ret, false); + bool ret uninit(false); for (struct bfs_expr *child = bfs_expr_children(expr); child; child = child->next) { ret = eval_expr(child, state); diff --git a/src/parse.c b/src/parse.c index 3a78840..5d0f333 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1891,11 +1891,11 @@ static int parse_mode(const struct bfs_parser *parser, const char *mode, struct MODE_PLUS, MODE_MINUS, MODE_EQUALS, - } op = uninit(op, MODE_EQUALS); + } op uninit(MODE_EQUALS); - mode_t who = uninit(who, 0); - mode_t file_change = uninit(file_change, 0); - mode_t dir_change = uninit(dir_change, 0); + mode_t who uninit(0); + mode_t file_change uninit(0); + mode_t dir_change uninit(0); const char *i = mode; while (true) { diff --git a/src/sanity.h b/src/sanity.h index 29b3519..423e6ff 100644 --- a/src/sanity.h +++ b/src/sanity.h @@ -86,9 +86,9 @@ * Initialize a variable, unless sanitizers would detect uninitialized uses. */ #if SANITIZE_MEMORY -# define uninit(var, value) var +# define uninit(value) #else -# define uninit(var, value) value +# define uninit(value) = value #endif #endif // BFS_SANITY_H -- cgit v1.2.3 From 1a6e712246d01fd7e68b81116bfdae52026b4cd4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 12 Feb 2024 15:36:37 -0500 Subject: eval: Add thousands separators to -status --- src/eval.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/eval.c b/src/eval.c index 6d7afad..c4c79e0 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1120,12 +1120,12 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time const struct BFTW *ftwbuf = state->ftwbuf; dchar *status = NULL; - dchar *rhs = dstrprintf(" (visited: %zu, depth: %2zu)", count, ftwbuf->depth); + dchar *rhs = dstrprintf(" (visited: %'zu, depth: %2zu)", count, ftwbuf->depth); if (!rhs) { return; } - size_t rhslen = dstrlen(rhs); + size_t rhslen = xstrwidth(rhs); if (3 + rhslen > width) { dstresize(&rhs, 0); rhslen = 0; -- cgit v1.2.3 From d59d9acbe3c458f240fc3fb406f5bd1dcd38a1c8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 10:58:04 -0500 Subject: stat: Pack struct bfs_stat a bit --- src/stat.c | 19 ++++++++----------- src/stat.h | 33 ++++++++++++++++----------------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/stat.c b/src/stat.c index 4e7e7e2..36222cb 100644 --- a/src/stat.c +++ b/src/stat.c @@ -21,14 +21,12 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { switch (field) { + case BFS_STAT_MODE: + return "mode"; case BFS_STAT_DEV: return "device number"; case BFS_STAT_INO: return "inode nunmber"; - case BFS_STAT_TYPE: - return "type"; - case BFS_STAT_MODE: - return "mode"; case BFS_STAT_NLINK: return "link count"; case BFS_STAT_GID: @@ -74,15 +72,15 @@ int bfs_fstatat_flags(enum bfs_stat_flags flags) { void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { dest->mask = 0; + dest->mode = src->st_mode; + dest->mask |= BFS_STAT_MODE; + dest->dev = src->st_dev; dest->mask |= BFS_STAT_DEV; dest->ino = src->st_ino; dest->mask |= BFS_STAT_INO; - dest->mode = src->st_mode; - dest->mask |= BFS_STAT_TYPE | BFS_STAT_MODE; - dest->nlink = src->st_nlink; dest->mask |= BFS_STAT_NLINK; @@ -173,16 +171,15 @@ int bfs_statx_convert(struct bfs_stat *dest, const struct statx *src) { dest->mask = 0; + dest->mode = src->stx_mode; + dest->mask |= BFS_STAT_MODE; + dest->dev = xmakedev(src->stx_dev_major, src->stx_dev_minor); dest->mask |= BFS_STAT_DEV; dest->ino = src->stx_ino; dest->mask |= BFS_STAT_INO; - dest->mode = src->stx_mode; - dest->mask |= BFS_STAT_TYPE; - dest->mask |= BFS_STAT_MODE; - dest->nlink = src->stx_nlink; dest->mask |= BFS_STAT_NLINK; diff --git a/src/stat.h b/src/stat.h index 17b7fe1..856a2ca 100644 --- a/src/stat.h +++ b/src/stat.h @@ -45,21 +45,20 @@ * bfs_stat field bitmask. */ enum bfs_stat_field { - BFS_STAT_DEV = 1 << 0, - BFS_STAT_INO = 1 << 1, - BFS_STAT_TYPE = 1 << 2, - BFS_STAT_MODE = 1 << 3, - BFS_STAT_NLINK = 1 << 4, - BFS_STAT_GID = 1 << 5, - BFS_STAT_UID = 1 << 6, - BFS_STAT_SIZE = 1 << 7, - BFS_STAT_BLOCKS = 1 << 8, - BFS_STAT_RDEV = 1 << 9, - BFS_STAT_ATTRS = 1 << 10, - BFS_STAT_ATIME = 1 << 11, - BFS_STAT_BTIME = 1 << 12, - BFS_STAT_CTIME = 1 << 13, - BFS_STAT_MTIME = 1 << 14, + BFS_STAT_MODE = 1 << 0, + BFS_STAT_DEV = 1 << 1, + BFS_STAT_INO = 1 << 2, + BFS_STAT_NLINK = 1 << 3, + BFS_STAT_GID = 1 << 4, + BFS_STAT_UID = 1 << 5, + BFS_STAT_SIZE = 1 << 6, + BFS_STAT_BLOCKS = 1 << 7, + BFS_STAT_RDEV = 1 << 8, + BFS_STAT_ATTRS = 1 << 9, + BFS_STAT_ATIME = 1 << 10, + BFS_STAT_BTIME = 1 << 11, + BFS_STAT_CTIME = 1 << 12, + BFS_STAT_MTIME = 1 << 13, }; /** @@ -88,12 +87,12 @@ struct bfs_stat { /** Bitmask indicating filled fields. */ enum bfs_stat_field mask; + /** File type and access mode. */ + mode_t mode; /** Device ID containing the file. */ dev_t dev; /** Inode number. */ ino_t ino; - /** File type and access mode. */ - mode_t mode; /** Number of hard links. */ nlink_t nlink; /** Owner group ID. */ -- cgit v1.2.3 From ed36b75a120830d835194fc793cfcb212ab67929 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 11:05:41 -0500 Subject: expr: Pack struct bfs_expr a bit --- src/expr.h | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/expr.h b/src/expr.h index 349e052..957b04a 100644 --- a/src/expr.h +++ b/src/expr.h @@ -136,21 +136,15 @@ struct bfs_expr { /** The comparison mode. */ enum bfs_int_cmp int_cmp; - /** Optional extra data. */ - union { - /** -size data. */ - enum bfs_size_unit size_unit; - - /** Timestamp comparison data. */ - struct { - /** The stat field to look at. */ - enum bfs_stat_field stat_field; - /** The reference time. */ - struct timespec reftime; - /** The time unit. */ - enum bfs_time_unit time_unit; - }; - }; + /** -size data. */ + enum bfs_size_unit size_unit; + + /** The stat field to look at. */ + enum bfs_stat_field stat_field; + /** The time unit. */ + enum bfs_time_unit time_unit; + /** The reference time. */ + struct timespec reftime; }; /** String comparisons. */ -- cgit v1.2.3 From 16ba60f128f6a099f4d47291e570019a7c897f00 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 11:16:17 -0500 Subject: bftw: Document which bftw_file nodes go with which lists --- src/bftw.c | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 664651c..6f52299 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -196,12 +196,26 @@ struct bftw_file { /** The root under which this file was found. */ struct bftw_file *root; - /** The next file to open/close/visit. */ + /** + * List node for: + * + * bftw_queue::buffer + * bftw_queue::waiting + * bftw_file_open()::parents + */ struct bftw_file *next; - /** The next directory to read. */ + + /** + * List node for: + * + * bftw_queue::ready + * bftw_state::to_close + */ struct { struct bftw_file *next; } ready; - /** LRU list node. */ + /** + * List node for bftw_cache. + */ struct { struct bftw_file *prev; struct bftw_file *next; -- cgit v1.2.3 From 2fe4c9922bd02e0ec4bca8151cbff1a0bcf29dcf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 11:36:03 -0500 Subject: ioq: Pack ioq_ent args structs --- src/ioq.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ioq.h b/src/ioq.h index 77aabaa..30e90e0 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -54,8 +54,8 @@ struct ioq_ent { /** ioq_opendir() args. */ struct ioq_opendir { struct bfs_dir *dir; - int dfd; const char *path; + int dfd; enum bfs_dir_flags flags; } opendir; /** ioq_closedir() args. */ @@ -64,11 +64,11 @@ struct ioq_ent { } closedir; /** ioq_stat() args. */ struct ioq_stat { - int dfd; const char *path; - enum bfs_stat_flags flags; struct bfs_stat *buf; void *xbuf; + int dfd; + enum bfs_stat_flags flags; } stat; }; }; -- cgit v1.2.3 From 5f7cc43ba12c87b70fe1e8f8f225258e718048ba Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 14:03:26 -0500 Subject: ioq: Replay IOQ_STOP messages rather than spam them --- src/ioq.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 438601d..ede2413 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -511,9 +511,11 @@ static struct ioq_ent *ioq_ring_pop(struct ioq_ring_state *state) { // Block if we have nothing else to do bool block = !state->prepped && !state->submitted; - struct ioq_ent *ret = ioqq_pop(state->ioq->pending, block); + struct ioqq *pending = state->ioq->pending; + struct ioq_ent *ret = ioqq_pop(pending, block); if (ret == &IOQ_STOP) { + ioqq_push(pending, &IOQ_STOP); state->stop = true; ret = NULL; } @@ -685,6 +687,7 @@ static void ioq_sync_work(struct ioq_thread *thread) { while (true) { struct ioq_ent *ent = ioqq_pop(ioq->pending, true); if (ent == &IOQ_STOP) { + ioqq_push(ioq->pending, &IOQ_STOP); break; } @@ -884,9 +887,7 @@ void ioq_free(struct ioq *ioq, struct ioq_ent *ent) { void ioq_cancel(struct ioq *ioq) { if (!exchange(&ioq->cancel, true, relaxed)) { - for (size_t i = 0; i < ioq->nthreads; ++i) { - ioqq_push(ioq->pending, &IOQ_STOP); - } + ioqq_push(ioq->pending, &IOQ_STOP); } } -- cgit v1.2.3 From fc2ce8a878bd5acb8881e9408f6e0484d9b446c5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 14:37:23 -0500 Subject: ioq: Make -j also limit the io_uring worker threads --- src/ioq.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/ioq.c b/src/ioq.c index ede2413..0126f5c 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -755,6 +755,15 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { // Use a page for each SQE ring size_t entries = 4096 / sizeof(struct io_uring_sqe); thread->ring_err = -io_uring_queue_init_params(entries, &thread->ring, ¶ms); + + if (!prev && thread->ring_err == 0) { + // Limit the number of io_uring workers + unsigned int values[] = { + [IO_WQ_BOUND] = nthreads, + [IO_WQ_UNBOUND] = 0, + }; + io_uring_register_iowq_max_workers(&thread->ring, values); + } } #endif -- cgit v1.2.3 From 80fbd584adf480f62e3282d570f67767c74c13d3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 15:05:57 -0500 Subject: ioq: Factor out io_uring initialization --- src/ioq.c | 110 +++++++++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 73 insertions(+), 37 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 0126f5c..3c26814 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -712,6 +712,75 @@ static void *ioq_work(void *ptr) { return NULL; } +/** Initialize io_uring thread state. */ +static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) { +#if BFS_USE_LIBURING + struct ioq_thread *prev = NULL; + if (thread > ioq->threads) { + prev = thread - 1; + } + + if (prev && prev->ring_err) { + thread->ring_err = prev->ring_err; + return -1; + } + + // Share io-wq workers between rings + struct io_uring_params params = {0}; + if (prev) { + params.flags |= IORING_SETUP_ATTACH_WQ; + params.wq_fd = prev->ring.ring_fd; + } + + // Use a page for each SQE ring + size_t entries = 4096 / sizeof(struct io_uring_sqe); + thread->ring_err = -io_uring_queue_init_params(entries, &thread->ring, ¶ms); + if (thread->ring_err) { + return -1; + } + + if (!prev) { + // Limit the number of io_uring workers + unsigned int values[] = { + [IO_WQ_BOUND] = ioq->nthreads, + [IO_WQ_UNBOUND] = 0, + }; + io_uring_register_iowq_max_workers(&thread->ring, values); + } +#endif + + return 0; +} + +/** Destroy an io_uring. */ +static void ioq_ring_exit(struct ioq_thread *thread) { +#if BFS_USE_LIBURING + if (thread->ring_err == 0) { + io_uring_queue_exit(&thread->ring); + } +#endif +} + +/** Create an I/O queue thread. */ +static int ioq_thread_create(struct ioq *ioq, struct ioq_thread *thread) { + thread->parent = ioq; + + ioq_ring_init(ioq, thread); + + if (thread_create(&thread->id, NULL, ioq_work, thread) != 0) { + ioq_ring_exit(thread); + return -1; + } + + return 0; +} + +/** Join an I/O queue thread. */ +static void ioq_thread_join(struct ioq_thread *thread) { + thread_join(thread->id, NULL); + ioq_ring_exit(thread); +} + struct ioq *ioq_create(size_t depth, size_t nthreads) { struct ioq *ioq = ZALLOC_FLEX(struct ioq, threads, nthreads); if (!ioq) { @@ -736,41 +805,12 @@ struct ioq *ioq_create(size_t depth, size_t nthreads) { goto fail; } + ioq->nthreads = nthreads; for (size_t i = 0; i < nthreads; ++i) { - struct ioq_thread *thread = &ioq->threads[i]; - thread->parent = ioq; - -#if BFS_USE_LIBURING - struct ioq_thread *prev = i ? &ioq->threads[i - 1] : NULL; - if (prev && prev->ring_err) { - thread->ring_err = prev->ring_err; - } else { - // Share io-wq workers between rings - struct io_uring_params params = {0}; - if (prev) { - params.flags |= IORING_SETUP_ATTACH_WQ; - params.wq_fd = prev->ring.ring_fd; - } - - // Use a page for each SQE ring - size_t entries = 4096 / sizeof(struct io_uring_sqe); - thread->ring_err = -io_uring_queue_init_params(entries, &thread->ring, ¶ms); - - if (!prev && thread->ring_err == 0) { - // Limit the number of io_uring workers - unsigned int values[] = { - [IO_WQ_BOUND] = nthreads, - [IO_WQ_UNBOUND] = 0, - }; - io_uring_register_iowq_max_workers(&thread->ring, values); - } - } -#endif - - if (thread_create(&thread->id, NULL, ioq_work, thread) != 0) { + if (ioq_thread_create(ioq, &ioq->threads[i]) != 0) { + ioq->nthreads = i; goto fail; } - ++ioq->nthreads; } return ioq; @@ -908,11 +948,7 @@ void ioq_destroy(struct ioq *ioq) { ioq_cancel(ioq); for (size_t i = 0; i < ioq->nthreads; ++i) { - struct ioq_thread *thread = &ioq->threads[i]; - thread_join(thread->id, NULL); -#if BFS_USE_LIBURING - io_uring_queue_exit(&thread->ring); -#endif + ioq_thread_join(&ioq->threads[i]); } ioqq_destroy(ioq->ready); -- cgit v1.2.3 From b3cdf12d71798a26c1f6d46dceff7ed50b9eed83 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 14 Feb 2024 16:29:12 -0500 Subject: ioq: Don't use the symbolic IO_WQ_[UN]BOUND indices They are only available since liburing 2.2, which is newer than CI. --- src/ioq.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 3c26814..0936adf 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -742,8 +742,8 @@ static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) { if (!prev) { // Limit the number of io_uring workers unsigned int values[] = { - [IO_WQ_BOUND] = ioq->nthreads, - [IO_WQ_UNBOUND] = 0, + ioq->nthreads, // [IO_WQ_BOUND] + 0, // [IO_WQ_UNBOUND] }; io_uring_register_iowq_max_workers(&thread->ring, values); } -- cgit v1.2.3 From 08e23800dcccc0bc302dabc18ba5f5b8f78c846d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 15 Feb 2024 12:43:27 -0500 Subject: ioq: Add a missing close() if bfs_opendir() fails --- src/ioq.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/ioq.c b/src/ioq.c index 0936adf..2558d62 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -623,6 +623,8 @@ static void ioq_reap_cqe(struct ioq_ring_state *state, struct io_uring_cqe *cqe) if (ent->result >= 0) { // TODO: io_uring_prep_getdents() bfs_polldir(args->dir); + } else { + xclose(fd); } break; -- cgit v1.2.3 From 71cffe72d83dd3ba31198d66a97cea83ba6b352e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 15 Feb 2024 12:46:23 -0500 Subject: ioq: Don't push immediately in ioq_check_cancel() --- src/ioq.c | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 2558d62..b57daba 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -446,7 +446,6 @@ static bool ioq_check_cancel(struct ioq *ioq, struct ioq_ent *ent) { } ent->result = -EINTR; - ioqq_push(ioq->ready, ent); return true; } @@ -481,12 +480,6 @@ static void ioq_dispatch_sync(struct ioq *ioq, struct ioq_ent *ent) { ent->result = -ENOSYS; } -/** Complete a single request synchronously. */ -static void ioq_complete(struct ioq *ioq, struct ioq_ent *ent) { - ioq_dispatch_sync(ioq, ent); - ioqq_push(ioq->ready, ent); -} - #if BFS_USE_LIBURING /** io_uring worker state. */ @@ -568,6 +561,7 @@ static struct io_uring_sqe *ioq_dispatch_async(struct io_uring *ring, struct ioq static void ioq_prep_sqe(struct ioq_ring_state *state, struct ioq_ent *ent) { struct ioq *ioq = state->ioq; if (ioq_check_cancel(ioq, ent)) { + ioqq_push(ioq->ready, ent); return; } @@ -576,7 +570,8 @@ static void ioq_prep_sqe(struct ioq_ring_state *state, struct ioq_ent *ent) { io_uring_sqe_set_data(sqe, ent); ++state->prepped; } else { - ioq_complete(ioq, ent); + ioq_dispatch_sync(ioq, ent); + ioqq_push(ioq->ready, ent); } } @@ -615,7 +610,7 @@ static void ioq_reap_cqe(struct ioq_ring_state *state, struct io_uring_cqe *cqe) int fd = ent->result; if (ioq_check_cancel(ioq, ent)) { xclose(fd); - return; + goto push; } struct ioq_opendir *args = &ent->opendir; @@ -694,8 +689,9 @@ static void ioq_sync_work(struct ioq_thread *thread) { } if (!ioq_check_cancel(ioq, ent)) { - ioq_complete(ioq, ent); + ioq_dispatch_sync(ioq, ent); } + ioqq_push(ioq->ready, ent); } } -- cgit v1.2.3 From 9c40099611099c567d26c7a8b2782fa9a47d6849 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 15 Feb 2024 12:53:31 -0500 Subject: ioq: Add batched ioqq_push/pop operations --- src/ioq.c | 176 +++++++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 127 insertions(+), 49 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index b57daba..cf0b927 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -339,17 +339,6 @@ static bool ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent return !(prev & IOQ_SKIP); } -/** Push an entry onto the queue. */ -static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { - while (true) { - size_t i = fetch_add(&ioqq->head, 1, relaxed); - ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; - if (ioq_slot_push(ioqq, slot, ent)) { - break; - } - } -} - /** (Try to) pop an entry from a slot. */ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool block) { uintptr_t prev = load(slot, relaxed); @@ -383,6 +372,32 @@ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool bloc return (struct ioq_ent *)(prev << 1); } +/** Push an entry onto the queue. */ +static void ioqq_push(struct ioqq *ioqq, struct ioq_ent *ent) { + while (true) { + size_t i = fetch_add(&ioqq->head, 1, relaxed); + ioq_slot *slot = &ioqq->slots[i & ioqq->slot_mask]; + if (ioq_slot_push(ioqq, slot, ent)) { + break; + } + } +} + +/** Push a batch of entries to the queue. */ +static void ioqq_push_batch(struct ioqq *ioqq, struct ioq_ent *batch[], size_t size) { + size_t mask = ioqq->slot_mask; + do { + size_t i = fetch_add(&ioqq->head, size, relaxed); + for (size_t j = i + size; i != j; ++i) { + ioq_slot *slot = &ioqq->slots[i & mask]; + if (ioq_slot_push(ioqq, slot, *batch)) { + ++batch; + --size; + } + } + } while (size > 0); +} + /** Pop an entry from the queue. */ static struct ioq_ent *ioqq_pop(struct ioqq *ioqq, bool block) { size_t i = fetch_add(&ioqq->tail, 1, relaxed); @@ -390,6 +405,47 @@ static struct ioq_ent *ioqq_pop(struct ioqq *ioqq, bool block) { return ioq_slot_pop(ioqq, slot, block); } +/** Pop a batch of entries from the queue. */ +static void ioqq_pop_batch(struct ioqq *ioqq, struct ioq_ent *batch[], size_t size, bool block) { + size_t mask = ioqq->slot_mask; + size_t i = fetch_add(&ioqq->tail, size, relaxed); + for (size_t j = i + size; i != j; ++i) { + ioq_slot *slot = &ioqq->slots[i & mask]; + *batch++ = ioq_slot_pop(ioqq, slot, block); + block = false; + } +} + +/** Use cache-line-sized batches. */ +#define IOQ_BATCH (FALSE_SHARING_SIZE / sizeof(ioq_slot)) + +/** + * A batch of entries to send all at once. + */ +struct ioq_batch { + /** The current batch size. */ + size_t size; + /** The array of entries. */ + struct ioq_ent *entries[IOQ_BATCH]; +}; + +/** Send the batch to a queue. */ +static void ioq_batch_flush(struct ioqq *ioqq, struct ioq_batch *batch) { + if (batch->size > 0) { + ioqq_push_batch(ioqq, batch->entries, batch->size); + batch->size = 0; + } +} + +/** An an entry to a batch, flushing if necessary. */ +static void ioq_batch_push(struct ioqq *ioqq, struct ioq_batch *batch, struct ioq_ent *ent) { + if (batch->size >= IOQ_BATCH) { + ioq_batch_flush(ioqq, batch); + } + + batch->entries[batch->size++] = ent; +} + /** Sentinel stop command. */ static struct ioq_ent IOQ_STOP; @@ -494,28 +550,10 @@ struct ioq_ring_state { size_t submitted; /** Whether to stop the loop. */ bool stop; + /** A batch of ready entries. */ + struct ioq_batch ready; }; -/** Pop a request for ioq_ring_prep(). */ -static struct ioq_ent *ioq_ring_pop(struct ioq_ring_state *state) { - if (state->stop) { - return NULL; - } - - // Block if we have nothing else to do - bool block = !state->prepped && !state->submitted; - struct ioqq *pending = state->ioq->pending; - struct ioq_ent *ret = ioqq_pop(pending, block); - - if (ret == &IOQ_STOP) { - ioqq_push(pending, &IOQ_STOP); - state->stop = true; - ret = NULL; - } - - return ret; -} - /** Dispatch a single request asynchronously. */ static struct io_uring_sqe *ioq_dispatch_async(struct io_uring *ring, struct ioq_ent *ent) { struct io_uring_sqe *sqe = NULL; @@ -557,11 +595,16 @@ static struct io_uring_sqe *ioq_dispatch_async(struct io_uring *ring, struct ioq return NULL; } +/** Check if ioq_ring_reap() has work to do. */ +static bool ioq_ring_empty(struct ioq_ring_state *state) { + return !state->prepped && !state->submitted && !state->ready.size; +} + /** Prep a single SQE. */ static void ioq_prep_sqe(struct ioq_ring_state *state, struct ioq_ent *ent) { struct ioq *ioq = state->ioq; if (ioq_check_cancel(ioq, ent)) { - ioqq_push(ioq->ready, ent); + ioq_batch_push(ioq->ready, &state->ready, ent); return; } @@ -571,24 +614,44 @@ static void ioq_prep_sqe(struct ioq_ring_state *state, struct ioq_ent *ent) { ++state->prepped; } else { ioq_dispatch_sync(ioq, ent); - ioqq_push(ioq->ready, ent); + ioq_batch_push(ioq->ready, &state->ready, ent); } } /** Prep a batch of SQEs. */ static bool ioq_ring_prep(struct ioq_ring_state *state) { + if (state->stop) { + return false; + } + + struct ioq *ioq = state->ioq; struct io_uring *ring = state->ring; + struct ioq_ent *pending[IOQ_BATCH]; + + while (io_uring_sq_space_left(ring) >= IOQ_BATCH) { + bool block = ioq_ring_empty(state); + ioqq_pop_batch(ioq->pending, pending, IOQ_BATCH, block); + + bool any = false; + for (size_t i = 0; i < IOQ_BATCH; ++i) { + struct ioq_ent *ent = pending[i]; + if (ent == &IOQ_STOP) { + ioqq_push(ioq->pending, &IOQ_STOP); + state->stop = true; + goto done; + } else if (ent) { + ioq_prep_sqe(state, ent); + any = true; + } + } - while (io_uring_sq_space_left(ring)) { - struct ioq_ent *ent = ioq_ring_pop(state); - if (!ent) { + if (!any) { break; } - - ioq_prep_sqe(state, ent); } - return state->prepped || state->submitted; +done: + return !ioq_ring_empty(state); } /** Reap a single CQE. */ @@ -638,11 +701,12 @@ static void ioq_reap_cqe(struct ioq_ring_state *state, struct io_uring_cqe *cqe) } push: - ioqq_push(ioq->ready, ent); + ioq_batch_push(ioq->ready, &state->ready, ent); } /** Reap a batch of CQEs. */ static void ioq_ring_reap(struct ioq_ring_state *state) { + struct ioq *ioq = state->ioq; struct io_uring *ring = state->ring; while (state->prepped) { @@ -661,6 +725,8 @@ static void ioq_ring_reap(struct ioq_ring_state *state) { ioq_reap_cqe(state, cqe); } + + ioq_batch_flush(ioq->ready, &state->ready); } /** io_uring worker loop. */ @@ -681,17 +747,29 @@ static void ioq_ring_work(struct ioq_thread *thread) { static void ioq_sync_work(struct ioq_thread *thread) { struct ioq *ioq = thread->parent; - while (true) { - struct ioq_ent *ent = ioqq_pop(ioq->pending, true); - if (ent == &IOQ_STOP) { - ioqq_push(ioq->pending, &IOQ_STOP); - break; + bool stop = false; + while (!stop) { + struct ioq_ent *pending[IOQ_BATCH]; + ioqq_pop_batch(ioq->pending, pending, IOQ_BATCH, true); + + struct ioq_batch ready; + ready.size = 0; + + for (size_t i = 0; i < IOQ_BATCH; ++i) { + struct ioq_ent *ent = pending[i]; + if (ent == &IOQ_STOP) { + ioqq_push(ioq->pending, &IOQ_STOP); + stop = true; + break; + } else if (ent) { + if (!ioq_check_cancel(ioq, ent)) { + ioq_dispatch_sync(ioq, ent); + } + ioq_batch_push(ioq->ready, &ready, ent); + } } - if (!ioq_check_cancel(ioq, ent)) { - ioq_dispatch_sync(ioq, ent); - } - ioqq_push(ioq->ready, ent); + ioq_batch_flush(ioq->ready, &ready); } } -- cgit v1.2.3 From 60fb65a75147a0d703842b412effdf8ca6ae2169 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Feb 2024 08:43:44 -0500 Subject: Release 3.1.1 --- GNUmakefile | 2 +- docs/CHANGELOG.md | 12 ++++++++++++ src/config.h | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 8b1f848..b0314d8 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 3.1 +VERSION := 3.1.1 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b70ed32..3255495 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,18 @@ 3.* === +3.1.1 +----- + +**February 16, 2024** + +### Changes + +- Performance and scalability improvements + +- The file count in `bfs -status` now has a thousands separator + + 3.1 --- diff --git a/src/config.h b/src/config.h index 519a454..41a8d5f 100644 --- a/src/config.h +++ b/src/config.h @@ -30,7 +30,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "3.1" +# define BFS_VERSION "3.1.1" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From c749c11b04444ca40941dd2ddc5802faed148f6a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 16 Feb 2024 13:44:04 -0500 Subject: ioq: Ensure ioq_ent is sufficiently aligned The natural alignment of struct ioq_ent is only 2 on m68k, so over-align it to at least 4 bytes on all platforms. Link: https://buildd.debian.org/status/fetch.php?pkg=bfs&arch=m68k&ver=3.1-1&stamp=1707699583 --- src/ioq.c | 2 +- src/ioq.h | 9 ++++++++- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index cf0b927..f23b62f 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -177,7 +177,7 @@ typedef atomic uintptr_t ioq_slot; #define IOQ_SKIP_ONE (~IOQ_BLOCKED) // Need room for two flag bits -bfs_static_assert(alignof(struct ioq_ent) > 2); +bfs_static_assert(alignof(struct ioq_ent) >= (1 << 2)); /** * An MPMC queue of I/O commands. diff --git a/src/ioq.h b/src/ioq.h index 30e90e0..818eea6 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -32,12 +32,19 @@ enum ioq_op { IOQ_STAT, }; +/** + * The I/O queue implementation needs two tag bits in each pointer to a struct + * ioq_ent, so we need to ensure at least 4-byte alignment. The natural + * alignment is enough on most architectures, but not m68k, so over-align it. + */ +#define IOQ_ENT_ALIGN alignas(4) + /** * An I/O queue entry. */ struct ioq_ent { /** The I/O operation. */ - enum ioq_op op; + IOQ_ENT_ALIGN enum ioq_op op; /** The return value (on success) or negative error code (on failure). */ int result; -- cgit v1.2.3 From ef28da1f876cb901f966dc01dd24ac861b3622ec Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 17 Feb 2024 09:33:06 -0500 Subject: tests: Redirect stdin in bfs_pty() Otherwise bfs will think it's interactive. --- tests/run.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/run.sh b/tests/run.sh index ccd2ac1..720515d 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -386,7 +386,7 @@ bfs_pty() { bfs_verbose "$@" local ret=0 - "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@"' bash "${BFS[@]}" "$@" || ret=$? + "$UNBUFFER" bash -c 'stty cols 80 rows 24 && "$@" 125)); then exit $ret -- cgit v1.2.3 From b83343fa0dba85dc7e8d357ba7aaeca991315e96 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 18 Feb 2024 21:45:12 -0500 Subject: ioq: Remove some branches from ioq_slot_{push,pop}() --- src/ioq.c | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index f23b62f..74e2587 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -171,8 +171,11 @@ typedef atomic uintptr_t ioq_slot; /** Someone might be waiting on this slot. */ #define IOQ_BLOCKED ((uintptr_t)1) + +/** Bit for IOQ_SKIP. */ +#define IOQ_SKIP_BIT (UINTPTR_WIDTH - 1) /** The next push(es) should skip this slot. */ -#define IOQ_SKIP ((uintptr_t)1 << (UINTPTR_WIDTH - 1)) +#define IOQ_SKIP ((uintptr_t)1 << IOQ_SKIP_BIT) /** Amount to add for an additional skip. */ #define IOQ_SKIP_ONE (~IOQ_BLOCKED) @@ -309,24 +312,30 @@ static void ioq_slot_wake(struct ioqq *ioqq, ioq_slot *slot) { cond_broadcast(&monitor->cond); } +/** Branch-free (slot & IOQ_SKIP) ? ~IOQ_BLOCKED : 0 */ +static uintptr_t ioq_skip_mask(uintptr_t slot) { + return -(slot >> IOQ_SKIP_BIT) << 1; +} + /** Push an entry into a slot. */ static bool ioq_slot_push(struct ioqq *ioqq, ioq_slot *slot, struct ioq_ent *ent) { uintptr_t prev = load(slot, relaxed); + while (true) { - uintptr_t next; - if (prev & IOQ_SKIP) { - // skip(1) → empty - // skip(n) → skip(n - 1) - next = (prev - IOQ_SKIP_ONE) & ~IOQ_BLOCKED; - } else if (prev > IOQ_BLOCKED) { + size_t skip_mask = ioq_skip_mask(prev); + size_t full_mask = ~skip_mask & ~IOQ_BLOCKED; + if (prev & full_mask) { // full(ptr) → wait prev = ioq_slot_wait(ioqq, slot, prev); continue; - } else { - // empty → full(ptr) - next = (uintptr_t)ent >> 1; } + // empty → full(ptr) + uintptr_t next = ((uintptr_t)ent >> 1) & full_mask; + // skip(1) → empty + // skip(n) → skip(n - 1) + next |= (prev - IOQ_SKIP_ONE) & skip_mask; + if (compare_exchange_weak(slot, &prev, next, release, relaxed)) { break; } @@ -349,7 +358,7 @@ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool bloc uintptr_t next = prev + IOQ_SKIP_ONE; // skip(n) → ~IOQ_BLOCKED // full(ptr) → 0 - next &= (next & IOQ_SKIP) ? ~IOQ_BLOCKED : 0; + next &= ioq_skip_mask(next); if (block && next) { prev = ioq_slot_wait(ioqq, slot, prev); @@ -368,7 +377,7 @@ static struct ioq_ent *ioq_slot_pop(struct ioqq *ioqq, ioq_slot *slot, bool bloc // empty → 0 // skip(n) → 0 // full(ptr) → ptr - prev &= (prev & IOQ_SKIP) ? 0 : ~IOQ_BLOCKED; + prev &= ioq_skip_mask(~prev); return (struct ioq_ent *)(prev << 1); } -- cgit v1.2.3 From 8bc72d6c20c5e38783c4956c4d9fde9b3ee9140c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 28 Feb 2024 11:30:20 -0500 Subject: ioq: Probe for supported io_uring operations --- src/ioq.c | 85 +++++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 74e2587..f71ee6e 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -458,6 +458,17 @@ 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 +/** + * Supported io_uring operations. + */ +enum ioq_ring_ops { + IOQ_RING_OPENAT = 1 << 0, + IOQ_RING_CLOSE = 1 << 1, + IOQ_RING_STATX = 1 << 2, +}; +#endif + /** I/O queue thread-specific data. */ struct ioq_thread { /** The thread handle. */ @@ -470,6 +481,8 @@ struct ioq_thread { struct io_uring ring; /** Any error that occurred initializing the ring. */ int ring_err; + /** Bitmask of supported io_uring operations. */ + enum ioq_ring_ops ring_ops; #endif }; @@ -553,6 +566,8 @@ struct ioq_ring_state { struct ioq *ioq; /** The io_uring. */ struct io_uring *ring; + /** Supported io_uring operations. */ + enum ioq_ring_ops ops; /** Number of prepped, unsubmitted SQEs. */ size_t prepped; /** Number of submitted, unreaped SQEs. */ @@ -564,40 +579,48 @@ struct ioq_ring_state { }; /** Dispatch a single request asynchronously. */ -static struct io_uring_sqe *ioq_dispatch_async(struct io_uring *ring, struct ioq_ent *ent) { +static struct io_uring_sqe *ioq_dispatch_async(struct ioq_ring_state *state, struct ioq_ent *ent) { + struct io_uring *ring = state->ring; + enum ioq_ring_ops ops = state->ops; struct io_uring_sqe *sqe = NULL; switch (ent->op) { - case IOQ_CLOSE: + case IOQ_CLOSE: + if (ops & IOQ_RING_CLOSE) { sqe = io_uring_get_sqe(ring); io_uring_prep_close(sqe, ent->close.fd); - return sqe; + } + return sqe; - case IOQ_OPENDIR: { + case IOQ_OPENDIR: + if (ops & IOQ_RING_OPENAT) { sqe = io_uring_get_sqe(ring); struct ioq_opendir *args = &ent->opendir; int flags = O_RDONLY | O_CLOEXEC | O_DIRECTORY; io_uring_prep_openat(sqe, args->dfd, args->path, flags, 0); - return sqe; } + return sqe; - case IOQ_CLOSEDIR: + case IOQ_CLOSEDIR: #if BFS_USE_UNWRAPDIR + if (ops & IOQ_RING_CLOSE) { sqe = io_uring_get_sqe(ring); io_uring_prep_close(sqe, bfs_unwrapdir(ent->closedir.dir)); + } #endif - return sqe; + return sqe; - case IOQ_STAT: { + case IOQ_STAT: #if BFS_USE_STATX + if (ops & IOQ_RING_STATX) { sqe = io_uring_get_sqe(ring); struct ioq_stat *args = &ent->stat; int flags = bfs_statx_flags(args->flags); unsigned int mask = STATX_BASIC_STATS | STATX_BTIME; io_uring_prep_statx(sqe, args->dfd, args->path, flags, mask, args->xbuf); -#endif - return sqe; } +#endif + return sqe; } bfs_bug("Unknown ioq_op %d", (int)ent->op); @@ -617,7 +640,7 @@ static void ioq_prep_sqe(struct ioq_ring_state *state, struct ioq_ent *ent) { return; } - struct io_uring_sqe *sqe = ioq_dispatch_async(state->ring, ent); + struct io_uring_sqe *sqe = ioq_dispatch_async(state, ent); if (sqe) { io_uring_sqe_set_data(sqe, ent); ++state->prepped; @@ -743,6 +766,7 @@ static void ioq_ring_work(struct ioq_thread *thread) { struct ioq_ring_state state = { .ioq = thread->parent, .ring = &thread->ring, + .ops = thread->ring_ops, }; while (ioq_ring_prep(&state)) { @@ -824,14 +848,39 @@ static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) { return -1; } - if (!prev) { - // Limit the number of io_uring workers - unsigned int values[] = { - ioq->nthreads, // [IO_WQ_BOUND] - 0, // [IO_WQ_UNBOUND] - }; - io_uring_register_iowq_max_workers(&thread->ring, values); + if (prev) { + // Initial setup already complete + return 0; + } + + // Check for supported operations + struct io_uring_probe *probe = io_uring_get_probe_ring(&thread->ring); + if (probe) { + if (io_uring_opcode_supported(probe, IORING_OP_OPENAT)) { + thread->ring_ops |= IOQ_RING_OPENAT; + } + if (io_uring_opcode_supported(probe, IORING_OP_CLOSE)) { + thread->ring_ops |= IOQ_RING_CLOSE; + } +#if BFS_USE_STATX + if (io_uring_opcode_supported(probe, IORING_OP_STATX)) { + thread->ring_ops |= IOQ_RING_STATX; + } +#endif + io_uring_free_probe(probe); } + if (!thread->ring_ops) { + io_uring_queue_exit(&thread->ring); + thread->ring_err = ENOTSUP; + return -1; + } + + // Limit the number of io_uring workers + unsigned int values[] = { + ioq->nthreads, // [IO_WQ_BOUND] + 0, // [IO_WQ_UNBOUND] + }; + io_uring_register_iowq_max_workers(&thread->ring, values); #endif return 0; -- cgit v1.2.3 From 39ce13d45c5881d84e2de98243052811e3648224 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 28 Feb 2024 22:10:06 -0500 Subject: tests/bfs/D_opt: Don't rely on directory link counts Fixes: https://github.com/tavianator/bfs/issues/131 --- tests/bfs/D_opt.out | 11 ----------- tests/bfs/D_opt.sh | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) diff --git a/tests/bfs/D_opt.out b/tests/bfs/D_opt.out index 3b461cf..6218a0c 100644 --- a/tests/bfs/D_opt.out +++ b/tests/bfs/D_opt.out @@ -1,18 +1,7 @@ basic/a basic/b -basic/c basic/c/d -basic/e basic/e/f -basic/g -basic/g/h -basic/i -basic/j basic/j/foo -basic/k -basic/k/foo basic/k/foo/bar -basic/l -basic/l/foo -basic/l/foo/bar basic/l/foo/bar/baz diff --git a/tests/bfs/D_opt.sh b/tests/bfs/D_opt.sh index d95cf86..c14fe70 100644 --- a/tests/bfs/D_opt.sh +++ b/tests/bfs/D_opt.sh @@ -1 +1 @@ -bfs_diff -D opt -nohidden -not \( -type b -o -type c \) -links -5 -links -10 -not -hidden basic +bfs_diff -D opt -nohidden -not \( -type c -o -type d \) -links -5 -links -10 -not -hidden basic -- cgit v1.2.3 From f0418655db6a344afd5c26efd04a4e4d87128233 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 28 Feb 2024 12:31:11 -0500 Subject: tests/ioq: New unit test --- GNUmakefile | 1 + tests/ioq.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/main.c | 1 + tests/tests.h | 3 +++ 4 files changed, 78 insertions(+) create mode 100644 tests/ioq.c diff --git a/GNUmakefile b/GNUmakefile index b0314d8..852621e 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -283,6 +283,7 @@ $(BIN)/tests/units: \ $(OBJ)/tests/alloc.o \ $(OBJ)/tests/bfstd.o \ $(OBJ)/tests/bit.o \ + $(OBJ)/tests/ioq.o \ $(OBJ)/tests/main.o \ $(OBJ)/tests/trie.o \ $(OBJ)/tests/xtime.o \ diff --git a/tests/ioq.c b/tests/ioq.c new file mode 100644 index 0000000..3d35650 --- /dev/null +++ b/tests/ioq.c @@ -0,0 +1,73 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "tests.h" +#include "../src/ioq.h" +#include "../src/bfstd.h" +#include "../src/diag.h" +#include "../src/dir.h" +#include +#include + +/** + * Test for blocking within ioq_slot_push(). + * + * struct ioqq only supports non-blocking reads; if a write encounters a full + * slot, it must block until someone pops from that slot: + * + * Reader Writer + * ────────────────────────── ───────────────────────── + * tail: 0 → 1 + * slots[0]: empty → full + * tail: 1 → 0 + * slots[1]: empty → full + * tail: 0 → 1 + * slots[0]: full → full* (IOQ_BLOCKED) + * ioq_slot_wait() ... + * head: 0 → 1 + * slots[0]: full* → empty + * ioq_slot_wake() + * ... + * slots[0]: empty → full + * + * To reproduce this unlikely scenario, we must fill up the ready queue, then + * call ioq_cancel() which pushes an additional sentinel IOQ_STOP operation. + */ +static void check_ioq_push_block(void) { + // Must be a power of two to fill the entire queue + const size_t depth = 2; + + struct ioq *ioq = ioq_create(depth, 1); + bfs_verify(ioq, "ioq_create(): %s", xstrerror(errno)); + + // Push enough operations to fill the queue + for (size_t i = 0; i < depth; ++i) { + struct bfs_dir *dir = bfs_allocdir(); + bfs_verify(dir, "bfs_allocdir(): %s", xstrerror(errno)); + + int ret = ioq_opendir(ioq, dir, AT_FDCWD, ".", 0, NULL); + bfs_verify(ret == 0, "ioq_opendir(): %s", xstrerror(errno)); + } + bfs_verify(ioq_capacity(ioq) == 0); + + // Now cancel the queue, pushing an additional IOQ_STOP message + ioq_cancel(ioq); + + // Drain the queue + struct ioq_ent *ent; + while ((ent = ioq_pop(ioq, true))) { + bfs_verify(ent->op == IOQ_OPENDIR); + if (ent->result >= 0) { + bfs_closedir(ent->opendir.dir); + } + free(ent->opendir.dir); + ioq_free(ioq, ent); + } + + ioq_destroy(ioq); +} + +bool check_ioq(void) { + check_ioq_push_block(); + return true; +} diff --git a/tests/main.c b/tests/main.c index b7292b4..e69fe35 100644 --- a/tests/main.c +++ b/tests/main.c @@ -98,6 +98,7 @@ int main(int argc, char *argv[]) { run_test(&ctx, "alloc", check_alloc); run_test(&ctx, "bfstd", check_bfstd); run_test(&ctx, "bit", check_bit); + run_test(&ctx, "ioq", check_ioq); run_test(&ctx, "trie", check_trie); run_test(&ctx, "xtime", check_xtime); diff --git a/tests/tests.h b/tests/tests.h index d2f3611..d8adbb9 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -22,6 +22,9 @@ bool check_bfstd(void); /** Bit manipulation tests. */ bool check_bit(void); +/** I/O queue tests. */ +bool check_ioq(void); + /** Trie tests. */ bool check_trie(void); -- cgit v1.2.3 From c8ab9926132085f2daf6b949cafda43378d50fb5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 28 Feb 2024 13:00:54 -0500 Subject: stat: Use errno_is_like(ENOSYS) for EPERM kludge --- src/bfstd.c | 4 ++++ src/stat.c | 4 +--- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index d19049f..ce4aa49 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -45,6 +45,10 @@ bool error_is_like(int error, int category) { case ENOENT: return error == ENOTDIR; + case ENOSYS: + // https://github.com/opencontainers/runc/issues/2151 + return errno == EPERM; + #if __DragonFly__ // https://twitter.com/tavianator/status/1742991411203485713 case ENAMETOOLONG: diff --git a/src/stat.c b/src/stat.c index 36222cb..2f2743b 100644 --- a/src/stat.c +++ b/src/stat.c @@ -253,9 +253,7 @@ static int bfs_stat_explicit(int at_fd, const char *at_path, int at_flags, struc if (load(&has_statx, relaxed)) { int ret = bfs_statx_impl(at_fd, at_path, at_flags, buf); - // EPERM is commonly returned in a seccomp() sandbox that does - // not allow statx() - if (ret != 0 && (errno == ENOSYS || errno == EPERM)) { + if (ret != 0 && errno_is_like(ENOSYS)) { store(&has_statx, false, relaxed); } else { return ret; -- cgit v1.2.3 From a9f3cde30426b546ba6e3172e1a7951213a72049 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 28 Feb 2024 15:43:44 -0500 Subject: xtime: Fix some xgetdate() bugs And add some more test cases. --- src/xtime.c | 33 +++++++++++++------ tests/xtime.c | 102 ++++++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 117 insertions(+), 18 deletions(-) diff --git a/src/xtime.c b/src/xtime.c index e90bdb1..4309289 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -174,19 +174,29 @@ overflow: return -1; } +/** Parse a decimal digit. */ +static int xgetdigit(char c) { + int ret = c - '0'; + if (ret < 0 || ret > 9) { + return -1; + } else { + return ret; + } +} + /** Parse some digits from a timestamp. */ static int xgetpart(const char **str, size_t n, int *result) { - char buf[n + 1]; + *result = 0; + for (size_t i = 0; i < n; ++i, ++*str) { - char c = **str; - if (c < '0' || c > '9') { + int dig = xgetdigit(**str); + if (dig < 0) { return -1; } - buf[i] = c; + *result *= 10; + *result += dig; } - buf[n] = '\0'; - *result = atoi(buf); return 0; } @@ -239,6 +249,8 @@ int xgetdate(const char *str, struct timespec *result) { goto end; } else if (*str == ':') { ++str; + } else if (xgetdigit(*str) < 0) { + goto zone; } if (xgetpart(&str, 2, &tm.tm_min) != 0) { goto invalid; @@ -249,11 +261,14 @@ int xgetdate(const char *str, struct timespec *result) { goto end; } else if (*str == ':') { ++str; + } else if (xgetdigit(*str) < 0) { + goto zone; } if (xgetpart(&str, 2, &tm.tm_sec) != 0) { goto invalid; } +zone: if (!*str) { goto end; } else if (*str == 'Z') { @@ -296,11 +311,11 @@ end: goto error; } - int offset = 60 * tz_hour + tz_min; + int offset = (tz_hour * 60 + tz_min) * 60; if (tz_negative) { - result->tv_sec -= offset; - } else { result->tv_sec += offset; + } else { + result->tv_sec -= offset; } } diff --git a/tests/xtime.c b/tests/xtime.c index 53ecbc4..c2ee8f3 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -3,7 +3,9 @@ #include "tests.h" #include "../src/xtime.h" +#include "../src/bfstd.h" #include "../src/config.h" +#include #include #include #include @@ -49,22 +51,92 @@ static void tm_print(FILE *file, const struct tm *tm) { tm->tm_isdst ? (tm->tm_isdst < 0 ? " (DST?)" : " (DST)") : ""); } -bool check_xtime(void) { - if (setenv("TZ", "UTC0", true) != 0) { - perror("setenv()"); +/** Check one xgetdate() result. */ +static bool compare_xgetdate(const char *str, int error, time_t expected) { + struct timespec ts; + int ret = xgetdate(str, &ts); + + if (error) { + if (ret != -1 || errno != error) { + fprintf(stderr, "xgetdate('%s'): %s\n", str, xstrerror(errno)); + return false; + } + } else if (ret != 0) { + fprintf(stderr, "xgetdate('%s'): %s\n", str, xstrerror(errno)); + return false; + } else if (ts.tv_sec != expected || ts.tv_nsec != 0) { + fprintf(stderr, "xgetdate('%s'): %jd.%09jd != %jd\n", str, (intmax_t)ts.tv_sec, (intmax_t)ts.tv_nsec, (intmax_t)expected); return false; } + return true; +} + +/** xgetdate() tests. */ +static bool check_xgetdate(void) { + return compare_xgetdate("", EINVAL, 0) + & compare_xgetdate("????", EINVAL, 0) + & compare_xgetdate("1991", EINVAL, 0) + & compare_xgetdate("1991-??", EINVAL, 0) + & compare_xgetdate("1991-12", EINVAL, 0) + & compare_xgetdate("1991-12-", EINVAL, 0) + & compare_xgetdate("1991-12-??", EINVAL, 0) + & compare_xgetdate("1991-12-14", 0, 692668800) + & compare_xgetdate("1991-12-14-", EINVAL, 0) + & compare_xgetdate("1991-12-14T", EINVAL, 0) + & compare_xgetdate("1991-12-14T??", EINVAL, 0) + & compare_xgetdate("1991-12-14T10", 0, 692704800) + & compare_xgetdate("1991-12-14T10:??", EINVAL, 0) + & compare_xgetdate("1991-12-14T10:11", 0, 692705460) + & compare_xgetdate("1991-12-14T10:11:??", EINVAL, 0) + & compare_xgetdate("1991-12-14T10:11:12", 0, 692705472) + & compare_xgetdate("1991-12-14T10Z", 0, 692704800) + & compare_xgetdate("1991-12-14T10:11Z", 0, 692705460) + & compare_xgetdate("1991-12-14T10:11:12Z", 0, 692705472) + & compare_xgetdate("1991-12-14T10:11:12?", EINVAL, 0) + & compare_xgetdate("1991-12-14T02-08", 0, 692704800) + & compare_xgetdate("1991-12-14T06:41-03:30", 0, 692705460) + & compare_xgetdate("1991-12-14T02:11:12-08:00", 0, 692705472) + & compare_xgetdate("19911214 021112-0800", 0, 692705472); +} + +/** xmktime() tests. */ +static bool check_xmktime(void) { + for (time_t time = -10; time <= 10; ++time) { + struct tm tm; + if (xlocaltime(&time, &tm) != 0) { + perror("xlocaltime()"); + return false; + } + + time_t made; + if (xmktime(&tm, &made) != 0) { + perror("xmktime()"); + return false; + } + + if (time != made) { + fprintf(stderr, "Mismatch: %jd != %jd\n", (intmax_t)time, (intmax_t)made); + tm_print(stderr, &tm); + return false; + } + } + + return true; +} + +/** xtimegm() tests. */ +static bool check_xtimegm(void) { struct tm tm = { .tm_isdst = -1, }; - for (tm.tm_year = 10; tm.tm_year <= 200; tm.tm_year += 10) - for (tm.tm_mon = -3; tm.tm_mon <= 15; tm.tm_mon += 3) - for (tm.tm_mday = -31; tm.tm_mday <= 61; tm.tm_mday += 4) - for (tm.tm_hour = -1; tm.tm_hour <= 24; tm.tm_hour += 5) - for (tm.tm_min = -1; tm.tm_min <= 60; tm.tm_min += 31) - for (tm.tm_sec = -60; tm.tm_sec <= 120; tm.tm_sec += 5) { + for (tm.tm_year = 10; tm.tm_year <= 200; tm.tm_year += 10) + for (tm.tm_mon = -3; tm.tm_mon <= 15; tm.tm_mon += 3) + for (tm.tm_mday = -31; tm.tm_mday <= 61; tm.tm_mday += 4) + for (tm.tm_hour = -1; tm.tm_hour <= 24; tm.tm_hour += 5) + for (tm.tm_min = -1; tm.tm_min <= 60; tm.tm_min += 31) + for (tm.tm_sec = -60; tm.tm_sec <= 120; tm.tm_sec += 5) { struct tm tma = tm, tmb = tm; time_t ta, tb; ta = mktime(&tma); @@ -93,3 +165,15 @@ bool check_xtime(void) { return true; } + +bool check_xtime(void) { + if (setenv("TZ", "UTC0", true) != 0) { + perror("setenv()"); + return false; + } + tzset(); + + return check_xgetdate() + & check_xmktime() + & check_xtimegm(); +} -- cgit v1.2.3 From e6d80d04d6928c452b48eae717373bf6a943e355 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Feb 2024 10:12:43 -0500 Subject: tests: Add more datetime parsing integration tests --- tests/common/newermt.sh | 4 +++- tests/xtime.c | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/tests/common/newermt.sh b/tests/common/newermt.sh index 3c5be68..e816b29 100644 --- a/tests/common/newermt.sh +++ b/tests/common/newermt.sh @@ -1 +1,3 @@ -bfs_diff times -newermt 1991-12-14T00:01 +bfs_diff times -newermt 1991-12-14T00:01 \ + -newermt "1991-12-14 01:01+01:00" \ + -newermt "19911213 20:31:00-0330" diff --git a/tests/xtime.c b/tests/xtime.c index c2ee8f3..4a7d55c 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -94,10 +94,10 @@ static bool check_xgetdate(void) { & compare_xgetdate("1991-12-14T10:11Z", 0, 692705460) & compare_xgetdate("1991-12-14T10:11:12Z", 0, 692705472) & compare_xgetdate("1991-12-14T10:11:12?", EINVAL, 0) - & compare_xgetdate("1991-12-14T02-08", 0, 692704800) + & compare_xgetdate("1991-12-14T03-07", 0, 692704800) & compare_xgetdate("1991-12-14T06:41-03:30", 0, 692705460) - & compare_xgetdate("1991-12-14T02:11:12-08:00", 0, 692705472) - & compare_xgetdate("19911214 021112-0800", 0, 692705472); + & compare_xgetdate("1991-12-14T03:11:12-07:00", 0, 692705472) + & compare_xgetdate("19911214 031112-0700", 0, 692705472); } /** xmktime() tests. */ -- cgit v1.2.3 From a228788769d7e3c71154606609a13eafb03a5fc2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Feb 2024 13:16:14 -0500 Subject: diag: New bfs_diag() macro --- src/diag.c | 17 ++++++++++++++--- src/diag.h | 13 +++++++++++++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/diag.c b/src/diag.c index efa7ebd..656fa89 100644 --- a/src/diag.c +++ b/src/diag.c @@ -14,15 +14,26 @@ #include #include -noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { +/** bfs_diagf() implementation. */ +attr(printf(2, 0)) +static void bfs_vdiagf(const struct bfs_loc *loc, const char *format, va_list args) { fprintf(stderr, "%s: %s@%s:%d: ", xgetprogname(), loc->func, loc->file, loc->line); + vfprintf(stderr, format, args); + fprintf(stderr, "\n"); +} +void bfs_diagf(const struct bfs_loc *loc, const char *format, ...) { va_list args; va_start(args, format); - vfprintf(stderr, format, args); + bfs_vdiagf(loc, format, args); va_end(args); +} - fprintf(stderr, "\n"); +noreturn void bfs_abortf(const struct bfs_loc *loc, const char *format, ...) { + va_list args; + va_start(args, format); + bfs_vdiagf(loc, format, args); + va_end(args); abort(); } diff --git a/src/diag.h b/src/diag.h index 791c065..4054c48 100644 --- a/src/diag.h +++ b/src/diag.h @@ -41,6 +41,19 @@ struct bfs_loc { # define bfs_location() (&(const struct bfs_loc)BFS_LOC_INIT) #endif +/** + * Print a low-level diagnostic message to standard error, formatted like + * + * bfs: func@src/file.c:0: Message + */ +attr(printf(2, 3)) +void bfs_diagf(const struct bfs_loc *loc, const char *format, ...); + +/** + * Unconditional diagnostic message. + */ +#define bfs_diag(...) bfs_diagf(bfs_location(), __VA_ARGS__) + /** * Print a message to standard error and abort. */ -- cgit v1.2.3 From 4e2f094d29ff8140f2b46a059128c780560db0f1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Feb 2024 13:23:02 -0500 Subject: tests: New bfs_check() macro We now report failures and continue, rather than aborting after the first failure. --- tests/alloc.c | 22 +++--- tests/bfstd.c | 65 +++++++++-------- tests/bit.c | 108 ++++++++++++++-------------- tests/tests.h | 20 ++++++ tests/trie.c | 46 ++++++------ tests/xtime.c | 220 ++++++++++++++++++++++++++-------------------------------- 6 files changed, 247 insertions(+), 234 deletions(-) diff --git a/tests/alloc.c b/tests/alloc.c index e14f131..4ce23d4 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -9,30 +9,32 @@ #include bool check_alloc(void) { + bool ret = true; + // Check sizeof_flex() struct flexible { alignas(64) int foo[8]; int bar[]; }; - bfs_verify(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible)); - bfs_verify(sizeof_flex(struct flexible, bar, 16) % alignof(struct flexible) == 0); + ret &= bfs_check(sizeof_flex(struct flexible, bar, 0) >= sizeof(struct flexible)); + ret &= bfs_check(sizeof_flex(struct flexible, bar, 16) % alignof(struct flexible) == 0); size_t too_many = SIZE_MAX / sizeof(int) + 1; - bfs_verify(sizeof_flex(struct flexible, bar, too_many) == align_floor(alignof(struct flexible), SIZE_MAX)); + ret &= bfs_check(sizeof_flex(struct flexible, bar, too_many) == align_floor(alignof(struct flexible), SIZE_MAX)); // Corner case: sizeof(type) > align_ceil(alignof(type), offsetof(type, member)) // Doesn't happen in typical ABIs - bfs_verify(flex_size(8, 16, 4, 4, 1) == 16); + ret &= bfs_check(flex_size(8, 16, 4, 4, 1) == 16); // Make sure we detect allocation size overflows #if __GNUC__ && !__clang__ # pragma GCC diagnostic ignored "-Walloc-size-larger-than=" #endif - bfs_verify(ALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); - bfs_verify(ZALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); - bfs_verify(ALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW); - bfs_verify(ZALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW); + ret &= bfs_check(ALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); + ret &= bfs_check(ZALLOC_ARRAY(int, too_many) == NULL && errno == EOVERFLOW); + ret &= bfs_check(ALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW); + ret &= bfs_check(ZALLOC_FLEX(struct flexible, bar, too_many) == NULL && errno == EOVERFLOW); // varena tests struct varena varena; @@ -41,9 +43,9 @@ bool check_alloc(void) { for (size_t i = 0; i < 256; ++i) { bfs_verify(varena_alloc(&varena, i)); struct arena *arena = &varena.arenas[varena.narenas - 1]; - bfs_verify(arena->size >= sizeof_flex(struct flexible, bar, i)); + ret &= bfs_check(arena->size >= sizeof_flex(struct flexible, bar, i)); } varena_destroy(&varena); - return true; + return ret; } diff --git a/tests/bfstd.c b/tests/bfstd.c index d385c6b..4d0ec23 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -14,61 +14,68 @@ #include /** Check the result of xdirname()/xbasename(). */ -static void check_base_dir(const char *path, const char *dir, const char *base) { +static bool check_base_dir(const char *path, const char *dir, const char *base) { + bool ret = true; + char *xdir = xdirname(path); bfs_verify(xdir, "xdirname(): %s", xstrerror(errno)); - bfs_verify(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir); + ret &= bfs_check(strcmp(xdir, dir) == 0, "xdirname('%s') == '%s' (!= '%s')", path, xdir, dir); free(xdir); char *xbase = xbasename(path); bfs_verify(xbase, "xbasename(): %s", xstrerror(errno)); - bfs_verify(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base); + ret &= bfs_check(strcmp(xbase, base) == 0, "xbasename('%s') == '%s' (!= '%s')", path, xbase, base); free(xbase); + + return ret; } /** Check the result of wordesc(). */ -static void check_wordesc(const char *str, const char *exp, enum wesc_flags flags) { +static bool check_wordesc(const char *str, const char *exp, enum wesc_flags flags) { char buf[256]; char *end = buf + sizeof(buf); - char *ret = wordesc(buf, end, str, flags); - bfs_verify(ret != end); - bfs_verify(strcmp(buf, exp) == 0, "wordesc(%s) == %s (!= %s)", str, buf, exp); + char *esc = wordesc(buf, end, str, flags); + + return bfs_check(esc != end) + && bfs_check(strcmp(buf, exp) == 0, "wordesc('%s') == '%s' (!= '%s')", str, buf, exp); } bool check_bfstd(void) { + bool ret = true; + // Try to set a UTF-8 locale if (!setlocale(LC_ALL, "C.UTF-8")) { setlocale(LC_ALL, ""); } // From man 3p basename - check_base_dir("usr", ".", "usr"); - check_base_dir("usr/", ".", "usr"); - check_base_dir("", ".", "."); - check_base_dir("/", "/", "/"); + ret &= check_base_dir("usr", ".", "usr"); + ret &= check_base_dir("usr/", ".", "usr"); + ret &= check_base_dir("", ".", "."); + ret &= check_base_dir("/", "/", "/"); // check_base_dir("//", "/" or "//", "/" or "//"); - check_base_dir("///", "/", "/"); - check_base_dir("/usr/", "/", "usr"); - check_base_dir("/usr/lib", "/usr", "lib"); - check_base_dir("//usr//lib//", "//usr", "lib"); - check_base_dir("/home//dwc//test", "/home//dwc", "test"); + ret &= check_base_dir("///", "/", "/"); + ret &= check_base_dir("/usr/", "/", "usr"); + ret &= check_base_dir("/usr/lib", "/usr", "lib"); + ret &= check_base_dir("//usr//lib//", "//usr", "lib"); + ret &= check_base_dir("/home//dwc//test", "/home//dwc", "test"); - check_wordesc("", "\"\"", WESC_SHELL); - check_wordesc("word", "word", WESC_SHELL); - check_wordesc("two words", "\"two words\"", WESC_SHELL); - check_wordesc("word's", "\"word's\"", WESC_SHELL); - check_wordesc("\"word\"", "'\"word\"'", WESC_SHELL); - check_wordesc("\"word's\"", "'\"word'\\''s\"'", WESC_SHELL); - check_wordesc("\033[1mbold's\033[0m", "$'\\e[1mbold\\'s\\e[0m'", WESC_SHELL | WESC_TTY); - check_wordesc("\x7F", "$'\\x7F'", WESC_SHELL | WESC_TTY); + ret &= check_wordesc("", "\"\"", WESC_SHELL); + ret &= check_wordesc("word", "word", WESC_SHELL); + ret &= check_wordesc("two words", "\"two words\"", WESC_SHELL); + ret &= check_wordesc("word's", "\"word's\"", WESC_SHELL); + ret &= check_wordesc("\"word\"", "'\"word\"'", WESC_SHELL); + ret &= check_wordesc("\"word's\"", "'\"word'\\''s\"'", WESC_SHELL); + ret &= check_wordesc("\033[1mbold's\033[0m", "$'\\e[1mbold\\'s\\e[0m'", WESC_SHELL | WESC_TTY); + ret &= check_wordesc("\x7F", "$'\\x7F'", WESC_SHELL | WESC_TTY); const char *charmap = nl_langinfo(CODESET); if (strcmp(charmap, "UTF-8") == 0) { - check_wordesc("\xF0", "$'\\xF0'", WESC_SHELL | WESC_TTY); - check_wordesc("\xF0\x9F", "$'\\xF0\\x9F'", WESC_SHELL | WESC_TTY); - check_wordesc("\xF0\x9F\x98", "$'\\xF0\\x9F\\x98'", WESC_SHELL | WESC_TTY); - check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY); + ret &= check_wordesc("\xF0", "$'\\xF0'", WESC_SHELL | WESC_TTY); + ret &= check_wordesc("\xF0\x9F", "$'\\xF0\\x9F'", WESC_SHELL | WESC_TTY); + ret &= check_wordesc("\xF0\x9F\x98", "$'\\xF0\\x9F\\x98'", WESC_SHELL | WESC_TTY); + ret &= check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY); } - return true; + return ret; } diff --git a/tests/bit.c b/tests/bit.c index 1c6a4de..b944748 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -52,75 +52,77 @@ bfs_static_assert(UINTMAX_MAX == UWIDTH_MAX(UINTMAX_WIDTH)); bfs_static_assert(INTMAX_MIN == IWIDTH_MIN(INTMAX_WIDTH)); bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); -#define verify_eq(a, b) \ - bfs_verify((a) == (b), "(0x%jX) %s != %s (0x%jX)", (uintmax_t)(a), #a, #b, (uintmax_t)(b)) +#define check_eq(a, b) \ + bfs_check((a) == (b), "(0x%jX) %s != %s (0x%jX)", (uintmax_t)(a), #a, #b, (uintmax_t)(b)) bool check_bit(void) { - verify_eq(bswap((uint8_t)0x12), 0x12); - verify_eq(bswap((uint16_t)0x1234), 0x3412); - verify_eq(bswap((uint32_t)0x12345678), 0x78563412); - verify_eq(bswap((uint64_t)0x1234567812345678), 0x7856341278563412); - - verify_eq(count_ones(0x0), 0); - verify_eq(count_ones(0x1), 1); - verify_eq(count_ones(0x2), 1); - verify_eq(count_ones(0x3), 2); - verify_eq(count_ones(0x137F), 10); - - verify_eq(count_zeros(0), INT_WIDTH); - verify_eq(count_zeros(0L), LONG_WIDTH); - verify_eq(count_zeros(0LL), LLONG_WIDTH); - verify_eq(count_zeros((uint8_t)0), 8); - verify_eq(count_zeros((uint16_t)0), 16); - verify_eq(count_zeros((uint32_t)0), 32); - verify_eq(count_zeros((uint64_t)0), 64); - - verify_eq(rotate_left((uint8_t)0xA1, 4), 0x1A); - verify_eq(rotate_left((uint16_t)0x1234, 12), 0x4123); - verify_eq(rotate_left((uint32_t)0x12345678, 20), 0x67812345); - verify_eq(rotate_left((uint32_t)0x12345678, 0), 0x12345678); - - verify_eq(rotate_right((uint8_t)0xA1, 4), 0x1A); - verify_eq(rotate_right((uint16_t)0x1234, 12), 0x2341); - verify_eq(rotate_right((uint32_t)0x12345678, 20), 0x45678123); - verify_eq(rotate_right((uint32_t)0x12345678, 0), 0x12345678); + bool ret = true; + + ret &= check_eq(bswap((uint8_t)0x12), 0x12); + ret &= check_eq(bswap((uint16_t)0x1234), 0x3412); + ret &= check_eq(bswap((uint32_t)0x12345678), 0x78563412); + ret &= check_eq(bswap((uint64_t)0x1234567812345678), 0x7856341278563412); + + ret &= check_eq(count_ones(0x0), 0); + ret &= check_eq(count_ones(0x1), 1); + ret &= check_eq(count_ones(0x2), 1); + ret &= check_eq(count_ones(0x3), 2); + ret &= check_eq(count_ones(0x137F), 10); + + ret &= check_eq(count_zeros(0), INT_WIDTH); + ret &= check_eq(count_zeros(0L), LONG_WIDTH); + ret &= check_eq(count_zeros(0LL), LLONG_WIDTH); + ret &= check_eq(count_zeros((uint8_t)0), 8); + ret &= check_eq(count_zeros((uint16_t)0), 16); + ret &= check_eq(count_zeros((uint32_t)0), 32); + ret &= check_eq(count_zeros((uint64_t)0), 64); + + ret &= check_eq(rotate_left((uint8_t)0xA1, 4), 0x1A); + ret &= check_eq(rotate_left((uint16_t)0x1234, 12), 0x4123); + ret &= check_eq(rotate_left((uint32_t)0x12345678, 20), 0x67812345); + ret &= check_eq(rotate_left((uint32_t)0x12345678, 0), 0x12345678); + + ret &= check_eq(rotate_right((uint8_t)0xA1, 4), 0x1A); + ret &= check_eq(rotate_right((uint16_t)0x1234, 12), 0x2341); + ret &= check_eq(rotate_right((uint32_t)0x12345678, 20), 0x45678123); + ret &= check_eq(rotate_right((uint32_t)0x12345678, 0), 0x12345678); for (int i = 0; i < 16; ++i) { uint16_t n = (uint16_t)1 << i; for (int j = i; j < 16; ++j) { uint16_t m = (uint16_t)1 << j; uint16_t nm = n | m; - verify_eq(count_ones(nm), 1 + (n != m)); - verify_eq(count_zeros(nm), 15 - (n != m)); - verify_eq(leading_zeros(nm), 15 - j); - verify_eq(trailing_zeros(nm), i); - verify_eq(first_leading_one(nm), j + 1); - verify_eq(first_trailing_one(nm), i + 1); - verify_eq(bit_width(nm), j + 1); - verify_eq(bit_floor(nm), m); + ret &= check_eq(count_ones(nm), 1 + (n != m)); + ret &= check_eq(count_zeros(nm), 15 - (n != m)); + ret &= check_eq(leading_zeros(nm), 15 - j); + ret &= check_eq(trailing_zeros(nm), i); + ret &= check_eq(first_leading_one(nm), j + 1); + ret &= check_eq(first_trailing_one(nm), i + 1); + ret &= check_eq(bit_width(nm), j + 1); + ret &= check_eq(bit_floor(nm), m); if (n == m) { - verify_eq(bit_ceil(nm), m); - bfs_verify(has_single_bit(nm)); + ret &= check_eq(bit_ceil(nm), m); + ret &= bfs_check(has_single_bit(nm)); } else { if (j < 15) { - verify_eq(bit_ceil(nm), (m << 1)); + ret &= check_eq(bit_ceil(nm), (m << 1)); } - bfs_verify(!has_single_bit(nm)); + ret &= bfs_check(!has_single_bit(nm)); } } } - verify_eq(leading_zeros((uint16_t)0), 16); - verify_eq(trailing_zeros((uint16_t)0), 16); - verify_eq(first_leading_one(0), 0); - verify_eq(first_trailing_one(0), 0); - verify_eq(bit_width(0), 0); - verify_eq(bit_floor(0), 0); - verify_eq(bit_ceil(0), 1); + ret &= check_eq(leading_zeros((uint16_t)0), 16); + ret &= check_eq(trailing_zeros((uint16_t)0), 16); + ret &= check_eq(first_leading_one(0), 0); + ret &= check_eq(first_trailing_one(0), 0); + ret &= check_eq(bit_width(0), 0); + ret &= check_eq(bit_floor(0), 0); + ret &= check_eq(bit_ceil(0), 1); - bfs_verify(!has_single_bit(0)); - bfs_verify(!has_single_bit(UINT32_MAX)); - bfs_verify(has_single_bit((uint32_t)1 << (UINT_WIDTH - 1))); + ret &= bfs_check(!has_single_bit(0)); + ret &= bfs_check(!has_single_bit(UINT32_MAX)); + ret &= bfs_check(has_single_bit((uint32_t)1 << (UINT_WIDTH - 1))); - return true; + return ret; } diff --git a/tests/tests.h b/tests/tests.h index d8adbb9..6629dcf 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -9,6 +9,7 @@ #define BFS_TESTS_H #include "../src/config.h" +#include "../src/diag.h" /** Unit test function type. */ typedef bool test_fn(void); @@ -31,4 +32,23 @@ bool check_trie(void); /** Time tests. */ bool check_xtime(void); +/** Don't ignore the bfs_check() return value. */ +attr(nodiscard) +static inline bool bfs_check(bool ret) { + return ret; +} + +/** + * Check a condition, logging a message on failure but continuing. + */ +#define bfs_check(...) \ + bfs_check(bfs_check_(#__VA_ARGS__, __VA_ARGS__, "", "")) + +#define bfs_check_(str, cond, format, ...) \ + ((cond) ? true : (bfs_diag( \ + sizeof(format) > 1 \ + ? "%.0s" format "%s%s" \ + : "Check failed: `%s`%s", \ + str, __VA_ARGS__), false)) + #endif // BFS_TESTS_H diff --git a/tests/trie.c b/tests/trie.c index f94d7c8..fec0de2 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -40,11 +40,13 @@ const char *keys[] = { const size_t nkeys = countof(keys); bool check_trie(void) { + bool ret = true; + struct trie trie; trie_init(&trie); for (size_t i = 0; i < nkeys; ++i) { - bfs_verify(!trie_find_str(&trie, keys[i])); + ret &= bfs_check(!trie_find_str(&trie, keys[i])); const char *prefix = NULL; for (size_t j = 0; j < i; ++j) { @@ -58,37 +60,37 @@ bool check_trie(void) { struct trie_leaf *leaf = trie_find_prefix(&trie, keys[i]); if (prefix) { bfs_verify(leaf); - bfs_verify(strcmp(prefix, leaf->key) == 0); + ret &= bfs_check(strcmp(prefix, leaf->key) == 0); } else { - bfs_verify(!leaf); + ret &= bfs_check(!leaf); } leaf = trie_insert_str(&trie, keys[i]); bfs_verify(leaf); - bfs_verify(strcmp(keys[i], leaf->key) == 0); - bfs_verify(leaf->length == strlen(keys[i]) + 1); + ret &= bfs_check(strcmp(keys[i], leaf->key) == 0); + ret &= bfs_check(leaf->length == strlen(keys[i]) + 1); } { size_t i = 0; for_trie (leaf, &trie) { - bfs_verify(leaf == trie_find_str(&trie, keys[i])); - bfs_verify(!leaf->prev || leaf->prev->next == leaf); - bfs_verify(!leaf->next || leaf->next->prev == leaf); + ret &= bfs_check(leaf == trie_find_str(&trie, keys[i])); + ret &= bfs_check(!leaf->prev || leaf->prev->next == leaf); + ret &= bfs_check(!leaf->next || leaf->next->prev == leaf); ++i; } - bfs_verify(i == nkeys); + ret &= bfs_check(i == nkeys); } for (size_t i = 0; i < nkeys; ++i) { struct trie_leaf *leaf = trie_find_str(&trie, keys[i]); bfs_verify(leaf); - bfs_verify(strcmp(keys[i], leaf->key) == 0); - bfs_verify(leaf->length == strlen(keys[i]) + 1); + ret &= bfs_check(strcmp(keys[i], leaf->key) == 0); + ret &= bfs_check(leaf->length == strlen(keys[i]) + 1); trie_remove(&trie, leaf); leaf = trie_find_str(&trie, keys[i]); - bfs_verify(!leaf); + ret &= bfs_check(!leaf); const char *postfix = NULL; for (size_t j = i + 1; j < nkeys; ++j) { @@ -102,14 +104,14 @@ bool check_trie(void) { leaf = trie_find_postfix(&trie, keys[i]); if (postfix) { bfs_verify(leaf); - bfs_verify(strcmp(postfix, leaf->key) == 0); + ret &= bfs_check(strcmp(postfix, leaf->key) == 0); } else { - bfs_verify(!leaf); + ret &= bfs_check(!leaf); } } for_trie (leaf, &trie) { - bfs_verify(false); + ret &= bfs_check(false, "trie should be empty"); } // This tests the "jump" node handling on 32-bit platforms @@ -118,18 +120,18 @@ bool check_trie(void) { bfs_verify(longstr); memset(longstr, 0xAC, longsize); - bfs_verify(!trie_find_mem(&trie, longstr, longsize)); - bfs_verify(trie_insert_mem(&trie, longstr, longsize)); + ret &= bfs_check(!trie_find_mem(&trie, longstr, longsize)); + ret &= bfs_check(trie_insert_mem(&trie, longstr, longsize)); memset(longstr + longsize / 2, 0xAB, longsize / 2); - bfs_verify(!trie_find_mem(&trie, longstr, longsize)); - bfs_verify(trie_insert_mem(&trie, longstr, longsize)); + ret &= bfs_check(!trie_find_mem(&trie, longstr, longsize)); + ret &= bfs_check(trie_insert_mem(&trie, longstr, longsize)); memset(longstr, 0xAA, longsize / 2); - bfs_verify(!trie_find_mem(&trie, longstr, longsize)); - bfs_verify(trie_insert_mem(&trie, longstr, longsize)); + ret &= bfs_check(!trie_find_mem(&trie, longstr, longsize)); + ret &= bfs_check(trie_insert_mem(&trie, longstr, longsize)); free(longstr); trie_destroy(&trie); - return true; + return ret; } diff --git a/tests/xtime.c b/tests/xtime.c index 4a7d55c..f853428 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -12,121 +12,122 @@ #include static bool tm_equal(const struct tm *tma, const struct tm *tmb) { - if (tma->tm_year != tmb->tm_year) { - return false; - } - if (tma->tm_mon != tmb->tm_mon) { - return false; - } - if (tma->tm_mday != tmb->tm_mday) { - return false; - } - if (tma->tm_hour != tmb->tm_hour) { - return false; - } - if (tma->tm_min != tmb->tm_min) { - return false; - } - if (tma->tm_sec != tmb->tm_sec) { - return false; - } - if (tma->tm_wday != tmb->tm_wday) { - return false; - } - if (tma->tm_yday != tmb->tm_yday) { - return false; - } - if (tma->tm_isdst != tmb->tm_isdst) { - return false; - } - - return true; -} - -static void tm_print(FILE *file, const struct tm *tm) { - fprintf(file, "Y%d M%d D%d h%d m%d s%d wd%d yd%d%s\n", - tm->tm_year, tm->tm_mon, tm->tm_mday, - tm->tm_hour, tm->tm_min, tm->tm_sec, - tm->tm_wday, tm->tm_yday, - tm->tm_isdst ? (tm->tm_isdst < 0 ? " (DST?)" : " (DST)") : ""); + return tma->tm_year == tmb->tm_year + && tma->tm_mon == tmb->tm_mon + && tma->tm_mday == tmb->tm_mday + && tma->tm_hour == tmb->tm_hour + && tma->tm_min == tmb->tm_min + && tma->tm_sec == tmb->tm_sec + && tma->tm_wday == tmb->tm_wday + && tma->tm_yday == tmb->tm_yday + && tma->tm_isdst == tmb->tm_isdst; } /** Check one xgetdate() result. */ -static bool compare_xgetdate(const char *str, int error, time_t expected) { +static bool check_one_xgetdate(const char *str, int error, time_t expected) { struct timespec ts; int ret = xgetdate(str, &ts); if (error) { - if (ret != -1 || errno != error) { - fprintf(stderr, "xgetdate('%s'): %s\n", str, xstrerror(errno)); - return false; - } - } else if (ret != 0) { - fprintf(stderr, "xgetdate('%s'): %s\n", str, xstrerror(errno)); - return false; - } else if (ts.tv_sec != expected || ts.tv_nsec != 0) { - fprintf(stderr, "xgetdate('%s'): %jd.%09jd != %jd\n", str, (intmax_t)ts.tv_sec, (intmax_t)ts.tv_nsec, (intmax_t)expected); - return false; + return bfs_check(ret == -1 && errno == error, "xgetdate('%s'): %s", str, xstrerror(errno)); + } else { + return bfs_check(ret == 0, "xgetdate('%s'): %s", str, xstrerror(errno)) + && bfs_check(ts.tv_sec == expected && ts.tv_nsec == 0, + "xgetdate('%s'): %jd.%09jd != %jd", + str, (intmax_t)ts.tv_sec, (intmax_t)ts.tv_nsec, (intmax_t)expected); } - - return true; } /** xgetdate() tests. */ static bool check_xgetdate(void) { - return compare_xgetdate("", EINVAL, 0) - & compare_xgetdate("????", EINVAL, 0) - & compare_xgetdate("1991", EINVAL, 0) - & compare_xgetdate("1991-??", EINVAL, 0) - & compare_xgetdate("1991-12", EINVAL, 0) - & compare_xgetdate("1991-12-", EINVAL, 0) - & compare_xgetdate("1991-12-??", EINVAL, 0) - & compare_xgetdate("1991-12-14", 0, 692668800) - & compare_xgetdate("1991-12-14-", EINVAL, 0) - & compare_xgetdate("1991-12-14T", EINVAL, 0) - & compare_xgetdate("1991-12-14T??", EINVAL, 0) - & compare_xgetdate("1991-12-14T10", 0, 692704800) - & compare_xgetdate("1991-12-14T10:??", EINVAL, 0) - & compare_xgetdate("1991-12-14T10:11", 0, 692705460) - & compare_xgetdate("1991-12-14T10:11:??", EINVAL, 0) - & compare_xgetdate("1991-12-14T10:11:12", 0, 692705472) - & compare_xgetdate("1991-12-14T10Z", 0, 692704800) - & compare_xgetdate("1991-12-14T10:11Z", 0, 692705460) - & compare_xgetdate("1991-12-14T10:11:12Z", 0, 692705472) - & compare_xgetdate("1991-12-14T10:11:12?", EINVAL, 0) - & compare_xgetdate("1991-12-14T03-07", 0, 692704800) - & compare_xgetdate("1991-12-14T06:41-03:30", 0, 692705460) - & compare_xgetdate("1991-12-14T03:11:12-07:00", 0, 692705472) - & compare_xgetdate("19911214 031112-0700", 0, 692705472); + bool ret = true; + + ret &= check_one_xgetdate("", EINVAL, 0); + ret &= check_one_xgetdate("????", EINVAL, 0); + ret &= check_one_xgetdate("1991", EINVAL, 0); + ret &= check_one_xgetdate("1991-??", EINVAL, 0); + ret &= check_one_xgetdate("1991-12", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-??", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-14", 0, 692668800); + ret &= check_one_xgetdate("1991-12-14-", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-14T", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-14T??", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-14T10", 0, 692704800); + ret &= check_one_xgetdate("1991-12-14T10:??", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-14T10:11", 0, 692705460); + ret &= check_one_xgetdate("1991-12-14T10:11:??", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-14T10:11:12", 0, 692705472); + ret &= check_one_xgetdate("1991-12-14T10Z", 0, 692704800); + ret &= check_one_xgetdate("1991-12-14T10:11Z", 0, 692705460); + ret &= check_one_xgetdate("1991-12-14T10:11:12Z", 0, 692705472); + ret &= check_one_xgetdate("1991-12-14T10:11:12?", EINVAL, 0); + ret &= check_one_xgetdate("1991-12-14T03-07", 0, 692704800); + ret &= check_one_xgetdate("1991-12-14T06:41-03:30", 0, 692705460); + ret &= check_one_xgetdate("1991-12-14T03:11:12-07:00", 0, 692705472); + ret &= check_one_xgetdate("19911214 031112-0700", 0, 692705472);; + + return ret; +} + +#define TM_FORMAT "%04d-%02d-%02d %02d:%02d:%02d (%d/7, %d/365%s)" + +#define TM_PRINTF(tm) \ + (1900 + (tm).tm_year), (tm).tm_mon, (tm).tm_mday, \ + (tm).tm_hour, (tm).tm_min, (tm).tm_sec, \ + ((tm).tm_wday + 1), ((tm).tm_yday + 1), \ + ((tm).tm_isdst ? ((tm).tm_isdst < 0 ? ", DST?" : ", DST") : "") + +/** Check one xmktime() result. */ +static bool check_one_xmktime(time_t expected) { + struct tm tm; + if (xlocaltime(&expected, &tm) != 0) { + bfs_diag("xlocaltime(%jd): %s", (intmax_t)expected, xstrerror(errno)); + return false; + } + + time_t actual; + return bfs_check(xmktime(&tm, &actual) == 0, "xmktime(" TM_FORMAT "): %s", TM_PRINTF(tm), xstrerror(errno)) + && bfs_check(actual == expected, "xmktime(" TM_FORMAT "): %jd != %jd", TM_PRINTF(tm), (intmax_t)actual, (intmax_t)expected); } /** xmktime() tests. */ static bool check_xmktime(void) { + bool ret = true; + for (time_t time = -10; time <= 10; ++time) { - struct tm tm; - if (xlocaltime(&time, &tm) != 0) { - perror("xlocaltime()"); - return false; - } - - time_t made; - if (xmktime(&tm, &made) != 0) { - perror("xmktime()"); - return false; - } - - if (time != made) { - fprintf(stderr, "Mismatch: %jd != %jd\n", (intmax_t)time, (intmax_t)made); - tm_print(stderr, &tm); - return false; - } + ret &= check_one_xmktime(time); + } + + return ret; +} + +/** Check one xtimegm() result. */ +static bool check_one_xtimegm(const struct tm *tm) { + struct tm tma = *tm, tmb = *tm; + time_t ta, tb; + ta = mktime(&tma); + if (xtimegm(&tmb, &tb) != 0) { + tb = -1; } - return true; + bool ret = true; + ret &= bfs_check(ta == tb, "%jd != %jd", (intmax_t)ta, (intmax_t)tb); + ret &= bfs_check(ta == -1 || tm_equal(&tma, &tmb)); + + if (!ret) { + bfs_diag("mktime(): " TM_FORMAT, TM_PRINTF(tma)); + bfs_diag("xtimegm(): " TM_FORMAT, TM_PRINTF(tmb)); + bfs_diag("(input): " TM_FORMAT, TM_PRINTF(*tm)); + } + + return ret; } /** xtimegm() tests. */ static bool check_xtimegm(void) { + bool ret = true; + struct tm tm = { .tm_isdst = -1, }; @@ -137,33 +138,10 @@ static bool check_xtimegm(void) { for (tm.tm_hour = -1; tm.tm_hour <= 24; tm.tm_hour += 5) for (tm.tm_min = -1; tm.tm_min <= 60; tm.tm_min += 31) for (tm.tm_sec = -60; tm.tm_sec <= 120; tm.tm_sec += 5) { - struct tm tma = tm, tmb = tm; - time_t ta, tb; - ta = mktime(&tma); - if (xtimegm(&tmb, &tb) != 0) { - tb = -1; - } - - bool fail = false; - if (ta != tb) { - printf("Mismatch: %jd != %jd\n", (intmax_t)ta, (intmax_t)tb); - fail = true; - } - if (ta != -1 && !tm_equal(&tma, &tmb)) { - printf("mktime(): "); - tm_print(stdout, &tma); - printf("xtimegm(): "); - tm_print(stdout, &tmb); - fail = true; - } - if (fail) { - printf("Input: "); - tm_print(stdout, &tm); - return false; - } + ret &= check_one_xtimegm(&tm); } - return true; + return ret; } bool check_xtime(void) { @@ -173,7 +151,9 @@ bool check_xtime(void) { } tzset(); - return check_xgetdate() - & check_xmktime() - & check_xtimegm(); + bool ret = true; + ret &= check_xgetdate(); + ret &= check_xmktime(); + ret &= check_xtimegm(); + return ret; } -- cgit v1.2.3 From abcac506d025ebf6f0d003635ee5b156f549d93f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 29 Feb 2024 11:30:54 -0500 Subject: Release 3.1.2 --- GNUmakefile | 2 +- docs/CHANGELOG.md | 22 +++++++++++++++++++--- src/config.h | 2 +- 3 files changed, 21 insertions(+), 5 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 852621e..91baf5c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 3.1.1 +VERSION := 3.1.2 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 3255495..9d03705 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,22 @@ 3.* === +3.1.2 +----- + +**February 29, 2024** + +### Bug fixes + +- On Linux, we now check for supported `io_uring` operations before using them, which should fix `bfs` on 5.X series kernels that support `io_uring` but not all of `openat()`/`close()`/`statx()` ([`8bc72d6`](https://github.com/tavianator/bfs/commit/8bc72d6c20c5e38783c4956c4d9fde9b3ee9140c)) + +- Fixed a test failure triggered by certain filesystem types for `/tmp` ([#131](https://github.com/tavianator/bfs/issues/131)) + +- Fixed parsing and interpretation of timezone offsets for explicit reference times used in `-*since` and `-newerXt` ([`a9f3cde`](https://github.com/tavianator/bfs/commit/a9f3cde30426b546ba6e3172e1a7951213a72049)) + +- Fixed the build on m68k ([`c749c11`](https://github.com/tavianator/bfs/commit/c749c11b04444ca40941dd2ddc5802faed148f6a)) + + 3.1.1 ----- @@ -36,7 +52,7 @@ - Leading whitespace is no longer accepted in integer command line arguments like `-links ' 1'` ([`e0d7dc5`](https://github.com/tavianator/bfs/commit/e0d7dc5dfd7bdaa62b6bc18e9c1cce00bbe08577)) -### Bug Fixes +### Bug fixes - `-quit` and `-exit` could be ignored in the iterative deepening modes (`-S {ids,eds}`). This is now fixed ([`670ebd9`](https://github.com/tavianator/bfs/commit/670ebd97fb431e830b1500b2e7e8013b121fb2c5)). @@ -62,7 +78,7 @@ **October 12, 2023** -### Bug Fixes +### Bug fixes - Fixed a segfault when reporting errors under musl ([`d40eb87`]) @@ -80,7 +96,7 @@ - Parallel depth-first search (`-S dfs`) was optimized to avoid enqueueing every file separately ([`2572273`]) -### Bug Fixes +### Bug fixes - Iterative deepening modes (`-S {ids,eds}`) were performing iterative *breadth*-first searches since `bfs` 3.0, negating any advantages they may have had over normal breadth-first search. They now do iterative *depth*-first searches as expected. diff --git a/src/config.h b/src/config.h index 41a8d5f..4697083 100644 --- a/src/config.h +++ b/src/config.h @@ -30,7 +30,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "3.1.1" +# define BFS_VERSION "3.1.2" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From 10d6f6c849ce2c6bc6275793c17a4a93c73b80de Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 2 Mar 2024 10:25:31 -0500 Subject: tests/ioq: Check that we push and pop the same number of times --- tests/ioq.c | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/ioq.c b/tests/ioq.c index 3d35650..56e1886 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -54,15 +54,17 @@ static void check_ioq_push_block(void) { ioq_cancel(ioq); // Drain the queue - struct ioq_ent *ent; - while ((ent = ioq_pop(ioq, true))) { - bfs_verify(ent->op == IOQ_OPENDIR); + for (size_t i = 0; i < depth; ++i) { + struct ioq_ent *ent = ioq_pop(ioq, true); + bfs_verify(ent && ent->op == IOQ_OPENDIR); + if (ent->result >= 0) { bfs_closedir(ent->opendir.dir); } free(ent->opendir.dir); ioq_free(ioq, ent); } + bfs_verify(!ioq_pop(ioq, true)); ioq_destroy(ioq); } -- cgit v1.2.3 From 61adc84620e90c3380c785d559137fac487a301c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Mar 2024 18:20:11 -0500 Subject: eval: Tweak status bar punctuation --- src/eval.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/eval.c b/src/eval.c index c4c79e0..8b71833 100644 --- a/src/eval.c +++ b/src/eval.c @@ -1120,7 +1120,7 @@ static void eval_status(struct bfs_eval *state, struct bfs_bar *bar, struct time const struct BFTW *ftwbuf = state->ftwbuf; dchar *status = NULL; - dchar *rhs = dstrprintf(" (visited: %'zu, depth: %2zu)", count, ftwbuf->depth); + dchar *rhs = dstrprintf(" (visited: %'zu; depth: %2zu)", count, ftwbuf->depth); if (!rhs) { return; } -- cgit v1.2.3 From f64f76b55400b71e8576ed7e4a377eb5ef9576aa Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Mar 2024 18:42:32 -0500 Subject: ioq: Copy ring_ops from the previous thread Otherwise threads 2-N won't use io_uring at all! Oops. Fixes: 8bc72d6c ("ioq: Probe for supported io_uring operations") --- src/ioq.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/ioq.c b/src/ioq.c index f71ee6e..00c3b86 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -850,6 +850,7 @@ static int ioq_ring_init(struct ioq *ioq, struct ioq_thread *thread) { if (prev) { // Initial setup already complete + thread->ring_ops = prev->ring_ops; return 0; } @@ -916,12 +917,14 @@ static void ioq_thread_join(struct ioq_thread *thread) { } struct ioq *ioq_create(size_t depth, size_t nthreads) { - struct ioq *ioq = ZALLOC_FLEX(struct ioq, threads, nthreads); + struct ioq *ioq = ALLOC_FLEX(struct ioq, threads, nthreads); if (!ioq) { goto fail; } ioq->depth = depth; + ioq->size = 0; + ioq->cancel = false; ARENA_INIT(&ioq->ents, struct ioq_ent); -- cgit v1.2.3 From a1e288cb6a2482b7acc1edb92eea9d676da0729f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 6 Mar 2024 18:55:16 -0500 Subject: Release 3.1.3 --- GNUmakefile | 2 +- docs/CHANGELOG.md | 12 ++++++++++++ src/config.h | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/GNUmakefile b/GNUmakefile index 91baf5c..7dcd76f 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -6,7 +6,7 @@ VERSION := $(shell git describe --always 2>/dev/null) endif ifndef VERSION -VERSION := 3.1.2 +VERSION := 3.1.3 endif ifndef OS diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 9d03705..672c2b4 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,18 @@ 3.* === +3.1.3 +----- + +**March 6, 2024** + +### Bug fixes + +- On Linux, the `io_uring` feature probing introduced in `bfs` 3.1.2 only applied to one thread, causing all other threads to avoid using io_uring entirely. + The probe results are now copied to all threads correctly. + ([`f64f76b`](https://github.com/tavianator/bfs/commit/f64f76b55400b71e8576ed7e4a377eb5ef9576aa)) + + 3.1.2 ----- diff --git a/src/config.h b/src/config.h index 4697083..ad2e481 100644 --- a/src/config.h +++ b/src/config.h @@ -30,7 +30,7 @@ # define BFS_COMMAND "bfs" #endif #ifndef BFS_VERSION -# define BFS_VERSION "3.1.2" +# define BFS_VERSION "3.1.3" #endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -- cgit v1.2.3 From 416ca3b557055efa5746a4d40d927391c59a9292 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 7 Mar 2024 16:01:46 -0500 Subject: tests: Change the timezone for -daystart tests Otherwise, if the test files are created just before midnight, and the test runs just after midnight, -daystart will consider them one day old. Fixes #132 --- tests/gnu/daystart.sh | 2 +- tests/gnu/daystart_twice.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/gnu/daystart.sh b/tests/gnu/daystart.sh index 9799bca..9c3be1a 100644 --- a/tests/gnu/daystart.sh +++ b/tests/gnu/daystart.sh @@ -1 +1 @@ -bfs_diff basic -daystart -mtime 0 +TZ=WAT-1 bfs_diff basic -daystart -mtime 0 diff --git a/tests/gnu/daystart_twice.sh b/tests/gnu/daystart_twice.sh index 21b2c0f..edbf18d 100644 --- a/tests/gnu/daystart_twice.sh +++ b/tests/gnu/daystart_twice.sh @@ -1 +1 @@ -bfs_diff basic -daystart -daystart -mtime 0 +TZ=WAT-1 bfs_diff basic -daystart -daystart -mtime 0 -- cgit v1.2.3 From 43cd776d7dc8ac573262f8459edeb1c1f5f3cd09 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 7 Mar 2024 16:18:32 -0500 Subject: xtime: Call tzset() from main() instead of lazily POSIX specifies[1] that If a thread accesses tzname, daylight, or timezone directly while another thread is in a call to tzset(), or to any function that is required or allowed to set timezone information as if by calling tzset(), the behavior is undefined. So calling it lazily from arbitrary threads is risky. [1]: https://pubs.opengroup.org/onlinepubs/9699919799/functions/tzset.html --- src/eval.c | 2 +- src/main.c | 4 ++++ src/parse.c | 12 ++++++------ src/printf.c | 4 ++-- src/xtime.c | 34 +--------------------------------- src/xtime.h | 24 ------------------------ tests/bfstd.c | 5 ----- tests/main.c | 14 ++++++++++++++ tests/xtime.c | 10 ++-------- tests/xtouch.c | 2 ++ 10 files changed, 32 insertions(+), 79 deletions(-) diff --git a/src/eval.c b/src/eval.c index 8b71833..1711001 100644 --- a/src/eval.c +++ b/src/eval.c @@ -730,7 +730,7 @@ bool eval_fls(const struct bfs_expr *expr, struct bfs_eval *state) { time_t six_months_ago = now - 6 * 30 * 24 * 60 * 60; time_t tomorrow = now + 24 * 60 * 60; struct tm tm; - if (xlocaltime(&time, &tm) != 0) { + if (!localtime_r(&time, &tm)) { goto error; } char time_str[256]; diff --git a/src/main.c b/src/main.c index 1ddbc54..16a2576 100644 --- a/src/main.c +++ b/src/main.c @@ -55,6 +55,7 @@ #include #include #include +#include #include /** @@ -121,6 +122,9 @@ int main(int argc, char *argv[]) { locale_err = errno; } + // Apply the environment's timezone + tzset(); + // Parse the command line struct bfs_ctx *ctx = bfs_parse_cmdline(argc, argv); if (!ctx) { diff --git a/src/parse.c b/src/parse.c index 5d0f333..b26a50f 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1086,8 +1086,8 @@ static struct bfs_expr *parse_const(struct bfs_parser *parser, int value, int ar */ static struct bfs_expr *parse_daystart(struct bfs_parser *parser, int arg1, int arg2) { struct tm tm; - if (xlocaltime(&parser->now.tv_sec, &tm) != 0) { - parse_perror(parser, "xlocaltime()"); + if (!localtime_r(&parser->now.tv_sec, &tm)) { + parse_perror(parser, "localtime_r()"); return NULL; } @@ -1708,8 +1708,8 @@ static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr) fprintf(stderr, "Supported timestamp formats are ISO 8601-like, e.g.\n\n"); struct tm tm; - if (xlocaltime(&parser->now.tv_sec, &tm) != 0) { - parse_perror(parser, "xlocaltime()"); + if (!localtime_r(&parser->now.tv_sec, &tm)) { + parse_perror(parser, "localtime_r()"); return -1; } @@ -1728,8 +1728,8 @@ static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr) fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d%+03d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec, tz_hour, tz_min); - if (xgmtime(&parser->now.tv_sec, &tm) != 0) { - parse_perror(parser, "xgmtime()"); + if (!gmtime_r(&parser->now.tv_sec, &tm)) { + parse_perror(parser, "gmtime_r()"); return -1; } diff --git a/src/printf.c b/src/printf.c index 1fe8cc3..34ed606 100644 --- a/src/printf.c +++ b/src/printf.c @@ -123,7 +123,7 @@ static int bfs_printf_ctime(CFILE *cfile, const struct bfs_fmt *fmt, const struc } struct tm tm; - if (xlocaltime(&ts->tv_sec, &tm) != 0) { + if (!localtime_r(&ts->tv_sec, &tm)) { return -1; } @@ -153,7 +153,7 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_fmt *fmt, const st } struct tm tm; - if (xlocaltime(&ts->tv_sec, &tm) != 0) { + if (!localtime_r(&ts->tv_sec, &tm)) { return -1; } diff --git a/src/xtime.c b/src/xtime.c index 4309289..8100f6c 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -11,38 +11,6 @@ #include #include -/** Call tzset() if necessary. */ -static void xtzset(void) { - static atomic bool is_set = false; - - if (!load(&is_set, relaxed)) { - tzset(); - store(&is_set, true, relaxed); - } -} - -int xlocaltime(const time_t *timep, struct tm *result) { - // Should be called before localtime_r() according to POSIX.1-2004 - xtzset(); - - if (localtime_r(timep, result)) { - return 0; - } else { - return -1; - } -} - -int xgmtime(const time_t *timep, struct tm *result) { - // Should be called before gmtime_r() according to POSIX.1-2004 - xtzset(); - - if (gmtime_r(timep, result)) { - return 0; - } else { - return -1; - } -} - int xmktime(struct tm *tm, time_t *timep) { *timep = mktime(tm); @@ -50,7 +18,7 @@ int xmktime(struct tm *tm, time_t *timep) { int error = errno; struct tm tmp; - if (xlocaltime(timep, &tmp) != 0) { + if (!localtime_r(timep, &tmp)) { return -1; } diff --git a/src/xtime.h b/src/xtime.h index 75d1f4e..fb60ae4 100644 --- a/src/xtime.h +++ b/src/xtime.h @@ -10,30 +10,6 @@ #include -/** - * localtime_r() wrapper that calls tzset() first. - * - * @param[in] timep - * The time_t to convert. - * @param[out] result - * Buffer to hold the result. - * @return - * 0 on success, -1 on failure. - */ -int xlocaltime(const time_t *timep, struct tm *result); - -/** - * gmtime_r() wrapper that calls tzset() first. - * - * @param[in] timep - * The time_t to convert. - * @param[out] result - * Buffer to hold the result. - * @return - * 0 on success, -1 on failure. - */ -int xgmtime(const time_t *timep, struct tm *result); - /** * mktime() wrapper that reports errors more reliably. * diff --git a/tests/bfstd.c b/tests/bfstd.c index 4d0ec23..0ded5de 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -43,11 +43,6 @@ static bool check_wordesc(const char *str, const char *exp, enum wesc_flags flag bool check_bfstd(void) { bool ret = true; - // Try to set a UTF-8 locale - if (!setlocale(LC_ALL, "C.UTF-8")) { - setlocale(LC_ALL, ""); - } - // From man 3p basename ret &= check_base_dir("usr", ".", "usr"); ret &= check_base_dir("usr/", ".", "usr"); diff --git a/tests/main.c b/tests/main.c index e69fe35..38438b2 100644 --- a/tests/main.c +++ b/tests/main.c @@ -11,9 +11,11 @@ #include "../src/config.h" #include "../src/diag.h" #include +#include #include #include #include +#include /** * Test context. @@ -90,6 +92,18 @@ static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) { } int main(int argc, char *argv[]) { + // Try to set a UTF-8 locale + if (!setlocale(LC_ALL, "C.UTF-8")) { + setlocale(LC_ALL, ""); + } + + // Run tests in UTC + if (setenv("TZ", "UTC0", true) != 0) { + perror("setenv()"); + return EXIT_FAILURE; + } + tzset(); + struct test_ctx ctx; if (test_init(&ctx, argc, argv) != 0) { goto done; diff --git a/tests/xtime.c b/tests/xtime.c index f853428..2609c1c 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -81,8 +81,8 @@ static bool check_xgetdate(void) { /** Check one xmktime() result. */ static bool check_one_xmktime(time_t expected) { struct tm tm; - if (xlocaltime(&expected, &tm) != 0) { - bfs_diag("xlocaltime(%jd): %s", (intmax_t)expected, xstrerror(errno)); + if (!localtime_r(&expected, &tm)) { + bfs_diag("localtime_r(%jd): %s", (intmax_t)expected, xstrerror(errno)); return false; } @@ -145,12 +145,6 @@ static bool check_xtimegm(void) { } bool check_xtime(void) { - if (setenv("TZ", "UTC0", true) != 0) { - perror("setenv()"); - return false; - } - tzset(); - bool ret = true; ret &= check_xgetdate(); ret &= check_xmktime(); diff --git a/tests/xtouch.c b/tests/xtouch.c index 6099128..8c5c5f3 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -157,6 +157,8 @@ done: } int main(int argc, char *argv[]) { + tzset(); + mode_t mask = umask(0); struct args args = { -- cgit v1.2.3 From f9e9e5f9013cc2e9d238cbcad9a1dfaf3a529aac Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 10 Mar 2024 11:39:34 -0400 Subject: tests/xtime: Add tests for integer overflow --- src/xtime.c | 4 +++- tests/xtime.c | 28 ++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/xtime.c b/src/xtime.c index 8100f6c..05f0e1a 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -2,8 +2,9 @@ // SPDX-License-Identifier: 0BSD #include "xtime.h" -#include "atomic.h" +#include "bfstd.h" #include "config.h" +#include "diag.h" #include #include #include @@ -19,6 +20,7 @@ int xmktime(struct tm *tm, time_t *timep) { struct tm tmp; if (!localtime_r(timep, &tmp)) { + bfs_bug("localtime_r(-1): %s", xstrerror(errno)); return -1; } diff --git a/tests/xtime.c b/tests/xtime.c index 2609c1c..3f1fec2 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -99,6 +99,19 @@ static bool check_xmktime(void) { ret &= check_one_xmktime(time); } + // Attempt to trigger overflow (but don't test for it, since it's not mandatory) + struct tm tm = { + .tm_year = INT_MAX, + .tm_mon = INT_MAX, + .tm_mday = INT_MAX, + .tm_hour = INT_MAX, + .tm_min = INT_MAX, + .tm_sec = INT_MAX, + .tm_isdst = -1, + }; + time_t time; + xmktime(&tm, &time); + return ret; } @@ -131,7 +144,9 @@ static bool check_xtimegm(void) { struct tm tm = { .tm_isdst = -1, }; + time_t time; + // Check equivalence with mktime() for (tm.tm_year = 10; tm.tm_year <= 200; tm.tm_year += 10) for (tm.tm_mon = -3; tm.tm_mon <= 15; tm.tm_mon += 3) for (tm.tm_mday = -31; tm.tm_mday <= 61; tm.tm_mday += 4) @@ -141,6 +156,19 @@ static bool check_xtimegm(void) { ret &= check_one_xtimegm(&tm); } + // Check integer overflow cases + tm = (struct tm){ .tm_sec = INT_MAX, .tm_min = INT_MAX }; + ret &= bfs_check(xtimegm(&tm, &time) == -1 && errno == EOVERFLOW); + + tm = (struct tm){ .tm_min = INT_MAX, .tm_hour = INT_MAX }; + ret &= bfs_check(xtimegm(&tm, &time) == -1 && errno == EOVERFLOW); + + tm = (struct tm){ .tm_hour = INT_MAX, .tm_mday = INT_MAX }; + ret &= bfs_check(xtimegm(&tm, &time) == -1 && errno == EOVERFLOW); + + tm = (struct tm){ .tm_mon = INT_MAX, .tm_year = INT_MAX }; + ret &= bfs_check(xtimegm(&tm, &time) == -1 && errno == EOVERFLOW); + return ret; } -- cgit v1.2.3 From 2c3ef3a06ee1f951f6d68be6d0d3f6a1822b05b7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 11 Mar 2024 09:51:03 -0400 Subject: Re-run include-what-you-use --- src/alloc.h | 1 + src/bfstd.c | 6 +++--- src/bftw.c | 1 + src/color.h | 1 - src/ctx.c | 1 + src/ctx.h | 2 ++ src/diag.c | 2 +- src/dir.h | 4 ++-- src/dstring.c | 2 ++ src/eval.c | 4 ++-- src/expr.c | 4 ++-- src/expr.h | 1 - src/ioq.c | 5 +++-- src/main.c | 1 + src/opt.c | 3 ++- src/parse.c | 3 +-- src/printf.c | 3 ++- src/trie.c | 2 -- src/trie.h | 1 - src/xspawn.c | 1 - src/xtime.c | 1 - tests/alloc.c | 1 + tests/bfstd.c | 3 --- tests/bit.c | 2 +- tests/ioq.c | 2 ++ tests/main.c | 3 --- tests/xtime.c | 4 ++-- tests/xtouch.c | 1 + 28 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/alloc.h b/src/alloc.h index 60dd738..ae055bc 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -10,6 +10,7 @@ #include "config.h" #include +#include #include /** Check if a size is properly aligned. */ diff --git a/src/bfstd.c b/src/bfstd.c index ce4aa49..c6c2e7f 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -2,18 +2,19 @@ // SPDX-License-Identifier: 0BSD #include "bfstd.h" -#include "bit.h" #include "config.h" #include "diag.h" #include "sanity.h" #include "thread.h" #include "xregex.h" -#include #include #include #include +#include #include #include +#include +#include #include #include #include @@ -24,7 +25,6 @@ #include #include #include -#include #if BFS_USE_SYS_SYSMACROS_H # include diff --git a/src/bftw.c b/src/bftw.c index 6f52299..50b8b02 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -35,6 +35,7 @@ #include #include #include +#include /** Initialize a bftw_stat cache. */ static void bftw_stat_init(struct bftw_stat *bufs, struct bfs_stat *stat_buf, struct bfs_stat *lstat_buf) { diff --git a/src/color.h b/src/color.h index 85633a4..e3e7973 100644 --- a/src/color.h +++ b/src/color.h @@ -10,7 +10,6 @@ #include "config.h" #include "dstring.h" -#include #include /** diff --git a/src/ctx.c b/src/ctx.c index 6c84f75..f5b28c7 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -6,6 +6,7 @@ #include "color.h" #include "diag.h" #include "expr.h" +#include "list.h" #include "mtab.h" #include "pwcache.h" #include "stat.h" diff --git a/src/ctx.h b/src/ctx.h index aa91f2c..e14db21 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -18,6 +18,8 @@ #include #include +struct CFILE; + /** * The execution context for bfs. */ diff --git a/src/diag.c b/src/diag.c index 656fa89..cb27b92 100644 --- a/src/diag.c +++ b/src/diag.c @@ -11,8 +11,8 @@ #include "expr.h" #include #include +#include #include -#include /** bfs_diagf() implementation. */ attr(printf(2, 0)) diff --git a/src/dir.h b/src/dir.h index b11d454..18d907e 100644 --- a/src/dir.h +++ b/src/dir.h @@ -8,8 +8,6 @@ #ifndef BFS_DIR_H #define BFS_DIR_H -#include "alloc.h" -#include "config.h" #include /** @@ -78,6 +76,8 @@ struct bfs_dirent { */ struct bfs_dir *bfs_allocdir(void); +struct arena; + /** * Initialize an arena for directories. * diff --git a/src/dstring.c b/src/dstring.c index bc18308..10b0fad 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -7,6 +7,8 @@ #include "config.h" #include "diag.h" #include +#include +#include #include #include #include diff --git a/src/eval.c b/src/eval.c index 1711001..9e55964 100644 --- a/src/eval.c +++ b/src/eval.c @@ -25,7 +25,6 @@ #include "stat.h" #include "trie.h" #include "xregex.h" -#include "xtime.h" #include #include #include @@ -36,8 +35,9 @@ #include #include #include +#include #include -#include +#include #include #include #include diff --git a/src/expr.c b/src/expr.c index 3e0033f..5784220 100644 --- a/src/expr.c +++ b/src/expr.c @@ -4,12 +4,12 @@ #include "expr.h" #include "alloc.h" #include "ctx.h" +#include "diag.h" #include "eval.h" #include "exec.h" +#include "list.h" #include "printf.h" #include "xregex.h" -#include -#include #include struct bfs_expr *bfs_expr_new(struct bfs_ctx *ctx, bfs_eval_fn *eval_fn, size_t argc, char **argv) { diff --git a/src/expr.h b/src/expr.h index 957b04a..75cb5fd 100644 --- a/src/expr.h +++ b/src/expr.h @@ -12,7 +12,6 @@ #include "config.h" #include "eval.h" #include "stat.h" -#include #include #include diff --git a/src/ioq.c b/src/ioq.c index 00c3b86..37eed7d 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -126,13 +126,14 @@ #include "config.h" #include "diag.h" #include "dir.h" -#include "sanity.h" #include "stat.h" #include "thread.h" -#include #include +#include #include +#include #include +#include #if BFS_USE_LIBURING # include diff --git a/src/main.c b/src/main.c index 16a2576..e120f03 100644 --- a/src/main.c +++ b/src/main.c @@ -48,6 +48,7 @@ #include "bfstd.h" #include "config.h" #include "ctx.h" +#include "diag.h" #include "eval.h" #include "parse.h" #include diff --git a/src/opt.c b/src/opt.c index 74145ac..76965de 100644 --- a/src/opt.c +++ b/src/opt.c @@ -26,11 +26,13 @@ */ #include "opt.h" +#include "bftw.h" #include "bit.h" #include "color.h" #include "config.h" #include "ctx.h" #include "diag.h" +#include "dir.h" #include "eval.h" #include "exec.h" #include "expr.h" @@ -40,7 +42,6 @@ #include #include #include -#include #include static char *fake_and_arg = "-and"; diff --git a/src/parse.c b/src/parse.c index b26a50f..3b7386d 100644 --- a/src/parse.c +++ b/src/parse.c @@ -42,8 +42,7 @@ #include #include #include -#include -#include +#include #include #include diff --git a/src/printf.c b/src/printf.c index 34ed606..487f039 100644 --- a/src/printf.c +++ b/src/printf.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "printf.h" +#include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" @@ -14,10 +15,10 @@ #include "mtab.h" #include "pwcache.h" #include "stat.h" -#include "xtime.h" #include #include #include +#include #include #include #include diff --git a/src/trie.c b/src/trie.c index bd5300d..1ffb23a 100644 --- a/src/trie.c +++ b/src/trie.c @@ -87,9 +87,7 @@ #include "config.h" #include "diag.h" #include "list.h" -#include #include -#include #include bfs_static_assert(CHAR_WIDTH == 8); diff --git a/src/trie.h b/src/trie.h index 02088f1..4288d76 100644 --- a/src/trie.h +++ b/src/trie.h @@ -5,7 +5,6 @@ #define BFS_TRIE_H #include "alloc.h" -#include "config.h" #include "list.h" #include #include diff --git a/src/xspawn.c b/src/xspawn.c index 8d6108b..6a94d3d 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -12,7 +12,6 @@ #include #include #include -#include #include #if BFS_USE_PATHS_H diff --git a/src/xtime.c b/src/xtime.c index 05f0e1a..5b259ab 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -7,7 +7,6 @@ #include "diag.h" #include #include -#include #include #include #include diff --git a/tests/alloc.c b/tests/alloc.c index 4ce23d4..9f08111 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -3,6 +3,7 @@ #include "tests.h" #include "../src/alloc.h" +#include "../src/config.h" #include "../src/diag.h" #include #include diff --git a/tests/bfstd.c b/tests/bfstd.c index 0ded5de..26abdb6 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -7,9 +7,6 @@ #include "../src/diag.h" #include #include -#include -#include -#include #include #include diff --git a/tests/bit.c b/tests/bit.c index b944748..3d66ce3 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -3,10 +3,10 @@ #include "tests.h" #include "../src/bit.h" +#include "../src/config.h" #include "../src/diag.h" #include #include -#include bfs_static_assert(UMAX_WIDTH(0x1) == 1); bfs_static_assert(UMAX_WIDTH(0x3) == 2); diff --git a/tests/ioq.c b/tests/ioq.c index 56e1886..1ce8f75 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -4,10 +4,12 @@ #include "tests.h" #include "../src/ioq.h" #include "../src/bfstd.h" +#include "../src/config.h" #include "../src/diag.h" #include "../src/dir.h" #include #include +#include /** * Test for blocking within ioq_slot_push(). diff --git a/tests/main.c b/tests/main.c index 38438b2..8849e8c 100644 --- a/tests/main.c +++ b/tests/main.c @@ -6,11 +6,8 @@ */ #include "tests.h" -#include "../src/bfstd.h" #include "../src/color.h" #include "../src/config.h" -#include "../src/diag.h" -#include #include #include #include diff --git a/tests/xtime.c b/tests/xtime.c index 3f1fec2..c8dc00b 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -5,10 +5,10 @@ #include "../src/xtime.h" #include "../src/bfstd.h" #include "../src/config.h" +#include "../src/diag.h" #include +#include #include -#include -#include #include static bool tm_equal(const struct tm *tma, const struct tm *tmb) { diff --git a/tests/xtouch.c b/tests/xtouch.c index 8c5c5f3..fad272f 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -11,6 +11,7 @@ #include #include #include +#include #include #include -- cgit v1.2.3 From 10b2c56677c1708289779e4ce37a958a8cf37533 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 17 Mar 2024 12:32:40 -0400 Subject: config: Don't mix [[attr]] and __attribute__((attr)) GCC and Clang don't support combining the two attribute syntaxes in arbitrary order. For now, just use the GNU style. --- src/config.h | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/config.h b/src/config.h index ad2e481..45422a2 100644 --- a/src/config.h +++ b/src/config.h @@ -203,9 +203,7 @@ typedef long double max_align_t; /** * Silence warnings about unused declarations. */ -#if __has_c_attribute(maybe_unused) -# define attr_maybe_unused [[maybe_unused]] -#elif __has_attribute(unused) +#if __has_attribute(unused) # define attr_maybe_unused __attribute__((unused)) #else # define attr_maybe_unused @@ -214,9 +212,7 @@ typedef long double max_align_t; /** * Warn if a value is unused. */ -#if __has_c_attribute(nodiscard) -# define attr_nodiscard [[nodiscard]] -#elif __has_attribute(warn_unused_result) +#if __has_attribute(warn_unused_result) # define attr_nodiscard __attribute__((warn_unused_result)) #else # define attr_nodiscard -- cgit v1.2.3 From 906009bbb7a88002d0db8b7a26ad9d5b71120869 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 19 Mar 2024 15:58:37 -0400 Subject: ci: Work around https://github.com/actions/runner-images/issues/9491 Link: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=113430 Link: https://github.com/llvm/llvm-project/issues/78354 Link: https://zolutal.github.io/aslrnt/ --- .github/workflows/ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 6276627..4c1c89c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,6 +37,8 @@ jobs: sudo ln -s libattr.so.1 /lib/i386-linux-gnu/libattr.so sudo ln -s libcap.so.2 /lib/i386-linux-gnu/libcap.so sudo ln -s libonig.so.5 /lib/i386-linux-gnu/libonig.so + # Work around https://github.com/actions/runner-images/issues/9491 + sudo sysctl vm.mmap_rnd_bits=28 - name: Run tests run: | -- cgit v1.2.3 From 912d2b94cf6ff0871c07325af5ed520a2bc97722 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 20 Mar 2024 10:44:34 -0400 Subject: Implement -limit N Closes: https://github.com/tavianator/bfs/issues/133 --- completions/bfs.bash | 3 +-- completions/bfs.fish | 1 + completions/bfs.zsh | 1 + docs/USAGE.md | 34 ++++++++++++++++++++++++++++++++++ docs/bfs.1 | 5 +++++ src/eval.c | 13 +++++++++++++ src/eval.h | 1 + src/opt.c | 1 + src/parse.c | 38 +++++++++++++++++++++++++++++++++++++- tests/bfs/limit.out | 4 ++++ tests/bfs/limit.sh | 1 + tests/bfs/limit_0.sh | 1 + tests/bfs/limit_implicit_print.sh | 1 + tests/bfs/limit_incomplete.sh | 1 + tests/bfs/limit_one.sh | 1 + 15 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 tests/bfs/limit.out create mode 100644 tests/bfs/limit.sh create mode 100644 tests/bfs/limit_0.sh create mode 100644 tests/bfs/limit_implicit_print.sh create mode 100644 tests/bfs/limit_incomplete.sh create mode 100644 tests/bfs/limit_one.sh diff --git a/completions/bfs.bash b/completions/bfs.bash index 2f52e8d..db582da 100644 --- a/completions/bfs.bash +++ b/completions/bfs.bash @@ -37,6 +37,7 @@ _bfs() { -ipath -iregex -iwholename + -limit -links -lname -maxdepth @@ -94,8 +95,6 @@ _bfs() { -depth -follow -ignore_readdir_race - -maxdepth - -mindepth -mount -nocolor -noignore_readdir_race diff --git a/completions/bfs.fish b/completions/bfs.fish index 3f399e7..1303639 100644 --- a/completions/bfs.fish +++ b/completions/bfs.fish @@ -133,6 +133,7 @@ complete -c bfs -o fls -d "Like -ls, but write to specified file" -F complete -c bfs -o fprint -d "Like -print, but write to specified file" -F complete -c bfs -o fprint0 -d "Like -print0, but write to specified file" -F complete -c bfs -o fprintf -d "Like -printf, but write to specified file" -F +complete -c bfs -o limit -d "Limit the number of results" -x complete -c bfs -o ls -d "List files like ls -dils" complete -c bfs -o print -d "Print the path to the found file" complete -c bfs -o print0 -d "Like -print, but use the null character as a separator rather than newlines" diff --git a/completions/bfs.zsh b/completions/bfs.zsh index 3d7dc3a..51b5029 100644 --- a/completions/bfs.zsh +++ b/completions/bfs.zsh @@ -133,6 +133,7 @@ args=( '*-fprint0[print the path to the found file using null character as separator, but write to FILE instead of standard output]:output file:_files' '*-fprintf[print according to format string, but write to FILE instead of standard output]:output file:_files:output format' + '*-limit[quit after N results]:maximum result count' '*-ls[list files like ls -dils]' '*-print[print the path to the found file]' '*-print0[print the path to the found file using null character as separator]' diff --git a/docs/USAGE.md b/docs/USAGE.md index 3efdee0..071c95b 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -130,6 +130,40 @@ Unlike `-prune`, `-exclude` even works in combination with `-depth`/`-delete`. --- +### `-limit` + +The `-limit N` action makes `bfs` quit once it gets evaluated `N` times. +Placing it after an action like `-print` limits the number of results that get printed, for example: + +```console +$ bfs -s -type f -name '*.txt' +./1.txt +./2.txt +./3.txt +./4.txt +$ bfs -s -type f -name '*.txt' -print -limit 2 +./1.txt +./2.txt +``` + +This is similar to + +```console +$ bfs -s -type f -name '*.txt' | head -n2 +``` + +but more powerful because you can apply separate limits to different expressions: + +```console +$ bfs \( -name '*.txt' -print -limit 3 -o -name '*.log' -print -limit 4 \) -limit 5 +[At most 3 .txt files, at most 4 .log files, and at most 5 in total] +``` + +and more efficient because it will quit immediately. +When piping to `head`, `bfs` will only quit *after* it tries to output too many results. + +--- + ### `-hidden`/`-nohidden` `-hidden` matches "hidden" files (dotfiles). diff --git a/docs/bfs.1 b/docs/bfs.1 index 2ecb891..3a4f15a 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -725,6 +725,11 @@ but write to instead of standard output. .RE .TP +\fB\-limit \fIN\fR +Quit once this action is evaluated +.I N +times. +.TP .B \-ls List files like .B ls diff --git a/src/eval.c b/src/eval.c index 9e55964..2f06858 100644 --- a/src/eval.c +++ b/src/eval.c @@ -840,6 +840,19 @@ error: return true; } +/** + * -limit action. + */ +bool eval_limit(const struct bfs_expr *expr, struct bfs_eval *state) { + long long evals = expr->evaluations + 1; + if (evals >= expr->num) { + state->action = BFTW_STOP; + state->quit = true; + } + + return true; +} + /** * -prune action. */ diff --git a/src/eval.h b/src/eval.h index 98bbc08..f7f6c77 100644 --- a/src/eval.h +++ b/src/eval.h @@ -88,6 +88,7 @@ bool eval_fprint(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_fprint0(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_fprintf(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_fprintx(const struct bfs_expr *expr, struct bfs_eval *state); +bool eval_limit(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_prune(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_quit(const struct bfs_expr *expr, struct bfs_eval *state); diff --git a/src/opt.c b/src/opt.c index 76965de..a470d25 100644 --- a/src/opt.c +++ b/src/opt.c @@ -1181,6 +1181,7 @@ static struct bfs_expr *annotate_visit(struct bfs_opt *opt, struct bfs_expr *exp eval_fprint0, eval_fprintf, eval_fprintx, + eval_limit, eval_prune, eval_true, // Non-returning diff --git a/src/parse.c b/src/parse.c index 3b7386d..2dfcab2 100644 --- a/src/parse.c +++ b/src/parse.c @@ -97,6 +97,8 @@ struct bfs_parser { char **last_arg; /** A "-depth"-type argument, if any. */ char **depth_arg; + /** A "-limit" argument, if any. */ + char **limit_arg; /** A "-prune" argument, if any. */ char **prune_arg; /** A "-mount" argument, if any. */ @@ -733,7 +735,7 @@ static struct bfs_expr *parse_action(struct bfs_parser *parser, bfs_eval_fn *eva return NULL; } - if (eval_fn != eval_prune && eval_fn != eval_quit) { + if (eval_fn != eval_limit && eval_fn != eval_prune && eval_fn != eval_quit) { parser->implicit_print = false; } @@ -1569,6 +1571,29 @@ static struct bfs_expr *parse_jobs(struct bfs_parser *parser, int arg1, int arg2 return expr; } +/** + * Parse -limit N. + */ +static struct bfs_expr *parse_limit(struct bfs_parser *parser, int arg1, int arg2) { + struct bfs_expr *expr = parse_unary_action(parser, eval_limit); + if (!expr) { + return NULL; + } + + char **arg = &expr->argv[1]; + if (!parse_int(parser, arg, *arg, &expr->num, IF_LONG_LONG)) { + return NULL; + } + + if (expr->num <= 0) { + parse_expr_error(parser, expr, "The ${blu}%s${rs} must be at least ${bld}1${rs}.\n", expr->argv[0]); + return NULL; + } + + parser->limit_arg = expr->argv; + return expr; +} + /** * Parse -links N. */ @@ -2845,6 +2870,8 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2 cfprintf(cout, " ${blu}-fprintf${rs} ${bld}FILE${rs} ${bld}FORMAT${rs}\n"); cfprintf(cout, " Like ${blu}-ls${rs}/${blu}-print${rs}/${blu}-print0${rs}/${blu}-printf${rs}, but write to ${bld}FILE${rs} instead of standard\n" " output\n"); + cfprintf(cout, " ${blu}-limit${rs} ${bld}N${rs}\n"); + cfprintf(cout, " Quit after this action is evaluated ${bld}N${rs} times\n"); cfprintf(cout, " ${blu}-ls${rs}\n"); cfprintf(cout, " List files like ${ex}ls${rs} ${bld}-dils${rs}\n"); cfprintf(cout, " ${blu}-print${rs}\n"); @@ -2968,6 +2995,7 @@ static const struct table_entry parse_table[] = { {"-iregex", T_TEST, parse_regex, BFS_REGEX_ICASE}, {"-iwholename", T_TEST, parse_path, true}, {"-j", T_FLAG, parse_jobs, 0, 0, true}, + {"-limit", T_ACTION, parse_limit}, {"-links", T_TEST, parse_links}, {"-lname", T_TEST, parse_lname, false}, {"-ls", T_ACTION, parse_ls}, @@ -3330,6 +3358,14 @@ static struct bfs_expr *parse_whole_expr(struct bfs_parser *parser) { } if (parser->implicit_print) { + char **limit = parser->limit_arg; + if (limit) { + parse_argv_error(parser, parser->limit_arg, 2, + "With ${blu}%s${rs}, you must specify an action explicitly; for example, ${blu}-print${rs} ${blu}%s${rs} ${bld}%s${rs}.\n", + limit[0], limit[0], limit[1]); + return NULL; + } + struct bfs_expr *print = parse_new_expr(parser, eval_fprint, 1, &fake_print_arg); if (!print) { return NULL; diff --git a/tests/bfs/limit.out b/tests/bfs/limit.out new file mode 100644 index 0000000..ea94276 --- /dev/null +++ b/tests/bfs/limit.out @@ -0,0 +1,4 @@ +basic/a +basic/b +basic/c/d +basic/e/f diff --git a/tests/bfs/limit.sh b/tests/bfs/limit.sh new file mode 100644 index 0000000..84b605f --- /dev/null +++ b/tests/bfs/limit.sh @@ -0,0 +1 @@ +bfs_diff -s basic -type f -print -limit 4 diff --git a/tests/bfs/limit_0.sh b/tests/bfs/limit_0.sh new file mode 100644 index 0000000..3ce26de --- /dev/null +++ b/tests/bfs/limit_0.sh @@ -0,0 +1 @@ +! invoke_bfs basic -print -limit 0 diff --git a/tests/bfs/limit_implicit_print.sh b/tests/bfs/limit_implicit_print.sh new file mode 100644 index 0000000..cdb059d --- /dev/null +++ b/tests/bfs/limit_implicit_print.sh @@ -0,0 +1 @@ +! invoke_bfs basic -type f -limit 1 diff --git a/tests/bfs/limit_incomplete.sh b/tests/bfs/limit_incomplete.sh new file mode 100644 index 0000000..2d1e842 --- /dev/null +++ b/tests/bfs/limit_incomplete.sh @@ -0,0 +1 @@ +! invoke_bfs basic -print -limit diff --git a/tests/bfs/limit_one.sh b/tests/bfs/limit_one.sh new file mode 100644 index 0000000..3f8181c --- /dev/null +++ b/tests/bfs/limit_one.sh @@ -0,0 +1 @@ +! invoke_bfs basic -print -limit one -- cgit v1.2.3 From 745fd4be765407f7c56d61c281c28e2468ba25b5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 20 Mar 2024 16:41:24 -0400 Subject: ioq: Fix some allocation failure paths --- src/ioq.c | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 37eed7d..b936681 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -1064,7 +1064,7 @@ void ioq_free(struct ioq *ioq, struct ioq_ent *ent) { --ioq->size; #if BFS_USE_LIBURING && BFS_USE_STATX - if (ent->op == IOQ_STAT) { + if (ent->op == IOQ_STAT && ent->stat.xbuf) { arena_free(&ioq->xbufs, ent->stat.xbuf); } #endif @@ -1083,7 +1083,9 @@ void ioq_destroy(struct ioq *ioq) { return; } - ioq_cancel(ioq); + if (ioq->nthreads > 0) { + ioq_cancel(ioq); + } for (size_t i = 0; i < ioq->nthreads; ++i) { ioq_thread_join(&ioq->threads[i]); -- cgit v1.2.3 From 54490a29006529f7ceb4dc0514a075f9f4175621 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 20 Mar 2024 16:56:46 -0400 Subject: bfstd: Check that wcwidth() is positive wcwidth() returns -1 for non-printable characters, but terminals typically don't print anything for them, so treat them as 0. --- src/bfstd.c | 8 ++++++-- tests/bfstd.c | 3 +++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index c6c2e7f..5b64452 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -695,8 +695,12 @@ size_t xstrwidth(const char *str) { if (wc == WEOF) { // Assume a single-width '?' ++ret; - } else { - ret += xwcwidth(wc); + continue; + } + + int width = xwcwidth(wc); + if (width > 0) { + ret += width; } } diff --git a/tests/bfstd.c b/tests/bfstd.c index 26abdb6..1351e11 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -69,5 +69,8 @@ bool check_bfstd(void) { ret &= check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY); } + ret &= bfs_check(xstrwidth("Hello world") == 11); + ret &= bfs_check(xstrwidth("Hello\1world") == 10); + return ret; } -- cgit v1.2.3 From ef0aef16961508075249a6206dc0e9a9ac27d81c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 21 Mar 2024 11:52:31 -0400 Subject: bit: Check __BYTE_ORDER__ for the native endian __ORDER_NATIVE_ENDIAN__ is not a thing. --- src/bit.h | 4 ++-- tests/bit.c | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/bit.h b/src/bit.h index c469305..69df21e 100644 --- a/src/bit.h +++ b/src/bit.h @@ -167,8 +167,8 @@ #ifdef __STDC_ENDIAN_NATIVE__ # define ENDIAN_NATIVE __STDC_ENDIAN_NATIVE__ -#elif defined(__ORDER_NATIVE_ENDIAN__) -# define ENDIAN_NATIVE __ORDER_NATIVE_ENDIAN__ +#elif defined(__BYTE_ORDER__) +# define ENDIAN_NATIVE __BYTE_ORDER__ #else # define ENDIAN_NATIVE 0 #endif diff --git a/tests/bit.c b/tests/bit.c index 3d66ce3..6548c30 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -7,6 +7,7 @@ #include "../src/diag.h" #include #include +#include bfs_static_assert(UMAX_WIDTH(0x1) == 1); bfs_static_assert(UMAX_WIDTH(0x3) == 2); @@ -58,6 +59,18 @@ bfs_static_assert(INTMAX_MAX == IWIDTH_MAX(INTMAX_WIDTH)); bool check_bit(void) { bool ret = true; + const char *str = "\x1\x2\x3\x4"; + uint32_t word; + memcpy(&word, str, sizeof(word)); + +#if ENDIAN_NATIVE == ENDIAN_LITTLE + ret &= check_eq(word, 0x04030201); +#elif ENDIAN_NATIVE == ENDIAN_BIG + ret &= check_eq(word, 0x01020304); +#else +# warning "Skipping byte order tests on mixed/unknown-endian machine" +#endif + ret &= check_eq(bswap((uint8_t)0x12), 0x12); ret &= check_eq(bswap((uint16_t)0x1234), 0x3412); ret &= check_eq(bswap((uint32_t)0x12345678), 0x78563412); -- cgit v1.2.3 From a2a6ac8edd5d85398f6edb6afd02e683d9da9e7b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 21 Mar 2024 11:55:38 -0400 Subject: bfstd: New asciilen() function --- src/bfstd.c | 68 +++++++++++++++++++++++++++++++++++++---------------------- src/bfstd.h | 15 +++++++++++++ tests/bfstd.c | 15 +++++++++++++ 3 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 5b64452..43513b8 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -2,6 +2,7 @@ // SPDX-License-Identifier: 0BSD #include "bfstd.h" +#include "bit.h" #include "config.h" #include "diag.h" #include "sanity.h" @@ -670,6 +671,44 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * #endif } +size_t asciilen(const char *str) { + return asciinlen(str, strlen(str)); +} + +size_t asciinlen(const char *str, size_t n) { + size_t i = 0; + +#if SIZE_WIDTH % 8 == 0 + // Word-at-a-time isascii() + for (size_t word; i + sizeof(word) <= n; i += sizeof(word)) { + memcpy(&word, str + i, sizeof(word)); + + const size_t mask = (SIZE_MAX / 0xFF) << 7; // 0x808080... + word &= mask; + if (!word) { + continue; + } + +#if ENDIAN_NATIVE == ENDIAN_BIG + word = bswap(word); +#elif ENDIAN_NATIVE != ENDIAN_LITTLE + break; +#endif + + size_t first = trailing_zeros(word) / 8; + return i + first; + } +#endif + + for (; i < n; ++i) { + if (!xisascii(str[i])) { + break; + } + } + + return i; +} + wint_t xmbrtowc(const char *str, size_t *i, size_t len, mbstate_t *mb) { wchar_t wc; size_t mblen = mbrtowc(&wc, str + *i, len - *i, mb); @@ -765,36 +804,15 @@ static size_t printable_len(const char *str, size_t len, enum wesc_flags flags) invoke_once(&once, char_cache_init); // Fast path: avoid multibyte checks - size_t i, word; - for (i = 0; i + sizeof(word) <= len;) { - // Word-at-a-time isascii() - memcpy(&word, str + i, sizeof(word)); - // 0xFFFF... / 0xFF == 0x10101... - size_t mask = (SIZE_MAX / 0xFF) << 7; - if (word & mask) { - goto multibyte; - } - - for (size_t j = 0; j < sizeof(word); ++i, ++j) { - if (!wesc_isprint(str[i], flags)) { - return i; - } - } - } - - for (; i < len; ++i) { - char c = str[i]; - if (!xisascii(c)) { - goto multibyte; - } - if (!wesc_isprint(c, flags)) { + size_t asclen = asciinlen(str, len); + size_t i; + for (i = 0; i < asclen; ++i) { + if (!wesc_isprint(str[i], flags)) { return i; } } -multibyte:; mbstate_t mb = {0}; - for (size_t j = i; i < len; i = j) { wint_t wc = xmbrtowc(str, &j, len, &mb); if (wc == WEOF) { diff --git a/src/bfstd.h b/src/bfstd.h index d160c88..fc22971 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -177,6 +177,21 @@ int ynprompt(void); // #include +/** + * Get the length of the pure-ASCII prefix of a string. + */ +size_t asciilen(const char *str); + +/** + * Get the length of the pure-ASCII prefix of a string. + * + * @param str + * The string to check. + * @param n + * The maximum prefix length. + */ +size_t asciinlen(const char *str, size_t n); + /** * Allocate a copy of a region of memory. * diff --git a/tests/bfstd.c b/tests/bfstd.c index 1351e11..3ffab41 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -40,6 +40,21 @@ static bool check_wordesc(const char *str, const char *exp, enum wesc_flags flag bool check_bfstd(void) { bool ret = true; + ret &= bfs_check(asciilen("") == 0); + ret &= bfs_check(asciilen("@") == 1); + ret &= bfs_check(asciilen("@@") == 2); + ret &= bfs_check(asciilen("\xFF@") == 0); + ret &= bfs_check(asciilen("@\xFF") == 1); + ret &= bfs_check(asciilen("@@@@@@@@") == 8); + ret &= bfs_check(asciilen("@@@@@@@@@@@@@@@@") == 16); + ret &= bfs_check(asciilen("@@@@@@@@@@@@@@@@@@@@@@@@") == 24); + ret &= bfs_check(asciilen("@@@@@@@@@@@@@@a\xFF@@@@@@@") == 15); + ret &= bfs_check(asciilen("@@@@@@@@@@@@@@@@\xFF@@@@@@@") == 16); + ret &= bfs_check(asciilen("@@@@@@@@@@@@@@@@a\xFF@@@@@@") == 17); + ret &= bfs_check(asciilen("@@@@@@@\xFF@@@@@@a\xFF@@@@@@@") == 7); + ret &= bfs_check(asciilen("@@@@@@@@\xFF@@@@@a\xFF@@@@@@@") == 8); + ret &= bfs_check(asciilen("@@@@@@@@@\xFF@@@@a\xFF@@@@@@@") == 9); + // From man 3p basename ret &= check_base_dir("usr", ".", "usr"); ret &= check_base_dir("usr/", ".", "usr"); -- cgit v1.2.3 From 43fbffc232679ddd286f7c527755d15872c73d01 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 21 Mar 2024 11:55:49 -0400 Subject: bfstd: Add an ASCII fast path to xstrwidth() --- src/bfstd.c | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/bfstd.c b/src/bfstd.c index 43513b8..12af438 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -728,8 +728,17 @@ size_t xstrwidth(const char *str) { size_t len = strlen(str); size_t ret = 0; + size_t asclen = asciinlen(str, len); + size_t i; + for (i = 0; i < asclen; ++i) { + // Assume all ASCII printables have width 1 + if (xisprint(str[i])) { + ++ret; + } + } + mbstate_t mb = {0}; - for (size_t i = 0; i < len;) { + while (i < len) { wint_t wc = xmbrtowc(str, &i, len, &mb); if (wc == WEOF) { // Assume a single-width '?' -- cgit v1.2.3 From c643e1dd27e633c67589b622a06582bbb6fb5cf6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 22 Mar 2024 14:06:04 -0400 Subject: Fix some -Wpedantic warnings --- src/config.h | 6 ++---- src/opt.c | 12 ++++++------ 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/config.h b/src/config.h index 45422a2..892f24c 100644 --- a/src/config.h +++ b/src/config.h @@ -192,9 +192,7 @@ typedef long double max_align_t; /** * Silence warnings about switch/case fall-throughs. */ -#if __has_c_attribute(fallthrough) -# define fallthru [[fallthrough]] -#elif __has_attribute(fallthrough) +#if __has_attribute(fallthrough) # define fallthru __attribute__((fallthrough)) #else # define fallthru ((void)0) @@ -309,7 +307,7 @@ typedef long double max_align_t; * attr_d */ #define attr(...) \ - attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none) + attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none, ) /** * attr() helper. For exposition, pretend we support only 2 args, instead of 9. diff --git a/src/opt.c b/src/opt.c index a470d25..b74b4e1 100644 --- a/src/opt.c +++ b/src/opt.c @@ -724,7 +724,7 @@ struct visitor { visit_fn *leave; /** A visitor lookup table. */ - struct visitor_table table[]; + const struct visitor_table *table; }; /** Recursive visitor implementation. */ @@ -1331,7 +1331,7 @@ static struct bfs_expr *annotate_visit(struct bfs_opt *opt, struct bfs_expr *exp static const struct visitor annotate = { .name = "annotate", .visit = annotate_visit, - .table = { + .table = (const struct visitor_table[]) { {eval_access, annotate_access}, {eval_empty, annotate_empty}, {eval_exec, annotate_exec}, @@ -1516,7 +1516,7 @@ static struct bfs_expr *canonicalize_assoc(struct bfs_opt *opt, struct bfs_expr */ static const struct visitor canonicalize = { .name = "canonicalize", - .table = { + .table = (const struct visitor_table[]) { {eval_not, canonicalize_not}, {eval_and, canonicalize_assoc}, {eval_or, canonicalize_assoc}, @@ -1609,7 +1609,7 @@ static struct bfs_expr *reorder_andor(struct bfs_opt *opt, struct bfs_expr *expr */ static const struct visitor reorder = { .name = "reorder", - .table = { + .table = (const struct visitor_table[]) { {eval_and, reorder_andor}, {eval_or, reorder_andor}, {NULL, NULL}, @@ -1887,7 +1887,7 @@ static const struct visitor data_flow = { .enter = data_flow_enter, .visit = data_flow_visit, .leave = data_flow_leave, - .table = { + .table = (const struct visitor_table[]) { {eval_access, data_flow_access}, {eval_gid, data_flow_gid}, {eval_inum, data_flow_inum}, @@ -2103,7 +2103,7 @@ static struct bfs_expr *simplify_comma(struct bfs_opt *opt, struct bfs_expr *exp */ static const struct visitor simplify = { .name = "simplify", - .table = { + .table = (const struct visitor_table[]) { {eval_not, simplify_not}, {eval_and, simplify_and}, {eval_or, simplify_or}, -- cgit v1.2.3 From 9827b2bae1d3864dffb06b50e2f59af594154693 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 22 Mar 2024 14:16:58 -0400 Subject: bftw: Use a signed integer for dir_limit --- src/bftw.c | 33 ++++++++------------------------- 1 file changed, 8 insertions(+), 25 deletions(-) diff --git a/src/bftw.c b/src/bftw.c index 50b8b02..6130c44 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -558,9 +558,7 @@ struct bftw_cache { /** bfs_dir arena. */ struct arena dirs; /** Remaining bfs_dir capacity. */ - size_t dir_limit; - /** Excess force-allocated bfs_dirs. */ - size_t dir_excess; + int dir_limit; /** bfs_stat arena. */ struct arena stat_bufs; @@ -576,47 +574,32 @@ static void bftw_cache_init(struct bftw_cache *cache, size_t capacity) { bfs_dir_arena(&cache->dirs); - cache->dir_limit = capacity - 1; - if (cache->dir_limit > 1024) { + if (cache->capacity > 1024) { cache->dir_limit = 1024; + } else { + cache->dir_limit = capacity - 1; } - cache->dir_excess = 0; - ARENA_INIT(&cache->stat_bufs, struct bfs_stat); } /** Allocate a directory. */ static struct bfs_dir *bftw_allocdir(struct bftw_cache *cache, bool force) { - size_t limit = cache->dir_limit; - size_t excess = cache->dir_excess; - - if (cache->dir_limit > 0) { - --cache->dir_limit; - } else if (force) { - ++cache->dir_excess; - } else { + if (!force && cache->dir_limit <= 0) { errno = ENOMEM; return NULL; } struct bfs_dir *dir = arena_alloc(&cache->dirs); - if (!dir) { - cache->dir_limit = limit; - cache->dir_excess = excess; + if (dir) { + --cache->dir_limit; } - return dir; } /** Free a directory. */ static void bftw_freedir(struct bftw_cache *cache, struct bfs_dir *dir) { - if (cache->dir_excess > 0) { - --cache->dir_excess; - } else { - ++cache->dir_limit; - } - + ++cache->dir_limit; arena_free(&cache->dirs, dir); } -- cgit v1.2.3 From 74f388a893add09ba6a1e35151e0d2600cd688cb Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 23 Mar 2024 10:21:44 -0400 Subject: trie: Calculate representative indices branchlessly --- src/trie.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/trie.c b/src/trie.c index 1ffb23a..f275064 100644 --- a/src/trie.c +++ b/src/trie.c @@ -206,9 +206,10 @@ static struct trie_leaf *trie_representative(const struct trie *trie, const void if ((offset >> 1) < length) { unsigned char nibble = trie_key_nibble(key, offset); unsigned int bit = 1U << nibble; - if (node->bitmap & bit) { - index = count_ones(node->bitmap & (bit - 1)); - } + // bits = bitmap & bit ? bitmap & (bit - 1) : 0 + unsigned int mask = -!!(node->bitmap & bit); + unsigned int bits = node->bitmap & (bit - 1) & mask; + index = count_ones(bits); } ptr = node->children[index]; } -- cgit v1.2.3 From dc885d2e4cb12d3a3444cea7f754a691f6d200c4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 25 Mar 2024 13:28:06 -0400 Subject: completions: Add -j --- completions/bfs.bash | 1 + completions/bfs.fish | 1 + completions/bfs.zsh | 9 +++++---- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/completions/bfs.bash b/completions/bfs.bash index db582da..816f1ec 100644 --- a/completions/bfs.bash +++ b/completions/bfs.bash @@ -18,6 +18,7 @@ _bfs() { -fstype -gid -group + -j -ok -okdir -regextype diff --git a/completions/bfs.fish b/completions/bfs.fish index 1303639..0c58ef4 100644 --- a/completions/bfs.fish +++ b/completions/bfs.fish @@ -20,6 +20,7 @@ complete -c bfs -o f -d "Treat specified path as a path to search" -a "(__fish_c complete -c bfs -o D -d "Turn on a debugging flag" -a $debug_flag_comp -x complete -c bfs -s O -d "Enable specified optimization level" -a $optimization_comp -x complete -c bfs -o S -d "Choose the search strategy" -a $strategy_comp -x +complete -c bfs -s j -d "Use this many threads" -x # Operators diff --git a/completions/bfs.zsh b/completions/bfs.zsh index 51b5029..07db456 100644 --- a/completions/bfs.zsh +++ b/completions/bfs.zsh @@ -10,7 +10,7 @@ args=( '-D[print diagnostics]:debug option:(cost exec opt rates search stat time tree all help)' '-E[use extended regular expressions with -regex/-iregex]' '-f[specify file hierarchy to traverse]:path:_directories' - '-O+[enable query optimisation]:level:(1 2 3)' + '-O+[enable query optimisation]:level:(0 1 2 3 4 fast)' '-s[traverse directories in sorted order]' '-X[warn if filename contains characters special to xargs]' "-x[don't span filesystems]" @@ -19,6 +19,7 @@ args=( '(-L -P)-H[only follow symlinks when resolving command-line arguments]' "-S[select search method]:value:(bfs dfs ids eds)" '-f[treat path as path to search]:path:_files -/' + '-j+[use this many threads]:threads:' # Operators '*-and' @@ -81,7 +82,7 @@ args=( '*-false[always false]' '*-true[always true]' '*-fstype[find files on file systems with the given type]:file system type:_file_systems' - + '*-gid[find files owned by group ID N]:numeric group ID:' '*-group[find files owned by group NAME]:group:_groups' '*-uid[find files owned by user ID N]:numeric user ID' @@ -117,7 +118,7 @@ args=( '*-xattr[find files with extended attributes]' '*-xattrname[find files with extended attribute NAME]:name:' '*-xtype[find files of the given type following links when -type would not, and vice versa]:file type:((b\:block\ device c\:character\ device d\:directory p\:named\ pipe f\:normal\ file l\:symbolic\ link s\:socket w\:whiteout D\:Door))' - + # Actions '*-delete[delete any found files (-implies -depth)]' '*-rm[delete any found files (-implies -depth)]' @@ -126,7 +127,7 @@ args=( '*-execdir[execute a command in the same directory as the found files]:program: _command_names -e:*(\;|+)::program arguments: _normal' '*-ok[prompt the user whether to execute a command]:program: _command_names -e:*(\;|+)::program arguments: _normal' '*-okdir[prompt the user whether to execute a command in the same directory as the found files]:program: _command_names -e:*(\;|+)::program arguments: _normal' - + '-exit[exit with status if found, default 0]' '*-fls[list files like ls -dils, but write to FILE instead of standard output]:output file:_files' '*-fprint[print the path to the found file, but write to FILE instead of standard output]:output file:_files' -- cgit v1.2.3 From 7e25b9c6e718437ed45aa2592598c63f0f87e70a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 26 Mar 2024 11:42:58 -0400 Subject: xtime: Don't update tm if xtimegm() overflows --- src/xtime.c | 62 +++++++++++++++++++++++++++++++---------------------------- tests/xtime.c | 34 ++++++++++++++++++++------------ 2 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/xtime.c b/src/xtime.c index 5b259ab..bcf6dd3 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -69,73 +69,77 @@ static int month_length(int year, int month) { } int xtimegm(struct tm *tm, time_t *timep) { - tm->tm_isdst = 0; + struct tm copy = *tm; + copy.tm_isdst = 0; - if (wrap(&tm->tm_sec, 60, &tm->tm_min) != 0) { + if (wrap(©.tm_sec, 60, ©.tm_min) != 0) { goto overflow; } - if (wrap(&tm->tm_min, 60, &tm->tm_hour) != 0) { + if (wrap(©.tm_min, 60, ©.tm_hour) != 0) { goto overflow; } - if (wrap(&tm->tm_hour, 24, &tm->tm_mday) != 0) { + if (wrap(©.tm_hour, 24, ©.tm_mday) != 0) { goto overflow; } // In order to wrap the days of the month, we first need to know what // month it is - if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + if (wrap(©.tm_mon, 12, ©.tm_year) != 0) { goto overflow; } - if (tm->tm_mday < 1) { + if (copy.tm_mday < 1) { do { - --tm->tm_mon; - if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + --copy.tm_mon; + if (wrap(©.tm_mon, 12, ©.tm_year) != 0) { goto overflow; } - tm->tm_mday += month_length(tm->tm_year, tm->tm_mon); - } while (tm->tm_mday < 1); + copy.tm_mday += month_length(copy.tm_year, copy.tm_mon); + } while (copy.tm_mday < 1); } else { while (true) { - int days = month_length(tm->tm_year, tm->tm_mon); - if (tm->tm_mday <= days) { + int days = month_length(copy.tm_year, copy.tm_mon); + if (copy.tm_mday <= days) { break; } - tm->tm_mday -= days; - ++tm->tm_mon; - if (wrap(&tm->tm_mon, 12, &tm->tm_year) != 0) { + copy.tm_mday -= days; + ++copy.tm_mon; + if (wrap(©.tm_mon, 12, ©.tm_year) != 0) { goto overflow; } } } - tm->tm_yday = 0; - for (int i = 0; i < tm->tm_mon; ++i) { - tm->tm_yday += month_length(tm->tm_year, i); + copy.tm_yday = 0; + for (int i = 0; i < copy.tm_mon; ++i) { + copy.tm_yday += month_length(copy.tm_year, i); } - tm->tm_yday += tm->tm_mday - 1; + copy.tm_yday += copy.tm_mday - 1; int leap_days; // Compute floor((year - 69)/4) - floor((year - 1)/100) + floor((year + 299)/400) without overflows - if (tm->tm_year >= 0) { - leap_days = floor_div(tm->tm_year - 69, 4) - floor_div(tm->tm_year - 1, 100) + floor_div(tm->tm_year - 101, 400) + 1; + if (copy.tm_year >= 0) { + leap_days = floor_div(copy.tm_year - 69, 4) - floor_div(copy.tm_year - 1, 100) + floor_div(copy.tm_year - 101, 400) + 1; } else { - leap_days = floor_div(tm->tm_year + 3, 4) - floor_div(tm->tm_year + 99, 100) + floor_div(tm->tm_year + 299, 400) - 17; + leap_days = floor_div(copy.tm_year + 3, 4) - floor_div(copy.tm_year + 99, 100) + floor_div(copy.tm_year + 299, 400) - 17; } - long long epoch_days = 365LL * (tm->tm_year - 70) + leap_days + tm->tm_yday; - tm->tm_wday = (epoch_days + 4) % 7; - if (tm->tm_wday < 0) { - tm->tm_wday += 7; + long long epoch_days = 365LL * (copy.tm_year - 70) + leap_days + copy.tm_yday; + copy.tm_wday = (epoch_days + 4) % 7; + if (copy.tm_wday < 0) { + copy.tm_wday += 7; } - long long epoch_time = tm->tm_sec + 60 * (tm->tm_min + 60 * (tm->tm_hour + 24 * epoch_days)); - *timep = (time_t)epoch_time; - if ((long long)*timep != epoch_time) { + long long epoch_time = copy.tm_sec + 60 * (copy.tm_min + 60 * (copy.tm_hour + 24 * epoch_days)); + time_t time = (time_t)epoch_time; + if ((long long)time != epoch_time) { goto overflow; } + + *tm = copy; + *timep = time; return 0; overflow: diff --git a/tests/xtime.c b/tests/xtime.c index c8dc00b..ec499d8 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -137,6 +137,24 @@ static bool check_one_xtimegm(const struct tm *tm) { return ret; } +/** Check an overflowing xtimegm() call. */ +static bool check_xtimegm_overflow(const struct tm *tm) { + struct tm copy = *tm; + time_t time = 123; + + bool ret = true; + ret &= bfs_check(xtimegm(©, &time) == -1 && errno == EOVERFLOW); + ret &= bfs_check(tm_equal(©, tm)); + ret &= bfs_check(time == 123); + + if (!ret) { + bfs_diag("xtimegm(): " TM_FORMAT, TM_PRINTF(copy)); + bfs_diag("(input): " TM_FORMAT, TM_PRINTF(*tm)); + } + + return ret; +} + /** xtimegm() tests. */ static bool check_xtimegm(void) { bool ret = true; @@ -144,7 +162,6 @@ static bool check_xtimegm(void) { struct tm tm = { .tm_isdst = -1, }; - time_t time; // Check equivalence with mktime() for (tm.tm_year = 10; tm.tm_year <= 200; tm.tm_year += 10) @@ -157,17 +174,10 @@ static bool check_xtimegm(void) { } // Check integer overflow cases - tm = (struct tm){ .tm_sec = INT_MAX, .tm_min = INT_MAX }; - ret &= bfs_check(xtimegm(&tm, &time) == -1 && errno == EOVERFLOW); - - tm = (struct tm){ .tm_min = INT_MAX, .tm_hour = INT_MAX }; - ret &= bfs_check(xtimegm(&tm, &time) == -1 && errno == EOVERFLOW); - - tm = (struct tm){ .tm_hour = INT_MAX, .tm_mday = INT_MAX }; - ret &= bfs_check(xtimegm(&tm, &time) == -1 && errno == EOVERFLOW); - - tm = (struct tm){ .tm_mon = INT_MAX, .tm_year = INT_MAX }; - ret &= bfs_check(xtimegm(&tm, &time) == -1 && errno == EOVERFLOW); + check_xtimegm_overflow(&(struct tm) { .tm_sec = INT_MAX, .tm_min = INT_MAX }); + check_xtimegm_overflow(&(struct tm) { .tm_min = INT_MAX, .tm_hour = INT_MAX }); + check_xtimegm_overflow(&(struct tm) { .tm_hour = INT_MAX, .tm_mday = INT_MAX }); + check_xtimegm_overflow(&(struct tm) { .tm_mon = INT_MAX, .tm_year = INT_MAX }); return ret; } -- cgit v1.2.3 From 310bc847bca9e2457700598be99390867fd2a84e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 26 Mar 2024 12:18:50 -0400 Subject: bfstd: Escape ASCII tildes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The POSIX spec [1] lists some characters that may need to be escaped. Unfortunately, the document uses ˜ (U+02DC SMALL TILDE) instead of ~ (U+007E TILDE), and I copy-pasted from it. [1]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 --- src/bfstd.c | 2 +- tests/bfstd.c | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/src/bfstd.c b/src/bfstd.c index 12af438..2499f00 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -908,7 +908,7 @@ static char *dollar_quote(char *dest, char *end, const char *str, size_t len, en /** How much of this string is safe as a bare word? */ static size_t bare_len(const char *str, size_t len) { // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_02 - size_t ret = strcspn(str, "|&;<>()$`\\\"' *?[#˜=%!{}"); + size_t ret = strcspn(str, "|&;<>()$`\\\"' *?[#~=%!{}"); return ret < len ? ret : len; } diff --git a/tests/bfstd.c b/tests/bfstd.c index 3ffab41..dc5ceaa 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -75,6 +75,7 @@ bool check_bfstd(void) { ret &= check_wordesc("\"word's\"", "'\"word'\\''s\"'", WESC_SHELL); ret &= check_wordesc("\033[1mbold's\033[0m", "$'\\e[1mbold\\'s\\e[0m'", WESC_SHELL | WESC_TTY); ret &= check_wordesc("\x7F", "$'\\x7F'", WESC_SHELL | WESC_TTY); + ret &= check_wordesc("~user", "\"~user\"", WESC_SHELL); const char *charmap = nl_langinfo(CODESET); if (strcmp(charmap, "UTF-8") == 0) { @@ -82,6 +83,7 @@ bool check_bfstd(void) { ret &= check_wordesc("\xF0\x9F", "$'\\xF0\\x9F'", WESC_SHELL | WESC_TTY); ret &= check_wordesc("\xF0\x9F\x98", "$'\\xF0\\x9F\\x98'", WESC_SHELL | WESC_TTY); ret &= check_wordesc("\xF0\x9F\x98\x80", "\xF0\x9F\x98\x80", WESC_SHELL | WESC_TTY); + ret &= check_wordesc("\xCB\x9Cuser", "\xCB\x9Cuser", WESC_SHELL); } ret &= bfs_check(xstrwidth("Hello world") == 11); -- cgit v1.2.3 From c01d1f48c950fc0c942988ad1fcdd8003ac93898 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Mar 2024 11:09:26 -0400 Subject: xspawn: Implement bfs_spawn_addopen() --- src/xspawn.c | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- src/xspawn.h | 8 ++++++++ 2 files changed, 67 insertions(+), 3 deletions(-) diff --git a/src/xspawn.c b/src/xspawn.c index 6a94d3d..e739c5f 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -26,6 +26,7 @@ * Types of spawn actions. */ enum bfs_spawn_op { + BFS_SPAWN_OPEN, BFS_SPAWN_CLOSE, BFS_SPAWN_DUP2, BFS_SPAWN_FCHDIR, @@ -36,13 +37,31 @@ enum bfs_spawn_op { * A spawn action. */ struct bfs_spawn_action { + /** The next action in the list. */ struct bfs_spawn_action *next; + /** This action's operation. */ enum bfs_spawn_op op; + /** The input fd (or -1). */ int in_fd; + /** The output fd (or -1). */ int out_fd; - int resource; - struct rlimit rlimit; + + /** Operation-specific args. */ + union { + /** BFS_SPAWN_OPEN args. */ + struct { + const char *path; + int flags; + mode_t mode; + }; + + /** BFS_SPAWN_SETRLIMIT args. */ + struct { + int resource; + struct rlimit rlimit; + }; + }; }; int bfs_spawn_init(struct bfs_spawn *ctx) { @@ -116,6 +135,30 @@ static struct bfs_spawn_action *bfs_spawn_action(enum bfs_spawn_op op) { return action; } +int bfs_spawn_addopen(struct bfs_spawn *ctx, int fd, const char *path, int flags, mode_t mode) { + struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_OPEN); + if (!action) { + return -1; + } + +#if _POSIX_SPAWN > 0 + if (ctx->flags & BFS_SPAWN_USE_POSIX) { + errno = posix_spawn_file_actions_addopen(&ctx->actions, fd, path, flags, mode); + if (errno != 0) { + free(action); + return -1; + } + } +#endif + + action->out_fd = fd; + action->path = path; + action->flags = flags; + action->mode = mode; + SLIST_APPEND(ctx, action); + return 0; +} + int bfs_spawn_addclose(struct bfs_spawn *ctx, int fd) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_CLOSE); if (!action) { @@ -249,9 +292,11 @@ static noreturn void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx xclose(pipefd[0]); for_slist (const struct bfs_spawn_action, action, ctx) { + int fd; + // Move the error-reporting pipe out of the way if necessary... if (action->out_fd == pipefd[1]) { - int fd = dup_cloexec(pipefd[1]); + fd = dup_cloexec(pipefd[1]); if (fd < 0) { goto fail; } @@ -266,6 +311,17 @@ static noreturn void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx } switch (action->op) { + case BFS_SPAWN_OPEN: + fd = open(action->path, action->flags, action->mode); + if (fd < 0) { + goto fail; + } + if (fd != action->out_fd) { + if (dup2(fd, action->out_fd) < 0) { + goto fail; + } + } + break; case BFS_SPAWN_CLOSE: if (close(action->out_fd) != 0) { goto fail; diff --git a/src/xspawn.h b/src/xspawn.h index 77273ee..a20cbd0 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -61,6 +61,14 @@ int bfs_spawn_init(struct bfs_spawn *ctx); */ int bfs_spawn_destroy(struct bfs_spawn *ctx); +/** + * Add an open() action to a bfs_spawn() context. + * + * @return + * 0 on success, -1 on failure. + */ +int bfs_spawn_addopen(struct bfs_spawn *ctx, int fd, const char *path, int flags, mode_t mode); + /** * Add a close() action to a bfs_spawn() context. * -- cgit v1.2.3 From 42972dfac6cd8faef6b1e2ddc6236d554daf433f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Mar 2024 11:15:46 -0400 Subject: xspawn: Refactor $PATH resolution Technically, we should be resolving executables *after* applying the file actions. It's only safe to resolve earlier if $PATH contains no relative entries, or if there are no fchdir() actions. The new implementation resolves as early as possible, deferring to posix_spawnp() if necessary for correctness. --- src/xspawn.c | 321 ++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 240 insertions(+), 81 deletions(-) diff --git a/src/xspawn.c b/src/xspawn.c index e739c5f..dc2bf67 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -274,11 +274,219 @@ fail: return -1; } +/** + * Context for resolving executables in the $PATH. + */ +struct bfs_resolver { + /** The executable to spawn. */ + const char *exe; + /** The $PATH to resolve in. */ + char *path; + /** A buffer to hold the resolved path. */ + char *buf; + /** The size of the buffer. */ + size_t len; + /** Whether the executable is already resolved. */ + bool done; + /** Whether to free(path). */ + bool free; +}; + +/** Free a $PATH resolution context. */ +static void bfs_resolve_free(struct bfs_resolver *res) { + if (res->free) { + free(res->path); + } + free(res->buf); +} + +/** Get the next component in the $PATH. */ +static bool bfs_resolve_next(const char **path, const char **next, size_t *len) { + *path = *next; + if (!*path) { + return false; + } + + *next = strchr(*path, ':'); + if (*next) { + *len = *next - *path; + ++*next; + } else { + *len = strlen(*path); + } + + if (*len == 0) { + // POSIX 8.3: "A zero-length prefix is a legacy feature that + // indicates the current working directory." + *path = "."; + *len = 1; + } + + return true; +} + +/** Finish resolving an executable, potentially from the child process. */ +static int bfs_resolve_late(struct bfs_resolver *res) { + if (res->done) { + return 0; + } + + char *buf = res->buf; + char *end = buf + res->len; + + const char *path; + const char *next = res->path; + size_t len; + while (bfs_resolve_next(&path, &next, &len)) { + char *cur = xstpencpy(buf, end, path, len); + cur = xstpecpy(cur, end, "/"); + cur = xstpecpy(cur, end, res->exe); + if (cur == end) { + bfs_bug("PATH resolution buffer too small"); + errno = ENOMEM; + return -1; + } + + if (xfaccessat(AT_FDCWD, buf, X_OK) == 0) { + res->exe = buf; + res->done = true; + return 0; + } + + if (end) { + path = end + 1; + } else { + errno = ENOENT; + return -1; + } + } + + return 0; +} + +/** Check if we can skip path resolution entirely. */ +static bool bfs_can_skip_resolve(const struct bfs_resolver *res, const struct bfs_spawn *ctx) { + if (ctx && !(ctx->flags & BFS_SPAWN_USE_PATH)) { + return true; + } + + if (strchr(res->exe, '/')) { + return true; + } + + return false; +} + +/** Check if any $PATH components are relative. */ +static bool bfs_resolve_relative(const struct bfs_resolver *res) { + const char *path; + const char *next = res->path; + size_t len; + while (bfs_resolve_next(&path, &next, &len)) { + if (path[0] != '/') { + return true; + } + } + + return false; +} + +/** 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; + } + } + } + + return true; +} + +/** Get the required path resolution buffer size. */ +static size_t bfs_resolve_capacity(const struct bfs_resolver *res) { + size_t max = 0; + + const char *path; + const char *next = res->path; + size_t len; + while (bfs_resolve_next(&path, &next, &len)) { + if (len > max) { + max = len; + } + } + + // path + "/" + exe + '\0' + return max + 1 + strlen(res->exe) + 1; +} + +/** Begin resolving an executable, from the parent process. */ +static int bfs_resolve_early(struct bfs_resolver *res, const char *exe, const struct bfs_spawn *ctx) { + *res = (struct bfs_resolver) { + .exe = exe, + }; + + if (bfs_can_skip_resolve(res, ctx)) { + res->done = true; + return 0; + } + + res->path = getenv("PATH"); + if (!res->path) { +#if defined(_CS_PATH) + res->path = xconfstr(_CS_PATH); + res->free = true; +#elif defined(_PATH_DEFPATH) + res->path = _PATH_DEFPATH; +#else + errno = ENOENT; +#endif + } + if (!res->path) { + goto fail; + } + + bool can_finish = bfs_can_resolve_early(res, ctx); + bool use_posix = ctx && (ctx->flags & BFS_SPAWN_USE_POSIX); + if (!can_finish && use_posix) { + // posix_spawnp() will do the resolution, so don't bother + // allocating a buffer + return 0; + } + + res->len = bfs_resolve_capacity(res); + res->buf = malloc(res->len); + if (!res->buf) { + goto fail; + } + + if (can_finish && bfs_resolve_late(res) != 0) { + goto fail; + } + + return 0; + +fail: + bfs_resolve_free(res); + return -1; +} + #if _POSIX_SPAWN > 0 /** bfs_spawn() implementation using posix_spawn(). */ -static pid_t bfs_posix_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { +static pid_t bfs_posix_spawn(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) { pid_t ret; - errno = posix_spawn(&ret, exe, &ctx->actions, &ctx->attr, argv, envp); + + if (res->done) { + errno = posix_spawn(&ret, res->exe, &ctx->actions, &ctx->attr, argv, envp); + } else { + errno = posix_spawnp(&ret, res->exe, &ctx->actions, &ctx->attr, argv, envp); + } + if (errno != 0) { return -1; } @@ -288,7 +496,7 @@ static pid_t bfs_posix_spawn(const char *exe, const struct bfs_spawn *ctx, char #endif /** Actually exec() the new process. */ -static noreturn void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { +static noreturn void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { xclose(pipefd[0]); for_slist (const struct bfs_spawn_action, action, ctx) { @@ -345,11 +553,14 @@ static noreturn void bfs_spawn_exec(const char *exe, const struct bfs_spawn *ctx } } - execve(exe, argv, envp); + if (bfs_resolve_late(res) != 0) { + goto fail; + } - int error; -fail: - error = errno; + execve(res->exe, argv, envp); + +fail:; + int error = errno; // In case of a write error, the parent will still see that we exited // unsuccessfully, but won't know why @@ -360,7 +571,7 @@ fail: } /** bfs_spawn() implementation using fork()/exec(). */ -static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { +static pid_t bfs_fork_spawn(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) { // Use a pipe to report errors from the child int pipefd[2]; if (pipe_cloexec(pipefd) != 0) { @@ -374,7 +585,7 @@ static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char * return -1; } else if (pid == 0) { // Child - bfs_spawn_exec(exe, ctx, argv, envp, pipefd); + bfs_spawn_exec(res, ctx, argv, envp, pipefd); } // Parent @@ -394,26 +605,23 @@ static pid_t bfs_fork_spawn(const char *exe, const struct bfs_spawn *ctx, char * } /** Call the right bfs_spawn() implementation. */ -static pid_t bfs_spawn_impl(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { +static pid_t bfs_spawn_impl(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) { #if _POSIX_SPAWN > 0 if (ctx->flags & BFS_SPAWN_USE_POSIX) { - return bfs_posix_spawn(exe, ctx, argv, envp); + return bfs_posix_spawn(res, ctx, argv, envp); } #endif - return bfs_fork_spawn(exe, ctx, argv, envp); + return bfs_fork_spawn(res, ctx, argv, envp); } pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char **envp) { // execvp()/posix_spawnp() are typically implemented with repeated // execv() calls for each $PATH component until one succeeds. It's // faster to resolve the full path ahead of time. - char *resolved = NULL; - if (ctx->flags & BFS_SPAWN_USE_PATH) { - exe = resolved = bfs_spawn_resolve(exe); - if (!resolved) { - return -1; - } + struct bfs_resolver res; + if (bfs_resolve_early(&res, exe, ctx) != 0) { + return -1; } extern char **environ; @@ -421,78 +629,29 @@ pid_t bfs_spawn(const char *exe, const struct bfs_spawn *ctx, char **argv, char envp = environ; } - pid_t ret = bfs_spawn_impl(exe, ctx, argv, envp); - free(resolved); + pid_t ret = bfs_spawn_impl(&res, ctx, argv, envp); + bfs_resolve_free(&res); return ret; } char *bfs_spawn_resolve(const char *exe) { - if (strchr(exe, '/')) { - return strdup(exe); - } - - const char *path = getenv("PATH"); - - char *confpath = NULL; - if (!path) { -#if defined(_CS_PATH) - path = confpath = xconfstr(_CS_PATH); -#elif defined(_PATH_DEFPATH) - path = _PATH_DEFPATH; -#else - errno = ENOENT; -#endif + struct bfs_resolver res; + if (bfs_resolve_early(&res, exe, NULL) != 0) { + return NULL; } - if (!path) { + if (bfs_resolve_late(&res) != 0) { + bfs_resolve_free(&res); return NULL; } - size_t cap = 0; - char *ret = NULL; - while (true) { - const char *end = strchr(path, ':'); - size_t len = end ? (size_t)(end - path) : strlen(path); - - // POSIX 8.3: "A zero-length prefix is a legacy feature that - // indicates the current working directory." - if (len == 0) { - path = "."; - len = 1; - } - - size_t total = len + 1 + strlen(exe) + 1; - if (cap < total) { - char *grown = realloc(ret, total); - if (!grown) { - goto fail; - } - ret = grown; - cap = total; - } - - memcpy(ret, path, len); - if (ret[len - 1] != '/') { - ret[len++] = '/'; - } - strcpy(ret + len, exe); - - if (xfaccessat(AT_FDCWD, ret, X_OK) == 0) { - break; - } - - if (!end) { - errno = ENOENT; - goto fail; - } - - path = end + 1; + char *ret; + if (res.exe == res.buf) { + ret = res.buf; + res.buf = NULL; + } else { + ret = strdup(res.exe); } - free(confpath); + bfs_resolve_free(&res); return ret; - -fail: - free(confpath); - free(ret); - return NULL; } -- cgit v1.2.3 From 0ad5c7edda2de25e47077d138d7c371f94590a22 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Mar 2024 12:41:33 -0400 Subject: xspawn: Don't use posix_spawnp() on macOS On macOS, posix_spawnp() resolves the executable against the $PATH *before* the file_actions are applied, contrary to the upcoming POSIX wording [1] for posix_spawn_file_actions_addfchdir(). [1]: https://www.austingroupbugs.net/view.php?id=1208#c4830 --- src/xspawn.c | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/xspawn.c b/src/xspawn.c index dc2bf67..f67adec 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -202,6 +202,18 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { return 0; } +/** + * https://www.austingroupbugs.net/view.php?id=1208#c4830 says: + * + * ... a search of the directories passed as the environment variable + * PATH ..., using the working directory of the child process after all + * file_actions have been performed. + * + * but macOS resolves the PATH *before* file_actions (because there + * posix_spawn() is its own syscall). + */ +#define BFS_POSIX_SPAWNP_AFTER_FCHDIR !__APPLE__ + int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR); if (!action) { @@ -452,12 +464,15 @@ static int bfs_resolve_early(struct bfs_resolver *res, const char *exe, const st } bool can_finish = bfs_can_resolve_early(res, ctx); + +#if BFS_POSIX_SPAWNP_AFTER_FCHDIR bool use_posix = ctx && (ctx->flags & BFS_SPAWN_USE_POSIX); if (!can_finish && use_posix) { // posix_spawnp() will do the resolution, so don't bother // allocating a buffer return 0; } +#endif res->len = bfs_resolve_capacity(res); res->buf = malloc(res->len); @@ -477,6 +492,7 @@ fail: } #if _POSIX_SPAWN > 0 + /** bfs_spawn() implementation using posix_spawn(). */ static pid_t bfs_posix_spawn(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) { pid_t ret; @@ -493,8 +509,24 @@ static pid_t bfs_posix_spawn(struct bfs_resolver *res, const struct bfs_spawn *c return ret; } + +/** Check if we can use posix_spawn(). */ +static bool bfs_use_posix_spawn(const struct bfs_resolver *res, const struct bfs_spawn *ctx) { + if (!(ctx->flags & BFS_SPAWN_USE_POSIX)) { + return false; + } + +#if !BFS_POSIX_SPAWNP_AFTER_FCHDIR + if (!res->done) { + return false; + } #endif + return true; +} + +#endif // _POSIX_SPAWN > 0 + /** Actually exec() the new process. */ static noreturn void bfs_spawn_exec(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp, int pipefd[2]) { xclose(pipefd[0]); @@ -607,7 +639,7 @@ static pid_t bfs_fork_spawn(struct bfs_resolver *res, const struct bfs_spawn *ct /** Call the right bfs_spawn() implementation. */ static pid_t bfs_spawn_impl(struct bfs_resolver *res, const struct bfs_spawn *ctx, char **argv, char **envp) { #if _POSIX_SPAWN > 0 - if (ctx->flags & BFS_SPAWN_USE_POSIX) { + if (bfs_use_posix_spawn(res, ctx)) { return bfs_posix_spawn(res, ctx, argv, envp); } #endif -- cgit v1.2.3 From 25769288f62816d733004c9e8347c35dd0e4ce2a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Mar 2024 11:20:20 -0400 Subject: tests: New bfs_pcheck() macro to report xstrerror(errno) --- tests/main.c | 6 ++++++ tests/tests.h | 16 ++++++++++++++++ tests/xtime.c | 6 +++--- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/tests/main.c b/tests/main.c index 8849e8c..7dec320 100644 --- a/tests/main.c +++ b/tests/main.c @@ -6,8 +6,10 @@ */ #include "tests.h" +#include "../src/bfstd.h" #include "../src/color.h" #include "../src/config.h" +#include #include #include #include @@ -88,6 +90,10 @@ static void run_test(struct test_ctx *ctx, const char *test, test_fn *fn) { } } +const char *bfs_errstr(void) { + return xstrerror(errno); +} + int main(int argc, char *argv[]) { // Try to set a UTF-8 locale if (!setlocale(LC_ALL, "C.UTF-8")) { diff --git a/tests/tests.h b/tests/tests.h index 6629dcf..b644450 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -51,4 +51,20 @@ static inline bool bfs_check(bool ret) { : "Check failed: `%s`%s", \ str, __VA_ARGS__), false)) +/** Get a string description of the last error. */ +const char *bfs_errstr(void); + +/** + * Check a condition, logging the current error string on failure. + */ +#define bfs_pcheck(...) \ + bfs_pcheck_(#__VA_ARGS__, __VA_ARGS__, "", "") + +#define bfs_pcheck_(str, cond, format, ...) \ + ((cond) ? true : (bfs_diag( \ + sizeof(format) > 1 \ + ? "%.0s" format "%s%s: %s" \ + : "Check failed: `%s`%s: %s", \ + str, __VA_ARGS__, bfs_errstr()), false)) + #endif // BFS_TESTS_H diff --git a/tests/xtime.c b/tests/xtime.c index ec499d8..f85402e 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -29,9 +29,9 @@ static bool check_one_xgetdate(const char *str, int error, time_t expected) { int ret = xgetdate(str, &ts); if (error) { - return bfs_check(ret == -1 && errno == error, "xgetdate('%s'): %s", str, xstrerror(errno)); + return bfs_pcheck(ret == -1 && errno == error, "xgetdate('%s')", str); } else { - return bfs_check(ret == 0, "xgetdate('%s'): %s", str, xstrerror(errno)) + return bfs_pcheck(ret == 0, "xgetdate('%s')", str) && bfs_check(ts.tv_sec == expected && ts.tv_nsec == 0, "xgetdate('%s'): %jd.%09jd != %jd", str, (intmax_t)ts.tv_sec, (intmax_t)ts.tv_nsec, (intmax_t)expected); @@ -87,7 +87,7 @@ static bool check_one_xmktime(time_t expected) { } time_t actual; - return bfs_check(xmktime(&tm, &actual) == 0, "xmktime(" TM_FORMAT "): %s", TM_PRINTF(tm), xstrerror(errno)) + return bfs_pcheck(xmktime(&tm, &actual) == 0, "xmktime(" TM_FORMAT ")", TM_PRINTF(tm)) && bfs_check(actual == expected, "xmktime(" TM_FORMAT "): %jd != %jd", TM_PRINTF(tm), (intmax_t)actual, (intmax_t)expected); } -- cgit v1.2.3 From bca78d2597c78b0166d76d023eee1354f5abd04e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Mar 2024 11:27:01 -0400 Subject: tests/xspawn: New unit test --- GNUmakefile | 10 +++- tests/main.c | 1 + tests/tests.h | 3 ++ tests/xspawn.c | 154 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ tests/xspawnee.c | 17 ++++++ 5 files changed, 183 insertions(+), 2 deletions(-) create mode 100644 tests/xspawn.c create mode 100644 tests/xspawnee.c diff --git a/GNUmakefile b/GNUmakefile index 7dcd76f..080fb17 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -270,10 +270,15 @@ LIBBFS := \ $(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) # Testing utilities -TEST_UTILS := $(BIN)/tests/mksock $(BIN)/tests/xtouch +TEST_UTILS := \ + $(BIN)/tests/mksock \ + $(BIN)/tests/xspawnee \ + $(BIN)/tests/xtouch $(BIN)/tests/mksock: $(OBJ)/tests/mksock.o $(LIBBFS) +$(BIN)/tests/xspawnee: $(OBJ)/tests/xspawnee.o + $(BIN)/tests/xtouch: $(OBJ)/tests/xtouch.o $(LIBBFS) # All test binaries @@ -286,6 +291,7 @@ $(BIN)/tests/units: \ $(OBJ)/tests/ioq.o \ $(OBJ)/tests/main.o \ $(OBJ)/tests/trie.o \ + $(OBJ)/tests/xspawn.o \ $(OBJ)/tests/xtime.o \ $(LIBBFS) @@ -294,7 +300,7 @@ tests: $(TESTS) .PHONY: tests # Run the unit tests -unit-tests: $(BIN)/tests/units +unit-tests: $(BIN)/tests/units $(BIN)/tests/xspawnee $< .PHONY: unit-tests diff --git a/tests/main.c b/tests/main.c index 7dec320..69903d4 100644 --- a/tests/main.c +++ b/tests/main.c @@ -117,6 +117,7 @@ int main(int argc, char *argv[]) { run_test(&ctx, "bit", check_bit); run_test(&ctx, "ioq", check_ioq); run_test(&ctx, "trie", check_trie); + run_test(&ctx, "xspawn", check_xspawn); run_test(&ctx, "xtime", check_xtime); done: diff --git a/tests/tests.h b/tests/tests.h index b644450..351badb 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -29,6 +29,9 @@ bool check_ioq(void); /** Trie tests. */ bool check_trie(void); +/** Process spawning tests. */ +bool check_xspawn(void); + /** Time tests. */ bool check_xtime(void); diff --git a/tests/xspawn.c b/tests/xspawn.c new file mode 100644 index 0000000..e356842 --- /dev/null +++ b/tests/xspawn.c @@ -0,0 +1,154 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "tests.h" +#include "../src/alloc.h" +#include "../src/bfstd.h" +#include "../src/config.h" +#include "../src/dstring.h" +#include "../src/xspawn.h" +#include +#include +#include + +/** Duplicate the current environment. */ +static char **envdup(void) { + extern char **environ; + + char **envp = NULL; + size_t envc = 0; + + for (char **var = environ; ; ++var) { + char *copy = NULL; + if (*var) { + copy = strdup(*var); + if (!copy) { + goto fail; + } + } + + char **dest = RESERVE(char *, &envp, &envc); + if (!dest) { + free(copy); + goto fail; + } + *dest = copy; + + if (!*var) { + break; + } + } + + return envp; + +fail: + for (size_t i = 0; i < envc; ++i) { + free(envp[i]); + } + free(envp); + return NULL; +} + +/** Check that we resolve executables in $PATH correctly. */ +static bool check_path_resolution(bool use_posix) { + bool ret = true; + + struct bfs_spawn spawn; + ret &= bfs_pcheck(bfs_spawn_init(&spawn) == 0); + if (!ret) { + goto out; + } + + spawn.flags |= BFS_SPAWN_USE_PATH; + if (!use_posix) { + spawn.flags &= ~BFS_SPAWN_USE_POSIX; + } + + const char *builddir = getenv("BUILDDIR"); + dchar *bin = dstrprintf("%s/bin", builddir ? builddir : "."); + ret &= bfs_pcheck(bin, "dstrprintf()"); + if (!ret) { + goto destroy; + } + + ret &= bfs_pcheck(bfs_spawn_addopen(&spawn, 10, bin, O_RDONLY | O_DIRECTORY, 0) == 0); + ret &= bfs_pcheck(bfs_spawn_addfchdir(&spawn, 10) == 0); + ret &= bfs_pcheck(bfs_spawn_addclose(&spawn, 10) == 0); + if (!ret) { + goto bin; + } + + // Check that $PATH is resolved in the parent's environment + char **envp; + ret &= bfs_pcheck(envp = envdup()); + if (!ret) { + goto bin; + } + + // Check that $PATH is resolved after the file actions + char *old_path = getenv("PATH"); + dchar *new_path = NULL; + if (old_path) { + ret &= bfs_pcheck(old_path = strdup(old_path)); + if (!ret) { + goto env; + } + new_path = dstrprintf("tests:%s", old_path); + } else { + new_path = dstrdup("tests"); + } + ret &= bfs_check(new_path); + if (!ret) { + goto path; + } + + ret &= bfs_pcheck(setenv("PATH", new_path, true) == 0); + if (!ret) { + goto path; + } + + char *argv[] = {"xspawnee", old_path, NULL}; + pid_t pid = bfs_spawn("xspawnee", &spawn, argv, envp); + ret &= bfs_pcheck(pid >= 0, "bfs_spawn()"); + if (!ret) { + goto unset; + } + + int wstatus; + ret &= bfs_pcheck(xwaitpid(pid, &wstatus, 0) == pid) + && bfs_check(WIFEXITED(wstatus)); + if (ret) { + int wexit = WEXITSTATUS(wstatus); + ret &= bfs_check(wexit == EXIT_SUCCESS, "xspawnee: exit(%d)", wexit); + } + +unset: + if (old_path) { + ret &= bfs_pcheck(setenv("PATH", old_path, true) == 0); + } else { + ret &= bfs_pcheck(unsetenv("PATH") == 0); + } +path: + dstrfree(new_path); + free(old_path); +env: + for (char **var = envp; *var; ++var) { + free(*var); + } + free(envp); +bin: + dstrfree(bin); +destroy: + ret &= bfs_pcheck(bfs_spawn_destroy(&spawn) == 0); +out: + return ret; +} + +bool check_xspawn(void) { + bool ret = true; + + ret &= check_path_resolution(true); + ret &= check_path_resolution(false); + + return ret; +} diff --git a/tests/xspawnee.c b/tests/xspawnee.c new file mode 100644 index 0000000..b0a76ca --- /dev/null +++ b/tests/xspawnee.c @@ -0,0 +1,17 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +/** Child binary for bfs_spawn() tests. */ +int main(int argc, char *argv[]) { + if (argc >= 2) { + const char *path = getenv("PATH"); + if (!path || strcmp(path, argv[1]) != 0) { + return EXIT_FAILURE; + } + } + + return EXIT_SUCCESS; +} -- cgit v1.2.3 From 5b58f0223c18aed1b2bd3e8224a3f2f3760e9ada Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Mar 2024 15:15:12 -0400 Subject: tests/xspawn: Also test dup2() --- tests/xspawn.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/xspawn.c b/tests/xspawn.c index e356842..d1d5473 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -72,8 +72,10 @@ static bool check_path_resolution(bool use_posix) { } ret &= bfs_pcheck(bfs_spawn_addopen(&spawn, 10, bin, O_RDONLY | O_DIRECTORY, 0) == 0); - ret &= bfs_pcheck(bfs_spawn_addfchdir(&spawn, 10) == 0); + ret &= bfs_pcheck(bfs_spawn_adddup2(&spawn, 10, 11) == 0); ret &= bfs_pcheck(bfs_spawn_addclose(&spawn, 10) == 0); + ret &= bfs_pcheck(bfs_spawn_addfchdir(&spawn, 11) == 0); + ret &= bfs_pcheck(bfs_spawn_addclose(&spawn, 11) == 0); if (!ret) { goto bin; } -- cgit v1.2.3 From 943581a4d59b42a684666f146324d908b0dac428 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Mar 2024 15:27:26 -0400 Subject: tests/xspawn: Test path resolution failure --- tests/xspawn.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/tests/xspawn.c b/tests/xspawn.c index d1d5473..5f80e76 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -50,7 +50,7 @@ fail: } /** Check that we resolve executables in $PATH correctly. */ -static bool check_path_resolution(bool use_posix) { +static bool check_use_path(bool use_posix) { bool ret = true; struct bfs_spawn spawn; @@ -146,11 +146,38 @@ out: return ret; } +/** Check path resolution of non-existent executables. */ +static bool check_enoent(bool use_posix) { + bool ret = true; + + struct bfs_spawn spawn; + ret &= bfs_pcheck(bfs_spawn_init(&spawn) == 0); + if (!ret) { + goto out; + } + + spawn.flags |= BFS_SPAWN_USE_PATH; + if (!use_posix) { + spawn.flags &= ~BFS_SPAWN_USE_POSIX; + } + + char *argv[] = {"eW6f5RM9Qi", NULL}; + pid_t pid = bfs_spawn("eW6f5RM9Qi", &spawn, argv, NULL); + ret &= bfs_pcheck(pid < 0 && errno == ENOENT, "bfs_spawn()"); + + ret &= bfs_pcheck(bfs_spawn_destroy(&spawn) == 0); +out: + return ret; +} + bool check_xspawn(void) { bool ret = true; - ret &= check_path_resolution(true); - ret &= check_path_resolution(false); + ret &= check_use_path(true); + ret &= check_use_path(false); + + ret &= check_enoent(true); + ret &= check_enoent(false); return ret; } -- cgit v1.2.3 From cd929976f8beb208c0c3524ae11de6ceed941324 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 27 Mar 2024 15:38:01 -0400 Subject: xspawn: Fix bfs_resolve_late() error reporting --- src/xspawn.c | 10 ++-------- tests/xspawn.c | 23 +++++++++++++++++++++++ 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/src/xspawn.c b/src/xspawn.c index f67adec..065fbae 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -364,16 +364,10 @@ static int bfs_resolve_late(struct bfs_resolver *res) { res->done = true; return 0; } - - if (end) { - path = end + 1; - } else { - errno = ENOENT; - return -1; - } } - return 0; + errno = ENOENT; + return -1; } /** Check if we can skip path resolution entirely. */ diff --git a/tests/xspawn.c b/tests/xspawn.c index 5f80e76..c1bac36 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -170,6 +170,27 @@ out: return ret; } +static bool check_resolve(void) { + bool ret = true; + char *exe; + + exe = bfs_spawn_resolve("sh"); + ret &= bfs_pcheck(exe, "bfs_spawn_resolve('sh')"); + free(exe); + + exe = bfs_spawn_resolve("/bin/sh"); + ret &= bfs_pcheck(exe && strcmp(exe, "/bin/sh") == 0); + free(exe); + + exe = bfs_spawn_resolve("bin/tests/xspawnee"); + ret &= bfs_pcheck(exe && strcmp(exe, "bin/tests/xspawnee") == 0); + free(exe); + + ret &= bfs_pcheck(!bfs_spawn_resolve("eW6f5RM9Qi") && errno == ENOENT); + + return ret; +} + bool check_xspawn(void) { bool ret = true; @@ -179,5 +200,7 @@ bool check_xspawn(void) { ret &= check_enoent(true); ret &= check_enoent(false); + ret &= check_resolve(); + return ret; } -- cgit v1.2.3 From 99a362d04b507c7c0436c06f087f3d0d3abdc665 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 28 Mar 2024 10:16:03 -0400 Subject: tests/common: Add a -delete error handling test --- tests/common/delete_error.out | 8 ++++++++ tests/common/delete_error.sh | 9 +++++++++ 2 files changed, 17 insertions(+) create mode 100644 tests/common/delete_error.out create mode 100644 tests/common/delete_error.sh diff --git a/tests/common/delete_error.out b/tests/common/delete_error.out new file mode 100644 index 0000000..b6b6505 --- /dev/null +++ b/tests/common/delete_error.out @@ -0,0 +1,8 @@ +. +. +./baz +./baz +./baz/qux +./baz/qux +./foo +./foo/bar diff --git a/tests/common/delete_error.sh b/tests/common/delete_error.sh new file mode 100644 index 0000000..e6327f3 --- /dev/null +++ b/tests/common/delete_error.sh @@ -0,0 +1,9 @@ +cd "$TEST" + +"$XTOUCH" -p foo/bar baz/qux +chmod -w foo +defer chmod +w foo + +! invoke_bfs . -print -delete -print >"$OUT" || fail +sort_output +diff_output -- cgit v1.2.3 From 2fc0b4425197099ba5575d3b50ac77b51c0e0a95 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 28 Mar 2024 11:38:33 -0400 Subject: tests/gnu: Add a -used test --- tests/gnu/used.out | 4 ++++ tests/gnu/used.sh | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 tests/gnu/used.out create mode 100644 tests/gnu/used.sh diff --git a/tests/gnu/used.out b/tests/gnu/used.out new file mode 100644 index 0000000..647621b --- /dev/null +++ b/tests/gnu/used.out @@ -0,0 +1,4 @@ +-used +7: ./nextyear +-used 1: ./tomorrow +-used 2: ./dayafter +-used 7: ./nextweek diff --git a/tests/gnu/used.sh b/tests/gnu/used.sh new file mode 100644 index 0000000..5e5d4e9 --- /dev/null +++ b/tests/gnu/used.sh @@ -0,0 +1,40 @@ +iso8601() { + printf '%04d-%02d-%02dT%02d:%02d:%02d\n' "$@" +} + +cd "$TEST" + +now=$(date '+%Y-%m-%dT%H:%M:%S') + +# Parse the current date +[[ "$now" =~ ^([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})$ ]] || fail +# Treat leading zeros as decimal, not octal +YYYY=$((10#${BASH_REMATCH[1]})) +MM=$((10#${BASH_REMATCH[2]})) +DD=$((10#${BASH_REMATCH[3]})) +hh=$((10#${BASH_REMATCH[4]})) +mm=$((10#${BASH_REMATCH[5]})) +ss=$((10#${BASH_REMATCH[6]})) + +# -used is always false if atime < ctime +yesterday=$(iso8601 $YYYY $MM $((DD - 1)) $hh $mm $ss) +"$XTOUCH" -at "$yesterday" yesterday + +# -used rounds up +tomorrow=$(iso8601 $YYYY $MM $DD $((hh + 1)) $mm $ss) +"$XTOUCH" -at "$tomorrow" tomorrow + +dayafter=$(iso8601 $YYYY $MM $((DD + 1)) $((hh + 1)) $mm $ss) +"$XTOUCH" -at "$dayafter" dayafter + +nextweek=$(iso8601 $YYYY $MM $((DD + 6)) $((hh + 1)) $mm $ss) +"$XTOUCH" -at "$nextweek" nextweek + +nextyear=$(iso8601 $((YYYY + 1)) $MM $DD $hh $mm $ss) +"$XTOUCH" -at "$nextyear" nextyear + +bfs_diff -mindepth 1 \ + -a -used 1 -printf '-used 1: %p\n' \ + -o -used 2 -printf '-used 2: %p\n' \ + -o -used 7 -printf '-used 7: %p\n' \ + -o -used +7 -printf '-used +7: %p\n' -- cgit v1.2.3 From ff1b3d12cb798477b64c09cc0e3b9b2b6bc3e9da Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 28 Mar 2024 12:24:50 -0400 Subject: tests/bsd: Add a -sparse test --- tests/bsd/sparse.out | 1 + tests/bsd/sparse.sh | 12 ++++++++++++ 2 files changed, 13 insertions(+) create mode 100644 tests/bsd/sparse.out create mode 100644 tests/bsd/sparse.sh diff --git a/tests/bsd/sparse.out b/tests/bsd/sparse.out new file mode 100644 index 0000000..52dcf49 --- /dev/null +++ b/tests/bsd/sparse.out @@ -0,0 +1 @@ +mnt/sparse diff --git a/tests/bsd/sparse.sh b/tests/bsd/sparse.sh new file mode 100644 index 0000000..7fcdeed --- /dev/null +++ b/tests/bsd/sparse.sh @@ -0,0 +1,12 @@ +test "$UNAME" = "Linux" || skip + +cd "$TEST" +mkdir mnt + +bfs_sudo mount -t tmpfs tmpfs mnt || skip +defer bfs_sudo umount mnt + +truncate -s 1M mnt/sparse +dd if=/dev/zero of=mnt/dense bs=1M count=1 + +bfs_diff mnt -type f -sparse -- cgit v1.2.3 From 0219441e5911b35035e927846cd8a0af77324f07 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 28 Mar 2024 15:13:04 -0400 Subject: config: Allow inlining allocator functions GCC disables inlining on custom allocator/deallocator pairs, so that they can be matched up accurately in stack traces. --- src/config.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.h b/src/config.h index 892f24c..506ad3e 100644 --- a/src/config.h +++ b/src/config.h @@ -247,7 +247,7 @@ typedef long double max_align_t; * Annotates allocator-like functions. */ #if __has_attribute(malloc) -# if __GNUC__ >= 11 +# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC # define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__))) # else # define attr_malloc(...) attr_nodiscard __attribute__((malloc)) -- cgit v1.2.3 From 1109843d017e0320f0d18bfc398e27ff25592dfb Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 4 Apr 2024 15:17:38 -0400 Subject: build: Don't use libattr We only rely on interfaces like listxattr() which are provided by the C library itself. --- .github/workflows/ci.yml | 3 --- .github/workflows/codecov.yml | 1 - .github/workflows/codeql.yml | 1 - GNUmakefile | 7 ------- README.md | 6 +++--- docs/BUILDING.md | 4 +--- 6 files changed, 4 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c1c89c..8e688fe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,8 +23,6 @@ jobs: libacl1-dev \ libacl1:i386 \ attr \ - libattr1-dev \ - libattr1:i386 \ libcap2-bin \ libcap-dev \ libcap2:i386 \ @@ -34,7 +32,6 @@ jobs: # Ubuntu doesn't let you install the -dev packages for both amd64 and # i386 at once, so we make our own symlinks to fix -m32 -lacl -l... sudo ln -s libacl.so.1 /lib/i386-linux-gnu/libacl.so - sudo ln -s libattr.so.1 /lib/i386-linux-gnu/libattr.so sudo ln -s libcap.so.2 /lib/i386-linux-gnu/libcap.so sudo ln -s libonig.so.5 /lib/i386-linux-gnu/libonig.so # Work around https://github.com/actions/runner-images/issues/9491 diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 9c2119b..d1dc351 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -18,7 +18,6 @@ jobs: acl \ libacl1-dev \ attr \ - libattr1-dev \ libcap2-bin \ libcap-dev \ libonig-dev \ diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 71073c6..c50b266 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -34,7 +34,6 @@ jobs: acl \ libacl1-dev \ attr \ - libattr1-dev \ libcap2-bin \ libcap-dev \ libonig-dev \ diff --git a/GNUmakefile b/GNUmakefile index 080fb17..8685ca3 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -125,7 +125,6 @@ endif # USE_ONIGURUMA ifeq ($(OS),Linux) ifndef NOLIBS USE_ACL := y -USE_ATTR := y USE_LIBCAP := y USE_LIBURING := y endif @@ -136,12 +135,6 @@ else LOCAL_CPPFLAGS += -DBFS_USE_SYS_ACL_H=0 endif -ifdef USE_ATTR -LOCAL_LDLIBS += -lattr -else -LOCAL_CPPFLAGS += -DBFS_USE_SYS_XATTR_H=0 -endif - ifdef USE_LIBCAP LOCAL_LDLIBS += -lcap else diff --git a/README.md b/README.md index 9992938..17226fd 100644 --- a/README.md +++ b/README.md @@ -293,7 +293,7 @@ Here's how to install them on some common platforms:
 Alpine Linux
-# apk add acl{,-dev} attr{,-dev} libcap{,-dev} liburing-dev oniguruma-dev
+# apk add acl{,-dev} attr libcap{,-dev} liburing-dev oniguruma-dev
 
 Arch Linux
 # pacman -S acl attr libcap liburing oniguruma
@@ -302,13 +302,13 @@ Here's how to install them on some common platforms:
 # apt install acl libacl1-dev attr libattr1-dev libcap2-bin libcap-dev liburing-dev libonig-dev
 
 Fedora
-# dnf install acl libacl-devel libattr-devel libcap-devel liburing-devel oniguruma-devel
+# dnf install acl libacl-devel attr libcap-devel liburing-devel oniguruma-devel
 
 NixOS
 # nix-env -i acl attr libcap liburing oniguruma
 
 Void Linux
-# xbps-install -S acl-{devel,progs} attr-{devel,progs} libcap-{devel,progs} liburing-devel oniguruma-devel
+# xbps-install -S acl-{devel,progs} attr-progs libcap-{devel,progs} liburing-devel oniguruma-devel
 
 Homebrew
 $ brew install oniguruma
diff --git a/docs/BUILDING.md b/docs/BUILDING.md
index 02f9756..7eb3a37 100644
--- a/docs/BUILDING.md
+++ b/docs/BUILDING.md
@@ -56,7 +56,7 @@ Here are some of the common ones; check the [`GNUmakefile`](/GNUmakefile) for mo
 | `CC`                             | The C compiler to use, e.g. `make CC=clang` |
 | `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | | `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | -| `USE_ACL`
`USE_ATTR`
... | Enable/disable [optional dependencies] | +| `USE_ACL`
`USE_LIBCAP`
... | Enable/disable [optional dependencies] | | `TEST_FLAGS` | `tests.sh` flags for `make check` | | `BUILDDIR` | The build output directory (default: `.`) | | `DESTDIR` | The root directory for `make install` | @@ -73,13 +73,11 @@ These dependencies are optional, and can be turned off at build time if necessar | Dependency | Platforms | `make` flag | |-------------|------------|-----------------| | [acl] | Linux only | `USE_ACL` | -| [attr] | Linux only | `USE_ATTR` | | [libcap] | Linux only | `USE_LIBCAP` | | [liburing] | Linux only | `USE_LIBURING` | | [Oniguruma] | All | `USE_ONIGURUMA` | [acl]: https://savannah.nongnu.org/projects/acl -[attr]: https://savannah.nongnu.org/projects/attr [libcap]: https://sites.google.com/site/fullycapable/ [liburing]: https://github.com/axboe/liburing [Oniguruma]: https://github.com/kkos/oniguruma -- cgit v1.2.3 From 505843eec93696d3fab69e56c34c578c8d032951 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 9 Apr 2024 16:36:25 -0400 Subject: xspawn: Fix $PATH resolution on NetBSD 10 --- src/xspawn.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xspawn.c b/src/xspawn.c index 065fbae..347625d 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -209,10 +209,10 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { * PATH ..., using the working directory of the child process after all * file_actions have been performed. * - * but macOS resolves the PATH *before* file_actions (because there + * but macOS and NetBSD resolve the PATH *before* file_actions (because there * posix_spawn() is its own syscall). */ -#define BFS_POSIX_SPAWNP_AFTER_FCHDIR !__APPLE__ +#define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD_Prereq__(10, 0, 0)) int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR); -- cgit v1.2.3 From 5e0b721d0d929223e4308406480a1f1ca9e3f4dc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 9 Apr 2024 16:36:48 -0400 Subject: ci: Update {Net,Open}BSD --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e688fe..51d06fb 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -102,7 +102,7 @@ jobs: - name: Run tests uses: vmactions/openbsd-vm@v1 with: - release: "7.4" + release: "7.5" usesh: true copyback: false @@ -132,7 +132,7 @@ jobs: - name: Run tests uses: vmactions/netbsd-vm@v1 with: - release: "9.3" + release: "10.0" usesh: true copyback: false -- cgit v1.2.3 From c31577d102d87455f3f12086be4c0e2159fa5d35 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 8 Apr 2024 11:27:11 -0400 Subject: build: Add a separate configuration step --- .github/workflows/ci.yml | 23 +- .github/workflows/codecov.yml | 3 +- .github/workflows/codeql.yml | 1 + .gitignore | 2 + GNUmakefile | 372 ------------------------------ Makefile | 517 ++++++++++++++++++++++++++++++++++++++++++ README.md | 4 +- config/cc.sh | 12 + config/empty.c | 6 + config/libacl.c | 6 + config/libcap.c | 6 + config/liburing.c | 6 + config/oniguruma.c | 6 + config/pkg.sh | 44 ++++ config/pkgconf.sh | 64 ++++++ docs/BUILDING.md | 83 +++---- docs/USAGE.md | 2 +- src/config.h | 11 +- src/main.c | 1 + src/parse.c | 2 +- src/version.c | 6 + tests/run.sh | 2 +- tests/tests.mk | 10 +- 23 files changed, 757 insertions(+), 432 deletions(-) delete mode 100644 GNUmakefile create mode 100644 Makefile create mode 100755 config/cc.sh create mode 100644 config/empty.c create mode 100644 config/libacl.c create mode 100644 config/libcap.c create mode 100644 config/liburing.c create mode 100644 config/oniguruma.c create mode 100755 config/pkg.sh create mode 100755 config/pkgconf.sh create mode 100644 src/version.c diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 51d06fb..78aa196 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,12 +53,13 @@ jobs: run: | brew install \ bash \ - expect + expect \ + make - name: Run tests run: | jobs=$(sysctl -n hw.ncpu) - make -j$jobs distcheck + gmake -j$jobs distcheck freebsd: name: FreeBSD @@ -79,8 +80,8 @@ jobs: pkg install -y \ bash \ expect \ - gmake \ oniguruma \ + pkgconf \ sudo \ tcl-wrapper pw useradd -n action -m -G wheel -s /usr/local/bin/bash @@ -89,7 +90,7 @@ jobs: run: | chown -R action:action . - sudo -u action gmake -j$(nproc) distcheck + sudo -u action make -j$(nproc) distcheck openbsd: name: OpenBSD @@ -119,6 +120,7 @@ jobs: run: | chown -R action:action . jobs=$(sysctl -n hw.ncpu) + doas -u action gmake config doas -u action gmake -j$jobs check TEST_FLAGS="--sudo=doas --verbose=skipped" netbsd: @@ -141,8 +143,8 @@ jobs: pkg_add \ bash \ clang \ - gmake \ oniguruma \ + pkgconf \ sudo \ tcl-expect useradd -m -G wheel -g =uid action @@ -152,7 +154,8 @@ jobs: PATH="/sbin:/usr/sbin:$PATH" chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action gmake -j$jobs check CC=clang LDFLAGS="-rpath /usr/pkg/lib" TEST_FLAGS="--sudo --verbose=skipped" + sudo -u action make config CC=clang + sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" dragonflybsd: name: DragonFly BSD @@ -173,8 +176,8 @@ jobs: pkg install -y \ bash \ expect \ - gmake \ oniguruma \ + pkgconf \ sudo \ tcl-wrapper pw useradd -n action -m -G wheel -s /usr/local/bin/bash @@ -183,7 +186,8 @@ jobs: run: | chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + sudo -u action make config + sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" omnios: name: OmniOS @@ -215,4 +219,5 @@ jobs: PATH="/usr/xpg4/bin:$PATH" chown -R action:staff . jobs=$(getconf NPROCESSORS_ONLN) - sudo -u action gmake -j$jobs check LDFLAGS="-Wl,-rpath,/opt/ooce/lib/amd64" TEST_FLAGS="--sudo --verbose=skipped" + sudo -u action gmake config + sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index d1dc351..2abe531 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -25,7 +25,8 @@ jobs: - name: Generate coverage run: | - make -j$(nproc) gcov check TEST_FLAGS="--sudo" + make config GCOV=y + make -j$(nproc) check TEST_FLAGS="--sudo" gcov -abcfpu obj/*/*.o - uses: codecov/codecov-action@v3 diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c50b266..a2c224a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -47,6 +47,7 @@ jobs: - name: Build run: | + make config make -j$(nproc) all - name: Perform CodeQL Analysis diff --git a/.gitignore b/.gitignore index 4ded7c4..84e47fc 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ /bin/ +/gen/ /obj/ +/distcheck-*/ diff --git a/GNUmakefile b/GNUmakefile deleted file mode 100644 index 8685ca3..0000000 --- a/GNUmakefile +++ /dev/null @@ -1,372 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -ifneq ($(wildcard .git),) -VERSION := $(shell git describe --always 2>/dev/null) -endif - -ifndef VERSION -VERSION := 3.1.3 -endif - -ifndef OS -OS := $(shell uname) -endif - -ifndef ARCH -ARCH := $(shell uname -m) -endif - -CC ?= gcc -INSTALL ?= install -MKDIR ?= mkdir -p -RM ?= rm -f - -export BUILDDIR ?= . -DESTDIR ?= -PREFIX ?= /usr -MANDIR ?= $(PREFIX)/share/man - -BIN := $(BUILDDIR)/bin -OBJ := $(BUILDDIR)/obj - -DEFAULT_CFLAGS := \ - -g \ - -Wall \ - -Wformat=2 \ - -Werror=implicit \ - -Wimplicit-fallthrough \ - -Wmissing-declarations \ - -Wshadow \ - -Wsign-compare \ - -Wstrict-prototypes - -CFLAGS ?= $(DEFAULT_CFLAGS) -LDFLAGS ?= -DEPFLAGS ?= -MD -MP -MF $(@:.o=.d) - -LOCAL_CPPFLAGS := \ - -D__EXTENSIONS__ \ - -D_ATFILE_SOURCE \ - -D_BSD_SOURCE \ - -D_DARWIN_C_SOURCE \ - -D_DEFAULT_SOURCE \ - -D_GNU_SOURCE \ - -D_LARGEFILE64_SOURCE \ - -D_POSIX_PTHREAD_SEMANTICS \ - -D_FILE_OFFSET_BITS=64 \ - -D_TIME_BITS=64 \ - -DBFS_VERSION=\"$(VERSION)\" - -LOCAL_CFLAGS := -std=c17 -pthread -LOCAL_LDFLAGS := -LOCAL_LDLIBS := - -ASAN := $(filter asan,$(MAKECMDGOALS)) -LSAN := $(filter lsan,$(MAKECMDGOALS)) -MSAN := $(filter msan,$(MAKECMDGOALS)) -TSAN := $(filter tsan,$(MAKECMDGOALS)) -UBSAN := $(filter ubsan,$(MAKECMDGOALS)) - -ifdef ASAN -LOCAL_CFLAGS += -fsanitize=address -SANITIZE := y -endif - -ifdef LSAN -LOCAL_CFLAGS += -fsanitize=leak -SANITIZE := y -endif - -ifdef MSAN -# msan needs all code instrumented -NOLIBS := y -LOCAL_CFLAGS += -fsanitize=memory -fsanitize-memory-track-origins -SANITIZE := y -endif - -ifdef TSAN -# tsan needs all code instrumented -NOLIBS := y -# https://github.com/google/sanitizers/issues/342 -LOCAL_CPPFLAGS += -DBFS_USE_TARGET_CLONES=0 -LOCAL_CFLAGS += -fsanitize=thread -SANITIZE := y -endif - -ifdef UBSAN -LOCAL_CFLAGS += -fsanitize=undefined -SANITIZE := y -endif - -ifdef SANITIZE -LOCAL_CFLAGS += -fno-sanitize-recover=all -endif - -ifndef NOLIBS -USE_ONIGURUMA := y -endif - -ifdef USE_ONIGURUMA -LOCAL_CPPFLAGS += -DBFS_USE_ONIGURUMA=1 - -ONIG_CONFIG := $(shell command -v onig-config 2>/dev/null) -ifdef ONIG_CONFIG -ONIG_CFLAGS := $(shell $(ONIG_CONFIG) --cflags) -ONIG_LDLIBS := $(shell $(ONIG_CONFIG) --libs) -else -ONIG_LDLIBS := -lonig -endif - -LOCAL_CFLAGS += $(ONIG_CFLAGS) -LOCAL_LDLIBS += $(ONIG_LDLIBS) -endif # USE_ONIGURUMA - -ifeq ($(OS),Linux) -ifndef NOLIBS -USE_ACL := y -USE_LIBCAP := y -USE_LIBURING := y -endif - -ifdef USE_ACL -LOCAL_LDLIBS += -lacl -else -LOCAL_CPPFLAGS += -DBFS_USE_SYS_ACL_H=0 -endif - -ifdef USE_LIBCAP -LOCAL_LDLIBS += -lcap -else -LOCAL_CPPFLAGS += -DBFS_USE_SYS_CAPABILITY_H=0 -endif - -ifdef USE_LIBURING -LOCAL_CPPFLAGS += -DBFS_USE_LIBURING=1 -LOCAL_LDLIBS += -luring -endif - -LOCAL_LDFLAGS += -Wl,--as-needed -LOCAL_LDLIBS += -lrt -endif # Linux - -ifeq ($(OS),NetBSD) -LOCAL_LDLIBS += -lutil -endif - -ifeq ($(OS),DragonFly) -LOCAL_LDLIBS += -lposix1e -endif - -ifeq ($(OS),SunOS) -LOCAL_LDLIBS += -lsocket -lnsl -endif - -ifneq ($(filter gcov,$(MAKECMDGOALS)),) -LOCAL_CFLAGS += --coverage -# gcov only intercepts fork()/exec() with -std=gnu* -LOCAL_CFLAGS := $(patsubst -std=c%,-std=gnu%,$(LOCAL_CFLAGS)) -endif - -ifneq ($(filter lint,$(MAKECMDGOALS)),) -LOCAL_CPPFLAGS += \ - -D_FORTIFY_SOURCE=3 \ - -DBFS_LINT -LOCAL_CFLAGS += -Werror -O2 -endif - -ifneq ($(filter release,$(MAKECMDGOALS)),) -LOCAL_CPPFLAGS += -DNDEBUG -CFLAGS := $(DEFAULT_CFLAGS) -O3 -flto=auto -endif - -ALL_CPPFLAGS = $(LOCAL_CPPFLAGS) $(CPPFLAGS) $(EXTRA_CPPFLAGS) -ALL_CFLAGS = $(ALL_CPPFLAGS) $(LOCAL_CFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(DEPFLAGS) -ALL_LDFLAGS = $(ALL_CFLAGS) $(LOCAL_LDFLAGS) $(LDFLAGS) $(EXTRA_LDFLAGS) -ALL_LDLIBS = $(LOCAL_LDLIBS) $(LDLIBS) $(EXTRA_LDLIBS) - -# Default make target -bfs: $(BIN)/bfs -.PHONY: bfs - -# Goals that are treated like flags by this makefile -FLAG_GOALS := asan lsan msan tsan ubsan gcov lint release - -# These are the remaining non-flag goals -GOALS := $(filter-out $(FLAG_GOALS),$(MAKECMDGOALS)) - -# Build the default goal if only flag goals are specified -FLAG_PREREQS := -ifndef GOALS -FLAG_PREREQS += bfs -endif - -# Make sure that "make release" builds everything, but "make release obj/src/main.o" doesn't -$(FLAG_GOALS): $(FLAG_PREREQS) - @: -.PHONY: $(FLAG_GOALS) - -all: bfs tests -.PHONY: all - -$(BIN)/%: - @$(MKDIR) $(@D) - +$(CC) $(ALL_LDFLAGS) $^ $(ALL_LDLIBS) -o $@ -ifeq ($(OS) $(SANITIZE),FreeBSD y) - elfctl -e +noaslr $@ -endif - -$(OBJ)/%.o: %.c $(OBJ)/FLAGS - @$(MKDIR) $(@D) - $(CC) $(ALL_CFLAGS) -c $< -o $@ - -# Save the full set of flags to rebuild everything when they change -$(OBJ)/FLAGS.new: - @$(MKDIR) $(@D) - @echo $(CC) : $(ALL_CFLAGS) : $(ALL_LDFLAGS) : $(ALL_LDLIBS) >$@ -.PHONY: $(OBJ)/FLAGS.new - -# Only update obj/FLAGS if obj/FLAGS.new is different -$(OBJ)/FLAGS: $(OBJ)/FLAGS.new - @test -e $@ && cmp -s $@ $< && rm $< || mv $< $@ - -# All object files except the entry point -LIBBFS := \ - $(OBJ)/src/alloc.o \ - $(OBJ)/src/bar.o \ - $(OBJ)/src/bfstd.o \ - $(OBJ)/src/bftw.o \ - $(OBJ)/src/color.o \ - $(OBJ)/src/ctx.o \ - $(OBJ)/src/diag.o \ - $(OBJ)/src/dir.o \ - $(OBJ)/src/dstring.o \ - $(OBJ)/src/eval.o \ - $(OBJ)/src/exec.o \ - $(OBJ)/src/expr.o \ - $(OBJ)/src/fsade.o \ - $(OBJ)/src/ioq.o \ - $(OBJ)/src/mtab.o \ - $(OBJ)/src/opt.o \ - $(OBJ)/src/parse.o \ - $(OBJ)/src/printf.o \ - $(OBJ)/src/pwcache.o \ - $(OBJ)/src/stat.o \ - $(OBJ)/src/thread.o \ - $(OBJ)/src/trie.o \ - $(OBJ)/src/typo.o \ - $(OBJ)/src/xregex.o \ - $(OBJ)/src/xspawn.o \ - $(OBJ)/src/xtime.o - -# The main executable -$(BIN)/bfs: $(OBJ)/src/main.o $(LIBBFS) - -# Testing utilities -TEST_UTILS := \ - $(BIN)/tests/mksock \ - $(BIN)/tests/xspawnee \ - $(BIN)/tests/xtouch - -$(BIN)/tests/mksock: $(OBJ)/tests/mksock.o $(LIBBFS) - -$(BIN)/tests/xspawnee: $(OBJ)/tests/xspawnee.o - -$(BIN)/tests/xtouch: $(OBJ)/tests/xtouch.o $(LIBBFS) - -# All test binaries -TESTS := $(BIN)/tests/units $(TEST_UTILS) - -$(BIN)/tests/units: \ - $(OBJ)/tests/alloc.o \ - $(OBJ)/tests/bfstd.o \ - $(OBJ)/tests/bit.o \ - $(OBJ)/tests/ioq.o \ - $(OBJ)/tests/main.o \ - $(OBJ)/tests/trie.o \ - $(OBJ)/tests/xspawn.o \ - $(OBJ)/tests/xtime.o \ - $(LIBBFS) - -# Build all the test binaries -tests: $(TESTS) -.PHONY: tests - -# Run the unit tests -unit-tests: $(BIN)/tests/units $(BIN)/tests/xspawnee - $< -.PHONY: unit-tests - -# The different flag combinations we check -INTEGRATIONS := default dfs ids eds j1 j2 j3 s -INTEGRATION_TESTS := $(INTEGRATIONS:%=check-%) - -check-default: $(BIN)/bfs $(TEST_UTILS) - +./tests/tests.sh --make="$(MAKE)" --bfs="$<" $(TEST_FLAGS) - -check-dfs check-ids check-eds: check-%: $(BIN)/bfs $(TEST_UTILS) - +./tests/tests.sh --make="$(MAKE)" --bfs="$< -S $*" $(TEST_FLAGS) - -check-j1 check-j2 check-j3 check-s: check-%: $(BIN)/bfs $(TEST_UTILS) - +./tests/tests.sh --make="$(MAKE)" --bfs="$< -$*" $(TEST_FLAGS) - -# Run the integration tests -integration-tests: $(INTEGRATION_TESTS) -.PHONY: integration-tests - -# Run all the tests -check: unit-tests integration-tests -.PHONY: check - -# Custom test flags for distcheck -DISTCHECK_FLAGS := -s TEST_FLAGS="--sudo --verbose=skipped" - -distcheck: - +$(MAKE) -B asan ubsan check $(DISTCHECK_FLAGS) -ifneq ($(OS),Darwin) - +$(MAKE) -B msan ubsan check CC=clang $(DISTCHECK_FLAGS) -endif - +$(MAKE) -B tsan ubsan check CC=clang $(DISTCHECK_FLAGS) -ifeq ($(OS) $(ARCH),Linux x86_64) - +$(MAKE) -B check EXTRA_CFLAGS="-m32" ONIG_CONFIG= USE_LIBURING= $(DISTCHECK_FLAGS) -endif - +$(MAKE) -B release check $(DISTCHECK_FLAGS) - +$(MAKE) -B check $(DISTCHECK_FLAGS) - +$(MAKE) check-install $(DISTCHECK_FLAGS) -.PHONY: distcheck - -clean: - $(RM) -r $(BIN) $(OBJ) -.PHONY: clean - -install: - $(MKDIR) $(DESTDIR)$(PREFIX)/bin - $(INSTALL) -m755 $(BIN)/bfs $(DESTDIR)$(PREFIX)/bin/bfs - $(MKDIR) $(DESTDIR)$(MANDIR)/man1 - $(INSTALL) -m644 docs/bfs.1 $(DESTDIR)$(MANDIR)/man1/bfs.1 - $(MKDIR) $(DESTDIR)$(PREFIX)/share/bash-completion/completions - $(INSTALL) -m644 completions/bfs.bash $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs - $(MKDIR) $(DESTDIR)$(PREFIX)/share/zsh/site-functions - $(INSTALL) -m644 completions/bfs.zsh $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_bfs - $(MKDIR) $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d - $(INSTALL) -m644 completions/bfs.fish $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/bfs.fish -.PHONY: install - -uninstall: - $(RM) $(DESTDIR)$(PREFIX)/share/bash-completion/completions/bfs - $(RM) $(DESTDIR)$(PREFIX)/share/zsh/site-functions/_bfs - $(RM) $(DESTDIR)$(PREFIX)/share/fish/vendor_completions.d/bfs.fish - $(RM) $(DESTDIR)$(MANDIR)/man1/bfs.1 - $(RM) $(DESTDIR)$(PREFIX)/bin/bfs -.PHONY: uninstall - -check-install: - +$(MAKE) install DESTDIR=$(BUILDDIR)/pkg - +$(MAKE) uninstall DESTDIR=$(BUILDDIR)/pkg - $(BIN)/bfs $(BUILDDIR)/pkg -not -type d -print -exit 1 - $(RM) -r $(BUILDDIR)/pkg -.PHONY: check-install - -.SUFFIXES: - --include $(wildcard $(OBJ)/*/*.d) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..326ee87 --- /dev/null +++ b/Makefile @@ -0,0 +1,517 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# This Makefile implements the configuration and build steps for bfs. It is +# portable to both GNU make and the BSD make implementations (how that works +# is documented below). To build bfs, run +# +# $ make config +# $ make + +# The default build target +default: bfs +.PHONY: default + +# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to +.OBJDIR: . + +# We don't use any suffix rules +.SUFFIXES: + +# GNU make has $^ for the full list of targets, while BSD make has $> and the +# long-form ${.ALLSRC}. We could write $^ $> to get them both, but that would +# break if one of them implemented support for the other. So instead, bring +# BSD's ${.ALLSRC} to GNU. +.ALLSRC ?= $^ + +# Platform detection +OS != uname +ARCH != uname -m + +# For out-of-tree builds, e.g. +# +# $ make config BUILDDIR=/path/to/build/dir +# $ make BUILDDIR=/path/to/build/dir +BUILDDIR ?= . + +# Shorthand for build subdirectories +BIN := ${BUILDDIR}/bin +GEN := ${BUILDDIR}/gen +OBJ := ${BUILDDIR}/obj + +# GNU make strips a leading ./ from target names, so do the same for BSD make +BIN := ${BIN:./%=%} +GEN := ${GEN:./%=%} +OBJ := ${OBJ:./%=%} + +# Installation paths +DESTDIR ?= +PREFIX ?= /usr +MANDIR ?= ${PREFIX}/share/man + +# Configurable executables; can be overridden with +# +# $ make config CC=clang +CC ?= cc +INSTALL ?= install +MKDIR ?= mkdir -p +PKG_CONFIG ?= pkg-config +RM ?= rm -f + +# Configurable flags + +CPPFLAGS ?= +CFLAGS ?= \ + -g \ + -Wall \ + -Wformat=2 \ + -Werror=implicit \ + -Wimplicit-fallthrough \ + -Wmissing-declarations \ + -Wshadow \ + -Wsign-compare \ + -Wstrict-prototypes +LDFLAGS ?= +LDLIBS ?= + +EXTRA_CPPFLAGS ?= +EXTRA_CFLAGS ?= +EXTRA_LDFLAGS ?= +EXTRA_LDLIBS ?= + +GIT_VERSION != test -d .git && command -v git >/dev/null 2>&1 && git describe --always --dirty || echo 3.1.3 +VERSION ?= ${GIT_VERSION} + +# Immutable flags +export BFS_CPPFLAGS= \ + -D__EXTENSIONS__ \ + -D_ATFILE_SOURCE \ + -D_BSD_SOURCE \ + -D_DARWIN_C_SOURCE \ + -D_DEFAULT_SOURCE \ + -D_GNU_SOURCE \ + -D_LARGEFILE64_SOURCE \ + -D_POSIX_PTHREAD_SEMANTICS \ + -D_FILE_OFFSET_BITS=64 \ + -D_TIME_BITS=64 +export BFS_CFLAGS= -std=c17 -pthread + +# Platform-specific system libraries +LDLIBS,DragonFly := -lposix1e +LDLIBS,Linux := -lrt +LDLIBS,NetBSD := -lutil +LDLIBS,SunOS := -lsocket -lnsl +_BFS_LDLIBS := ${LDLIBS,${OS}} +export BFS_LDLIBS=${_BFS_LDLIBS} + +# Build profiles +ASAN ?= n +LSAN ?= n +MSAN ?= n +TSAN ?= n +UBSAN ?= n +GCOV ?= n +LINT ?= n +RELEASE ?= n + +export ASAN_CFLAGS= -fsanitize=address +export LSAN_CFLAGS= -fsanitize=leak +export MSAN_CFLAGS= -fsanitize=memory -fsanitize-memory-track-origins +export UBSAN_CFLAGS= -fsanitize=undefined + +# https://github.com/google/sanitizers/issues/342 +export TSAN_CPPFLAGS= -DBFS_USE_TARGET_CLONES=0 +export TSAN_CFLAGS= -fsanitize=thread + +SAN := ${ASAN}${LSAN}${MSAN}${TSAN}${UBSAN} +export SAN_CFLAGS= -fno-sanitize-recover=all + +# MSAN and TSAN both need all code to be instrumented +export NOLIBS= ${MSAN}${TSAN} + +# gcov only intercepts fork()/exec() with -std=gnu* +export GCOV_CFLAGS= --coverage -std=gnu17 + +export LINT_CPPFLAGS= -D_FORTIFY_SOURCE=3 -DBFS_LINT +export LINT_CFLAGS= -Werror -O2 + +export RELEASE_CPPFLAGS= -DNDEBUG +export RELEASE_CFLAGS= -O3 -flto=auto + +# Auto-detected library dependencies. Can be set manually with +# +# $ make config USE_LIBURING=n USE_ONIGURUMA=y +USE_LIBACL ?= +USE_LIBCAP ?= +USE_LIBURING ?= +USE_ONIGURUMA ?= + +# Save the new value of these variables, before they potentially get overridden +# by `-include ${CONFIG}` below + +_XPREFIX := ${PREFIX} +_XMANDIR := ${MANDIR} + +_XOS := ${OS} +_XARCH := ${ARCH} + +_XCC := ${CC} +_XINSTALL := ${INSTALL} +_XMKDIR := ${MKDIR} +_XRM := ${RM} + +_XCPPFLAGS := ${CPPFLAGS} +_XCFLAGS := ${CFLAGS} +_XLDFLAGS := ${LDFLAGS} +_XLDLIBS := ${LDLIBS} + +_XUSE_LIBACL := ${USE_LIBACL} +_XUSE_LIBCAP := ${USE_LIBCAP} +_XUSE_LIBURING := ${USE_LIBURING} +_XUSE_ONIGURUMA := ${USE_ONIGURUMA} + +# GNU make supports `export VAR`, but BSD make requires `export VAR=value`. +# Sadly, GNU make gives a recursion error on `export VAR=${VAR}`. + +_BUILDDIR := ${BUILDDIR} +_PKG_CONFIG := ${PKG_CONFIG} + +export BUILDDIR=${_BUILDDIR} +export PKG_CONFIG=${_PKG_CONFIG} + +export XPREFIX=${_XPREFIX} +export XMANDIR=${_XMANDIR} + +export XOS=${_XOS} +export XARCH=${_XARCH} + +export XCC=${_XCC} +export XINSTALL=${_XINSTALL} +export XMKDIR=${_XMKDIR} +export XRM=${_XRM} + +export XCPPFLAGS=${_XCPPFLAGS} +export XCFLAGS=${_XCFLAGS} +export XLDFLAGS=${_XLDFLAGS} +export XLDLIBS=${_XLDLIBS} + +export XUSE_LIBACL=${_XUSE_LIBACL} +export XUSE_LIBCAP=${_XUSE_LIBCAP} +export XUSE_LIBURING=${_XUSE_LIBURING} +export XUSE_ONIGURUMA=${_XUSE_ONIGURUMA} + +# The configuration file generated by `make config` +CONFIG := ${GEN}/config.mk +-include ${CONFIG} + +## Configuration phase (`make config`) + +# External dependencies +PKGS := \ + ${GEN}/libacl.mk \ + ${GEN}/libcap.mk \ + ${GEN}/liburing.mk \ + ${GEN}/oniguruma.mk + +# Makefile fragments generated by `make config` +MKS := \ + ${GEN}/vars.mk \ + ${GEN}/deps.mk \ + ${GEN}/objs.mk \ + ${PKGS} + +# The configuration goal itself +config: ${MKS} + @printf 'include $${GEN}/%s\n' ${MKS:${GEN}/%=%} >${CONFIG} +.PHONY: config + +# Saves the configurable variables +${GEN}/vars.mk:: + @${XMKDIR} ${@D} + @printf 'PREFIX := %s\n' "$$XPREFIX" >$@ + @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ + @printf 'OS := %s\n' "$$XOS" >>$@ + @printf 'ARCH := %s\n' "$$XARCH" >>$@ + @printf 'CC := %s\n' "$$XCC" >>$@ + @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ + @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ + @printf 'RM := %s\n' "$$XRM" >>$@ + @printf 'CPPFLAGS := %s\n' "$$BFS_CPPFLAGS" >>$@ + @test "${TSAN}" != y || printf 'CPPFLAGS += %s\n' "$$TSAN_CPPFLAGS" >>$@ + @test "${LINT}" != y || printf 'CPPFLAGS += %s\n' "$$LINT_CPPFLAGS" >>$@ + @test "${RELEASE}" != y || printf 'CPPFLAGS += %s\n' "$$RELEASE_CPPFLAGS" >>$@ + @test -z "$$XCPPFLAGS" || printf 'CPPFLAGS += %s\n' "$$XCPPFLAGS" >>$@ + @test -z "$$EXTRA_CPPFLAGS" || printf 'CPPFLAGS += %s\n' "$$EXTRA_CPPFLAGS" >>$@ + @printf 'CFLAGS := %s\n' "$$BFS_CFLAGS" >>$@ + @test "${ASAN}" != y || printf 'CFLAGS += %s\n' "$$ASAN_CFLAGS" >>$@ + @test "${LSAN}" != y || printf 'CFLAGS += %s\n' "$$LSAN_CFLAGS" >>$@ + @test "${MSAN}" != y || printf 'CFLAGS += %s\n' "$$MSAN_CFLAGS" >>$@ + @test "${TSAN}" != y || printf 'CFLAGS += %s\n' "$$TSAN_CFLAGS" >>$@ + @test "${UBSAN}" != y || printf 'CFLAGS += %s\n' "$$UBSAN_CFLAGS" >>$@ + @case "${SAN}" in *y*) printf 'CFLAGS += %s\n' "$$SAN_CFLAGS" >>$@ ;; esac + @test "${GCOV}" != y || printf 'CFLAGS += %s\n' "$$GCOV_CFLAGS" >>$@ + @test "${LINT}" != y || printf 'CFLAGS += %s\n' "$$LINT_CFLAGS" >>$@ + @test "${RELEASE}" != y || printf 'CFLAGS += %s\n' "$$RELEASE_CFLAGS" >>$@ + @test -z "$$XCFLAGS" || printf 'CFLAGS += %s\n' "$$XCFLAGS" >>$@ + @test -z "$$EXTRA_CFLAGS" || printf 'CFLAGS += %s\n' "$$EXTRA_CFLAGS" >>$@ + @printf 'LDFLAGS := %s\n' "$$XLDFLAGS" >>$@ + @test -z "$$EXTRA_LDFLAGS" || printf 'LDFLAGS += %s\n' "$$EXTRA_LDFLAGS" >>$@ + @printf 'LDLIBS := %s\n' "$$XLDLIBS" >>$@ + @test -z "$$EXTRA_LDLIBS" || printf 'LDLIBS += %s\n' "$$EXTRA_LDLIBS" >>$@ + @test -z "$$BFS_LDLIBS" || printf 'LDLIBS += %s\n' "$$BFS_LDLIBS" >>$@ + @case "${OS}-${SAN}" in FreeBSD-*y*) printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ ;; esac + @cat $@ + +# Check for dependency generation support +${GEN}/deps.mk:: + @${MKDIR} ${@D} + @if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ + echo 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}'; \ + fi 2>$@.log | tee $@ + @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ + +# Lists file.o: file.c dependencies +${GEN}/objs.mk:: + @${MKDIR} ${@D} + @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >$@ + +# Auto-detect dependencies and their build flags +${PKGS}:: + @${MKDIR} ${@D} + @config/pkg.sh ${@:${GEN}/%.mk=%} >$@ 2>$@.log + @cat $@ + +# bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.). +# Direct users to the new configuration system. +asan lsan msan tsan ubsan gcov lint release:: + @printf 'error: `make %s` is no longer supported. ' $@ >&2 + @printf 'Use `make config %s=y` instead.\n' $$(echo $@ | tr '[a-z]' '[A-Z]') >&2 + @false + +## Build phase (`make`) + +# The main binary +bfs: ${BIN}/bfs +.PHONY: bfs + +# All binaries +BINS := \ + ${BIN}/bfs \ + ${BIN}/tests/mksock \ + ${BIN}/tests/units \ + ${BIN}/tests/xspawnee \ + ${BIN}/tests/xtouch + +all: ${BINS} +.PHONY: all + +# All object files except the entry point +LIBBFS := \ + ${OBJ}/src/alloc.o \ + ${OBJ}/src/bar.o \ + ${OBJ}/src/bfstd.o \ + ${OBJ}/src/bftw.o \ + ${OBJ}/src/color.o \ + ${OBJ}/src/ctx.o \ + ${OBJ}/src/diag.o \ + ${OBJ}/src/dir.o \ + ${OBJ}/src/dstring.o \ + ${OBJ}/src/eval.o \ + ${OBJ}/src/exec.o \ + ${OBJ}/src/expr.o \ + ${OBJ}/src/fsade.o \ + ${OBJ}/src/ioq.o \ + ${OBJ}/src/mtab.o \ + ${OBJ}/src/opt.o \ + ${OBJ}/src/parse.o \ + ${OBJ}/src/printf.o \ + ${OBJ}/src/pwcache.o \ + ${OBJ}/src/stat.o \ + ${OBJ}/src/thread.o \ + ${OBJ}/src/trie.o \ + ${OBJ}/src/typo.o \ + ${OBJ}/src/version.o \ + ${OBJ}/src/xregex.o \ + ${OBJ}/src/xspawn.o \ + ${OBJ}/src/xtime.o + +# Group relevant flags together +ALL_CFLAGS = ${CPPFLAGS} ${CFLAGS} ${DEPFLAGS} +ALL_LDFLAGS = ${CFLAGS} ${LDFLAGS} + +# The main binary +${BIN}/bfs: ${LIBBFS} ${OBJ}/src/main.o + +${BINS}: + @${MKDIR} ${@D} + +${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ + ${POSTLINK} + +# All object files +OBJS := \ + ${OBJ}/src/main.o \ + ${OBJ}/tests/alloc.o \ + ${OBJ}/tests/bfstd.o \ + ${OBJ}/tests/bit.o \ + ${OBJ}/tests/ioq.o \ + ${OBJ}/tests/main.o \ + ${OBJ}/tests/mksock.o \ + ${OBJ}/tests/trie.o \ + ${OBJ}/tests/xspawn.o \ + ${OBJ}/tests/xspawnee.o \ + ${OBJ}/tests/xtime.o \ + ${OBJ}/tests/xtouch.o \ + ${LIBBFS} + +# Depend on ${CONFIG} to make sure `make config` runs first, and to rebuild when +# the configuration changes +${OBJS}: ${CONFIG} + @${MKDIR} ${@D} + ${CC} ${ALL_CFLAGS} -c ${@:${OBJ}/%.o=%.c} -o $@ + +# Save the version number to this file, but only update VERSION if it changes +${GEN}/NEWVERSION:: + @${MKDIR} ${@D} + @printf '%s\n' '${VERSION}' >$@ + +${GEN}/VERSION: ${GEN}/NEWVERSION + @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@ + +# Rebuild version.c whenever the version number changes +${OBJ}/src/version.o: ${GEN}/VERSION +${OBJ}/src/version.o: CPPFLAGS := ${CPPFLAGS} -DBFS_VERSION='"${VERSION}"' + +# Clean all build products +clean:: + ${RM} -r ${BIN} ${OBJ} + +# Clean everything, including generated files +distclean: clean + ${RM} -r ${GEN} +.PHONY: distclean + +## Test phase (`make check`) + +# Unit test binaries +UTEST_BINS := \ + ${BIN}/tests/units \ + ${BIN}/tests/xspawnee + +# Integration test binaries +ITEST_BINS := \ + ${BIN}/tests/mksock \ + ${BIN}/tests/xtouch + +# Build (but don't run) test binaries +tests: ${UTEST_BINS} ${ITEST_BINS} +.PHONY: tests + +# Run all the tests +check: unit-tests integration-tests +.PHONY: check + +# Run the unit tests +unit-tests: ${UTEST_BINS} + ${BIN}/tests/units +.PHONY: unit-tests + +${BIN}/tests/units: \ + ${OBJ}/tests/alloc.o \ + ${OBJ}/tests/bfstd.o \ + ${OBJ}/tests/bit.o \ + ${OBJ}/tests/ioq.o \ + ${OBJ}/tests/main.o \ + ${OBJ}/tests/trie.o \ + ${OBJ}/tests/xspawn.o \ + ${OBJ}/tests/xtime.o \ + ${LIBBFS} + +${BIN}/tests/xspawnee: \ + ${OBJ}/tests/xspawnee.o + +# The different flag combinations we check +INTEGRATIONS := default dfs ids eds j1 j2 j3 s +INTEGRATION_TESTS := ${INTEGRATIONS:%=check-%} + +# Check just `bfs` +check-default: ${BIN}/bfs ${ITEST_BINS} + +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs" ${TEST_FLAGS} + +# Check the different search strategies +check-dfs check-ids check-eds: ${BIN}/bfs ${ITEST_BINS} + +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -S ${@:check-%=%}" ${TEST_FLAGS} + +# Check various flags +check-j1 check-j2 check-j3 check-s: ${BIN}/bfs ${ITEST_BINS} + +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -${@:check-%=%}" ${TEST_FLAGS} + +# Run the integration tests +integration-tests: ${INTEGRATION_TESTS} +.PHONY: integration-tests + +${BIN}/tests/mksock: \ + ${OBJ}/tests/mksock.o \ + ${LIBBFS} + +${BIN}/tests/xtouch: \ + ${OBJ}/tests/xtouch.o \ + ${LIBBFS} + +# `make distcheck` configurations +DISTCHECKS := distcheck-asan distcheck-tsan distcheck-release + +# Don't use msan on macOS +IS_DARWIN,Darwin := y +IS_DARWIN := ${IS_DARWIN,${OS}} +DISTCHECK_MSAN, := distcheck-msan +DISTCHECKS += ${DISTCHECK_MSAN,${IS_DARWIN}} + +# Only add a 32-bit build on 64-bit Linux +DISTCHECK_M32,Linux,x86_64 := distcheck-m32 +DISTCHECKS += ${DISTCHECK_M32,${OS},${ARCH}} + +# Test multiple configurations +distcheck: ${DISTCHECKS} +.PHONY: distcheck + +# Per-distcheck configuration +DISTCHECK_CONFIG_asan := ASAN=y UBSAN=y +DISTCHECK_CONFIG_msan := MSAN=y UBSAN=y CC=clang +DISTCHECK_CONFIG_tsan := TSAN=y UBSAN=y CC=clang +DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_PATH=/usr/lib32/pkgconfig USE_LIBURING=n +DISTCHECK_CONFIG_release := RELEASE=y + +${DISTCHECKS}:: + +${MAKE} -rs BUILDDIR=${BUILDDIR}/$@ config ${DISTCHECK_CONFIG_${@:distcheck-%=%}} + +${MAKE} -s BUILDDIR=${BUILDDIR}/$@ check TEST_FLAGS="--sudo --verbose=skipped" + +## Packaging (`make install`) + +DEST_PREFIX := ${DESTDIR}${PREFIX} +DEST_MANDIR := ${DESTDIR}${MANDIR} + +install:: + ${MKDIR} ${DEST_PREFIX}/bin + ${INSTALL} -m755 ${BIN}/bfs ${DEST_PREFIX}/bin/bfs + ${MKDIR} ${DEST_MANDIR}/man1 + ${INSTALL} -m644 docs/bfs.1 ${DEST_MANDIR}/man1/bfs.1 + ${MKDIR} ${DEST_PREFIX}/share/bash-completion/completions + ${INSTALL} -m644 completions/bfs.bash ${DEST_PREFIX}/share/bash-completion/completions/bfs + ${MKDIR} ${DEST_PREFIX}/share/zsh/site-functions + ${INSTALL} -m644 completions/bfs.zsh ${DEST_PREFIX}/share/zsh/site-functions/_bfs + ${MKDIR} ${DEST_PREFIX}/share/fish/vendor_completions.d + ${INSTALL} -m644 completions/bfs.fish ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish + +uninstall:: + ${RM} ${DEST_PREFIX}/share/bash-completion/completions/bfs + ${RM} ${DEST_PREFIX}/share/zsh/site-functions/_bfs + ${RM} ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish + ${RM} ${DEST_MANDIR}/man1/bfs.1 + ${RM} ${DEST_PREFIX}/bin/bfs + +# Check that `make install` works and `make uninstall` removes everything +check-install:: + +${MAKE} install DESTDIR=${BUILDDIR}/pkg + +${MAKE} uninstall DESTDIR=${BUILDDIR}/pkg + ${BIN}/bfs ${BUILDDIR}/pkg -not -type d -print -exit 1 + ${RM} -r ${BUILDDIR}/pkg diff --git a/README.md b/README.md index 17226fd..b95c16b 100644 --- a/README.md +++ b/README.md @@ -333,6 +333,7 @@ Once you have the dependencies, you can build bfs. Download one of the [releases](https://github.com/tavianator/bfs/releases) or clone the [git repo](https://github.com/tavianator/bfs). Then run + $ make config $ make This will build the `./bin/bfs` binary. @@ -342,7 +343,8 @@ Run the test suite to make sure it works correctly: If you're interested in speed, you may want to build the release version instead: - $ make release + $ make config RELEASE=y + $ make Finally, if you want to install it globally, run diff --git a/config/cc.sh b/config/cc.sh new file mode 100755 index 0000000..04b142a --- /dev/null +++ b/config/cc.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Run the compiler and check if it succeeded + +printf '$ %s' "$XCC" >&2 +printf ' %q' "$@" >&2 +printf ' -o /dev/null\n' >&2 + +$XCC "$@" -o /dev/null diff --git a/config/empty.c b/config/empty.c new file mode 100644 index 0000000..4fa9a5b --- /dev/null +++ b/config/empty.c @@ -0,0 +1,6 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +int main(void) { + return 0; +} diff --git a/config/libacl.c b/config/libacl.c new file mode 100644 index 0000000..877cb69 --- /dev/null +++ b/config/libacl.c @@ -0,0 +1,6 @@ +#include + +int main(void) { + acl_free(0); + return 0; +} diff --git a/config/libcap.c b/config/libcap.c new file mode 100644 index 0000000..64188ac --- /dev/null +++ b/config/libcap.c @@ -0,0 +1,6 @@ +#include + +int main(void) { + cap_free(0); + return 0; +} diff --git a/config/liburing.c b/config/liburing.c new file mode 100644 index 0000000..456059c --- /dev/null +++ b/config/liburing.c @@ -0,0 +1,6 @@ +#include + +int main(void) { + io_uring_free_probe(0); + return 0; +} diff --git a/config/oniguruma.c b/config/oniguruma.c new file mode 100644 index 0000000..b834fac --- /dev/null +++ b/config/oniguruma.c @@ -0,0 +1,6 @@ +#include + +int main(void) { + onig_free(0); + return 0; +} diff --git a/config/pkg.sh b/config/pkg.sh new file mode 100755 index 0000000..6335b4b --- /dev/null +++ b/config/pkg.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# pkg-config wrapper that outputs a makefile fragment + +set -eu + +NAME="${1^^}" +declare -n XUSE="XUSE_$NAME" + +if [ "$XUSE" ]; then + USE="$XUSE" +elif [[ "$NOLIBS" == *y* ]]; then + USE=n +elif config/pkgconf.sh "$1"; then + USE=y +else + USE=n +fi + +printf '%s := %s\n' "USE_$NAME" "$USE" + +if [ "$USE" = y ]; then + printf 'CPPFLAGS += -DBFS_USE_%s=1\n' "$NAME" + + CFLAGS=$(config/pkgconf.sh --cflags "$1") + if [ "$CFLAGS" ]; then + printf 'CFLAGS += %s\n' "$CFLAGS" + fi + + LDFLAGS=$(config/pkgconf.sh --ldflags "$1") + if [ "$LDFLAGS" ]; then + printf 'LDFLAGS += %s\n' "$LDFLAGS" + fi + + LDLIBS=$(config/pkgconf.sh --ldlibs "$1") + if [ "$LDLIBS" ]; then + printf 'LDLIBS += %s\n' "$LDLIBS" + fi +else + printf 'CPPFLAGS += -DBFS_USE_%s=0\n' "$NAME" +fi diff --git a/config/pkgconf.sh b/config/pkgconf.sh new file mode 100755 index 0000000..070fad6 --- /dev/null +++ b/config/pkgconf.sh @@ -0,0 +1,64 @@ +#!/usr/bin/env bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# pkg-config wrapper with hardcoded fallbacks + +set -eu + +MODE= +if [[ "$1" == --* ]]; then + MODE="$1" + shift +fi + +if command -v "${PKG_CONFIG:-}" &>/dev/null; then + case "$MODE" in + --cflags) + "$PKG_CONFIG" --cflags "$@" + ;; + --ldflags) + "$PKG_CONFIG" --libs-only-L --libs-only-other "$@" + ;; + --ldlibs) + "$PKG_CONFIG" --libs-only-l "$@" + ;; + "") + "$PKG_CONFIG" "$@" + ;; + esac +else + for lib; do + case "$lib" in + libacl) + LDLIB=-lacl + ;; + libcap) + LDLIB=-lcap + ;; + liburing) + LDLIB=-luring + ;; + oniguruma) + LDLIB=-lonig + ;; + *) + printf 'error: Unknown package %s\n' "$lib" >&2 + exit 1 + esac + + case "$MODE" in + --ldlibs) + printf ' %s' "$LDLIB" + ;; + "") + config/cc.sh "config/$lib.c" "$LDLIB" || exit $? + ;; + esac + done + + if [ "$MODE" = "--ldlibs" ]; then + printf '\n' + fi +fi diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 7eb3a37..abf6185 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -7,9 +7,10 @@ Compiling `bfs` uses [GNU Make](https://www.gnu.org/software/make/) as its build system. A simple invocation of + $ make config $ make -should build `bfs` successfully, with no additional steps necessary. +should build `bfs` successfully. As usual with `make`, you can run a [parallel build](https://www.gnu.org/software/make/manual/html_node/Parallel.html) with `-j`. For example, to use all your cores, run `make -j$(nproc)`. @@ -17,25 +18,28 @@ For example, to use all your cores, run `make -j$(nproc)`. | Command | Description | |------------------|---------------------------------------------------------------| +| `make config` | Configures the build system | | `make` | Builds just the `bfs` binary | | `make all` | Builds everything, including the tests (but doesn't run them) | | `make check` | Builds everything, and runs the tests | | `make install` | Installs `bfs` (with man page, shell completions, etc.) | | `make uninstall` | Uninstalls `bfs` | +| `make clean` | Delete the build products | +| `make mrclean` | Delete all generated files, including the build configuration | -### Flag-like targets +### Build profiles -The build system provides a few shorthand targets for handy configurations: +The configuration system provides a few shorthand flags for handy configurations: -| Command | Description | -|----------------|-------------------------------------------------------------| -| `make release` | Build `bfs` with optimizations, LTO, and without assertions | -| `make asan` | Enable [AddressSanitizer] | -| `make lsan` | Enable [LeakSanitizer] | -| `make msan` | Enable [MemorySanitizer] | -| `make tsan` | Enable [ThreadSanitizer] | -| `make ubsan` | Enable [UndefinedBehaviorSanitizer] | -| `make gcov` | Enable [code coverage] | +| Command | Description | +|-------------------------|-------------------------------------------------------------| +| `make config RELEASE=y` | Build `bfs` with optimizations, LTO, and without assertions | +| `make config ASAN=y` | Enable [AddressSanitizer] | +| `make config LSAN=y` | Enable [LeakSanitizer] | +| `make config MSAN=y` | Enable [MemorySanitizer] | +| `make config TSAN=y` | Enable [ThreadSanitizer] | +| `make config UBSAN=y` | Enable [UndefinedBehaviorSanitizer] | +| `make config GCOV=y` | Enable [code coverage] | [AddressSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizer [LeakSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#stand-alone-mode @@ -44,38 +48,38 @@ The build system provides a few shorthand targets for handy configurations: [UndefinedBehaviorSanitizer]: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html [code coverage]: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html -You can combine multiple flags and other targets (e.g. `make asan ubsan check`), but not all of them will work together. +You can combine multiple profiles (e.g. `make config ASAN=y UBSAN=y`), but not all of them will work together. ### Flags -Other flags are controlled with `make` variables and/or environment variables. -Here are some of the common ones; check the [`GNUmakefile`](/GNUmakefile) for more. - -| Flag | Description | -|----------------------------------|---------------------------------------------| -| `CC` | The C compiler to use, e.g. `make CC=clang` | -| `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | -| `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | -| `USE_ACL`
`USE_LIBCAP`
... | Enable/disable [optional dependencies] | -| `TEST_FLAGS` | `tests.sh` flags for `make check` | -| `BUILDDIR` | The build output directory (default: `.`) | -| `DESTDIR` | The root directory for `make install` | -| `PREFIX` | The installation prefix (default: `/usr`) | -| `MANDIR` | The man page installation directory | +Other flags can be specified on the `make config` command line or in the environment. +Here are some of the common ones; check the [`Makefile`](/Makefile) for more. + +| Flag | Description | +|----------------------------------|----------------------------------------------------| +| `CC` | The C compiler to use, e.g. `make config CC=clang` | +| `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | +| `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | +| `USE_ACL`
`USE_LIBCAP`
... | Enable/disable [optional dependencies] | +| `TEST_FLAGS` | `tests.sh` flags for `make check` | +| `BUILDDIR` | The build output directory (default: `.`) | +| `DESTDIR` | The root directory for `make install` | +| `PREFIX` | The installation prefix (default: `/usr`) | +| `MANDIR` | The man page installation directory | [optional dependencies]: #dependencies ### Dependencies `bfs` depends on some system libraries for some of its features. -These dependencies are optional, and can be turned off at build time if necessary by setting the appropriate variable to the empty string (e.g. `make USE_ONIGURUMA=`). +These dependencies are optional, and can be turned off in `make config` if necessary by setting the appropriate variable to `n` (e.g. `make config USE_ONIGURUMA=n`). -| Dependency | Platforms | `make` flag | -|-------------|------------|-----------------| -| [acl] | Linux only | `USE_ACL` | -| [libcap] | Linux only | `USE_LIBCAP` | -| [liburing] | Linux only | `USE_LIBURING` | -| [Oniguruma] | All | `USE_ONIGURUMA` | +| Dependency | Platforms | `make config` flag | +|-------------|------------|--------------------| +| [acl] | Linux only | `USE_ACL` | +| [libcap] | Linux only | `USE_LIBCAP` | +| [liburing] | Linux only | `USE_LIBURING` | +| [Oniguruma] | All | `USE_ONIGURUMA` | [acl]: https://savannah.nongnu.org/projects/acl [libcap]: https://sites.google.com/site/fullycapable/ @@ -84,21 +88,22 @@ These dependencies are optional, and can be turned off at build time if necessar ### Dependency tracking -The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`GNUmakefile`](/GNUmakefile)). +The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`Makefile`](/Makefile)). So if you edit a header file, `make` will rebuild the necessary object files ensuring they don't go out of sync. +We also add a dependency on the current configuration, so you can change configurations and rebuild without having to `make clean`. + We go one step further than most build systems by tracking the flags that were used for the previous compilation. That means you can change configurations without having to `make clean`. For example, + $ make config + $ make + $ make config RELEASE=y $ make - $ make release will build the project in debug mode and then rebuild it in release mode. -A side effect of this may be surprising: `make check` by itself will rebuild the project in the default configuration. -To test a different configuration, you'll have to repeat it (e.g. `make release check`). - Testing ------- diff --git a/docs/USAGE.md b/docs/USAGE.md index 071c95b..70f8475 100644 --- a/docs/USAGE.md +++ b/docs/USAGE.md @@ -7,8 +7,8 @@ When invoked with no arguments, `bfs` will list everything under the current dir ```console $ bfs . -./GNUmakefile ./LICENSE +./Makefile ./README.md ./completions ./docs diff --git a/src/config.h b/src/config.h index 506ad3e..2eff1fc 100644 --- a/src/config.h +++ b/src/config.h @@ -29,13 +29,14 @@ #ifndef BFS_COMMAND # define BFS_COMMAND "bfs" #endif -#ifndef BFS_VERSION -# define BFS_VERSION "3.1.3" -#endif #ifndef BFS_HOMEPAGE # define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" #endif +// This is a symbol instead of a literal so we don't have to rebuild everything +// when the version number changes +extern const char bfs_version[]; + // Check for system headers #ifdef __has_include @@ -97,10 +98,10 @@ # define BFS_USE_PATHS_H BFS_HAS_PATHS_H #endif #ifndef BFS_USE_SYS_ACL_H -# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__) +# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL)) #endif #ifndef BFS_USE_SYS_CAPABILITY_H -# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__) +# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP)) #endif #ifndef BFS_USE_SYS_EXTATTR_H # define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__) diff --git a/src/main.c b/src/main.c index e120f03..b4d65ce 100644 --- a/src/main.c +++ b/src/main.c @@ -40,6 +40,7 @@ * - thread.h (multi-threading) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) + * - version.c (defines the version number) * - xregex.[ch] (regular expression support) * - xspawn.[ch] (spawns processes) * - xtime.[ch] (date/time handling utilities) diff --git a/src/parse.c b/src/parse.c index 2dfcab2..38ebf3f 100644 --- a/src/parse.c +++ b/src/parse.c @@ -2909,7 +2909,7 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2 * "Parse" -version. */ static struct bfs_expr *parse_version(struct bfs_parser *parser, int arg1, int arg2) { - cfprintf(parser->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, BFS_VERSION); + cfprintf(parser->ctx->cout, "${ex}%s${rs} ${bld}%s${rs}\n\n", BFS_COMMAND, bfs_version); printf("%s\n", BFS_HOMEPAGE); diff --git a/src/version.c b/src/version.c new file mode 100644 index 0000000..736f3d5 --- /dev/null +++ b/src/version.c @@ -0,0 +1,6 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include "config.h" + +const char bfs_version[] = BFS_VERSION; diff --git a/tests/run.sh b/tests/run.sh index 720515d..ab1ed6d 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -156,7 +156,7 @@ comake() { -f "$TESTS/tests.mk" \ DONE=$DONE_PIPE \ READY=$READY_PIPE \ - "${TEST_CASES[@]/#/tests/}" \ + "${!TEST_CASES[@]}" \ /dev/null } diff --git a/tests/tests.mk b/tests/tests.mk index 5bf4f6c..035ca79 100644 --- a/tests/tests.mk +++ b/tests/tests.mk @@ -1,7 +1,13 @@ # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD -# GNU makefile that exposes make's job control to tests.sh +# Makefile that exposes make's job control to tests.sh -tests/%: +# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to +.OBJDIR: . + +# Turn off implicit rules +.SUFFIXES: + +.DEFAULT:: bash -c 'printf . >&$(READY) && read -r -N1 -u$(DONE)' -- cgit v1.2.3 From d0804fcf6b690de364c35d8354b21f72422df079 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 9 Apr 2024 20:47:11 -0400 Subject: tests: Move newer_link out of posix/ POSIX has clarified that it's unspecified whether -newer uses times from stat() or lstat(), because implementations vary. It does specify that it must fall back to lstat() for broken links, so test that. Link: https://austingroupbugs.net/view.php?id=1776 --- tests/gnu/newer_link.out | 1 + tests/gnu/newer_link.sh | 1 + tests/posix/newer_broken.out | 1 + tests/posix/newer_broken.sh | 4 ++++ tests/posix/newer_link.out | 1 - tests/posix/newer_link.sh | 1 - 6 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 tests/gnu/newer_link.out create mode 100644 tests/gnu/newer_link.sh create mode 100644 tests/posix/newer_broken.out create mode 100644 tests/posix/newer_broken.sh delete mode 100644 tests/posix/newer_link.out delete mode 100644 tests/posix/newer_link.sh diff --git a/tests/gnu/newer_link.out b/tests/gnu/newer_link.out new file mode 100644 index 0000000..d2dcdd1 --- /dev/null +++ b/tests/gnu/newer_link.out @@ -0,0 +1 @@ +times diff --git a/tests/gnu/newer_link.sh b/tests/gnu/newer_link.sh new file mode 100644 index 0000000..685ac78 --- /dev/null +++ b/tests/gnu/newer_link.sh @@ -0,0 +1 @@ +bfs_diff times -newer times/l diff --git a/tests/posix/newer_broken.out b/tests/posix/newer_broken.out new file mode 100644 index 0000000..d2dcdd1 --- /dev/null +++ b/tests/posix/newer_broken.out @@ -0,0 +1 @@ +times diff --git a/tests/posix/newer_broken.sh b/tests/posix/newer_broken.sh new file mode 100644 index 0000000..dccaa73 --- /dev/null +++ b/tests/posix/newer_broken.sh @@ -0,0 +1,4 @@ +ln -s nowhere "$TEST/broken" +"$XTOUCH" -h -t "1991-12-14 00:03" "$TEST/broken" + +bfs_diff times -newer "$TEST/broken" diff --git a/tests/posix/newer_link.out b/tests/posix/newer_link.out deleted file mode 100644 index d2dcdd1..0000000 --- a/tests/posix/newer_link.out +++ /dev/null @@ -1 +0,0 @@ -times diff --git a/tests/posix/newer_link.sh b/tests/posix/newer_link.sh deleted file mode 100644 index 685ac78..0000000 --- a/tests/posix/newer_link.sh +++ /dev/null @@ -1 +0,0 @@ -bfs_diff times -newer times/l -- cgit v1.2.3 From 170fd017a1e7d87d0d53502b8f407c9163aa2957 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 Apr 2024 10:02:00 -0400 Subject: docs/BUILDING: Fixes for new build system --- docs/BUILDING.md | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index abf6185..072da92 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -25,7 +25,7 @@ For example, to use all your cores, run `make -j$(nproc)`. | `make install` | Installs `bfs` (with man page, shell completions, etc.) | | `make uninstall` | Uninstalls `bfs` | | `make clean` | Delete the build products | -| `make mrclean` | Delete all generated files, including the build configuration | +| `make distclean` | Delete all generated files, including the build configuration | ### Build profiles @@ -55,17 +55,17 @@ You can combine multiple profiles (e.g. `make config ASAN=y UBSAN=y`), but not a Other flags can be specified on the `make config` command line or in the environment. Here are some of the common ones; check the [`Makefile`](/Makefile) for more. -| Flag | Description | -|----------------------------------|----------------------------------------------------| -| `CC` | The C compiler to use, e.g. `make config CC=clang` | -| `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | -| `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | -| `USE_ACL`
`USE_LIBCAP`
... | Enable/disable [optional dependencies] | -| `TEST_FLAGS` | `tests.sh` flags for `make check` | -| `BUILDDIR` | The build output directory (default: `.`) | -| `DESTDIR` | The root directory for `make install` | -| `PREFIX` | The installation prefix (default: `/usr`) | -| `MANDIR` | The man page installation directory | +| Flag | Description | +|-------------------------------------|----------------------------------------------------| +| `CC` | The C compiler to use, e.g. `make config CC=clang` | +| `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | +| `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | +| `USE_LIBACL`
`USE_LIBCAP`
... | Enable/disable [optional dependencies] | +| `TEST_FLAGS` | `tests.sh` flags for `make check` | +| `BUILDDIR` | The build output directory (default: `.`) | +| `DESTDIR` | The root directory for `make install` | +| `PREFIX` | The installation prefix (default: `/usr`) | +| `MANDIR` | The man page installation directory | [optional dependencies]: #dependencies @@ -76,12 +76,12 @@ These dependencies are optional, and can be turned off in `make config` if neces | Dependency | Platforms | `make config` flag | |-------------|------------|--------------------| -| [acl] | Linux only | `USE_ACL` | +| [libacl] | Linux only | `USE_LIBACL` | | [libcap] | Linux only | `USE_LIBCAP` | | [liburing] | Linux only | `USE_LIBURING` | | [Oniguruma] | All | `USE_ONIGURUMA` | -[acl]: https://savannah.nongnu.org/projects/acl +[libacl]: https://savannah.nongnu.org/projects/acl [libcap]: https://sites.google.com/site/fullycapable/ [liburing]: https://github.com/axboe/liburing [Oniguruma]: https://github.com/kkos/oniguruma -- cgit v1.2.3 From 9f90d09fcf58269dc09bad90b360d46c374e56e9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 Apr 2024 10:03:15 -0400 Subject: build: Add optional libselinux dependency --- Makefile | 6 +++++- config/libselinux.c | 6 ++++++ config/pkgconf.sh | 3 +++ docs/BUILDING.md | 14 ++++++++------ 4 files changed, 22 insertions(+), 7 deletions(-) create mode 100644 config/libselinux.c diff --git a/Makefile b/Makefile index 326ee87..ed59126 100644 --- a/Makefile +++ b/Makefile @@ -143,6 +143,7 @@ export RELEASE_CFLAGS= -O3 -flto=auto # $ make config USE_LIBURING=n USE_ONIGURUMA=y USE_LIBACL ?= USE_LIBCAP ?= +USE_LIBSELINUX ?= USE_LIBURING ?= USE_ONIGURUMA ?= @@ -167,6 +168,7 @@ _XLDLIBS := ${LDLIBS} _XUSE_LIBACL := ${USE_LIBACL} _XUSE_LIBCAP := ${USE_LIBCAP} +_XUSE_LIBSELINUX := ${USE_LIBSELINUX} _XUSE_LIBURING := ${USE_LIBURING} _XUSE_ONIGURUMA := ${USE_ONIGURUMA} @@ -197,6 +199,7 @@ export XLDLIBS=${_XLDLIBS} export XUSE_LIBACL=${_XUSE_LIBACL} export XUSE_LIBCAP=${_XUSE_LIBCAP} +export XUSE_LIBSELINUX=${_XUSE_LIBSELINUX} export XUSE_LIBURING=${_XUSE_LIBURING} export XUSE_ONIGURUMA=${_XUSE_ONIGURUMA} @@ -210,6 +213,7 @@ CONFIG := ${GEN}/config.mk PKGS := \ ${GEN}/libacl.mk \ ${GEN}/libcap.mk \ + ${GEN}/libselinux.mk \ ${GEN}/liburing.mk \ ${GEN}/oniguruma.mk @@ -478,7 +482,7 @@ distcheck: ${DISTCHECKS} DISTCHECK_CONFIG_asan := ASAN=y UBSAN=y DISTCHECK_CONFIG_msan := MSAN=y UBSAN=y CC=clang DISTCHECK_CONFIG_tsan := TSAN=y UBSAN=y CC=clang -DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_PATH=/usr/lib32/pkgconfig USE_LIBURING=n +DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_LIBDIR=/usr/lib32/pkgconfig USE_LIBURING=n DISTCHECK_CONFIG_release := RELEASE=y ${DISTCHECKS}:: diff --git a/config/libselinux.c b/config/libselinux.c new file mode 100644 index 0000000..72f5d33 --- /dev/null +++ b/config/libselinux.c @@ -0,0 +1,6 @@ +#include + +int main(void) { + freecon(0); + return 0; +} diff --git a/config/pkgconf.sh b/config/pkgconf.sh index 070fad6..286c19c 100755 --- a/config/pkgconf.sh +++ b/config/pkgconf.sh @@ -37,6 +37,9 @@ else libcap) LDLIB=-lcap ;; + libselinux) + LDLIB=-lselinux + ;; liburing) LDLIB=-luring ;; diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 072da92..4ed139c 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -74,15 +74,17 @@ Here are some of the common ones; check the [`Makefile`](/Makefile) for more. `bfs` depends on some system libraries for some of its features. These dependencies are optional, and can be turned off in `make config` if necessary by setting the appropriate variable to `n` (e.g. `make config USE_ONIGURUMA=n`). -| Dependency | Platforms | `make config` flag | -|-------------|------------|--------------------| -| [libacl] | Linux only | `USE_LIBACL` | -| [libcap] | Linux only | `USE_LIBCAP` | -| [liburing] | Linux only | `USE_LIBURING` | -| [Oniguruma] | All | `USE_ONIGURUMA` | +| Dependency | Platforms | `make config` flag | +|--------------|------------|--------------------| +| [libacl] | Linux only | `USE_LIBACL` | +| [libcap] | Linux only | `USE_LIBCAP` | +| [liburing] | Linux only | `USE_LIBURING` | +| [libselinux] | Linux only | `USE_LIBSELINUX` | +| [Oniguruma] | All | `USE_ONIGURUMA` | [libacl]: https://savannah.nongnu.org/projects/acl [libcap]: https://sites.google.com/site/fullycapable/ +[libselinux]: https://github.com/SELinuxProject/selinux [liburing]: https://github.com/axboe/liburing [Oniguruma]: https://github.com/kkos/oniguruma -- cgit v1.2.3 From c0cbaab04b3d37a1786f04018eb6226359291031 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 Apr 2024 10:04:07 -0400 Subject: fsade: Add libselinux wrappers --- src/fsade.c | 33 +++++++++++++++++++++++++++++++++ src/fsade.h | 17 +++++++++++++++++ 2 files changed, 50 insertions(+) diff --git a/src/fsade.c b/src/fsade.c index ee17416..0810c7f 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -22,6 +22,10 @@ # include #endif +#if BFS_CAN_CHECK_CONTEXT +# include +#endif + #if BFS_USE_SYS_EXTATTR_H # include #elif BFS_USE_SYS_XATTR_H @@ -414,3 +418,32 @@ int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { } #endif + +char *bfs_getfilecon(const struct BFTW *ftwbuf) { +#if BFS_CAN_CHECK_CONTEXT + const char *path = fake_at(ftwbuf); + + char *con; + int ret; + if (ftwbuf->type == BFS_LNK) { + ret = lgetfilecon(path, &con); + } else { + ret = getfilecon(path, &con); + } + + if (ret >= 0) { + return con; + } else { + return NULL; + } +#else + errno = ENOTSUP; + return NULL; +#endif +} + +void bfs_freecon(char *con) { +#if BFS_CAN_CHECK_CONTEXT + freecon(con); +#endif +} diff --git a/src/fsade.h b/src/fsade.h index 413938d..1f1dbfc 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -20,6 +20,8 @@ # endif #endif +#define BFS_CAN_CHECK_CONTEXT BFS_USE_LIBSELINUX + #define BFS_CAN_CHECK_XATTRS (BFS_USE_SYS_EXTATTR_H || BFS_USE_SYS_XATTR_H) struct BFTW; @@ -66,4 +68,19 @@ int bfs_check_xattrs(const struct BFTW *ftwbuf); */ int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name); +/** + * Get a file's SELinux context + * + * @param ftwbuf + * The file to check. + * @return + * The file's SELinux context, or NULL on failure. + */ +char *bfs_getfilecon(const struct BFTW *ftwbuf); + +/** + * Free a bfs_getfilecon() result. + */ +void bfs_freecon(char *con); + #endif // BFS_FSADE_H -- cgit v1.2.3 From 8f6b0c1b360f2fea3f7f6563808513cbdd51df80 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 Apr 2024 10:10:51 -0400 Subject: Implement -context Closes: https://github.com/tavianator/bfs/issues/27 --- completions/bfs.bash | 1 + completions/bfs.fish | 1 + completions/bfs.zsh | 1 + docs/bfs.1 | 4 ++ src/eval.c | 43 ++++++++++++------- src/eval.h | 1 + src/parse.c | 114 +++++++++++++++++++++++++++++---------------------- 7 files changed, 103 insertions(+), 62 deletions(-) diff --git a/completions/bfs.bash b/completions/bfs.bash index 816f1ec..6fd82c8 100644 --- a/completions/bfs.bash +++ b/completions/bfs.bash @@ -32,6 +32,7 @@ _bfs() { # (e.g. because they are numeric, glob, regexp, time, etc.) local nocomp=( -{a,B,c,m}{min,since,time} + -context -ilname -iname -inum diff --git a/completions/bfs.fish b/completions/bfs.fish index 0c58ef4..24b0ad9 100644 --- a/completions/bfs.fish +++ b/completions/bfs.fish @@ -71,6 +71,7 @@ complete -c bfs -o Btime -d "Find files birthed specified number of days ago" -x complete -c bfs -o ctime -d "Find files changed specified number of days ago" -x complete -c bfs -o mtime -d "Find files modified specified number of days ago" -x complete -c bfs -o capable -d "Find files with capabilities set" +complete -c bfs -o context -d "Find files by SELinux context" -x complete -c bfs -o depth -d "Find files with specified number of depth" -x complete -c bfs -o empty -d "Find empty files/directories" complete -c bfs -o executable -d "Find files the current user can execute" diff --git a/completions/bfs.zsh b/completions/bfs.zsh index 07db456..432ab8c 100644 --- a/completions/bfs.zsh +++ b/completions/bfs.zsh @@ -74,6 +74,7 @@ args=( '*-mtime[find files modified N days ago]:modification time (days):->times' '*-capable[find files with POSIX.1e capabilities set]' + '*-context[find files by SELinux context]:pattern' # -depth without parameters exist above. I don't know how to handle this gracefully '*-empty[find empty files/directories]' '*-executable[find files the current user can execute]' diff --git a/docs/bfs.1 b/docs/bfs.1 index 3a4f15a..54166ab 100644 --- a/docs/bfs.1 +++ b/docs/bfs.1 @@ -452,6 +452,10 @@ Find files with POSIX.1e .BR capabilities (7) set. .TP +\fB\-context \fIGLOB\fR +Find files whose SELinux context matches the +.IR GLOB . +.TP \fB\-depth\fR [\fI\-+\fR]\fIN\fR Find files with depth .IR N . diff --git a/src/eval.c b/src/eval.c index 2f06858..d0112c2 100644 --- a/src/eval.c +++ b/src/eval.c @@ -145,6 +145,20 @@ bool bfs_expr_cmp(const struct bfs_expr *expr, long long n) { return false; } +/** Common code for fnmatch() tests. */ +static bool eval_fnmatch(const struct bfs_expr *expr, const char *str) { + if (expr->literal) { +#ifdef FNM_CASEFOLD + if (expr->fnm_flags & FNM_CASEFOLD) { + return strcasecmp(expr->pattern, str) == 0; + } +#endif + return strcmp(expr->pattern, str) == 0; + } else { + return fnmatch(expr->pattern, str, expr->fnm_flags) == 0; + } +} + /** * -true test. */ @@ -193,6 +207,21 @@ bool eval_capable(const struct bfs_expr *expr, struct bfs_eval *state) { } } +/** + * -context test. + */ +bool eval_context(const struct bfs_expr *expr, struct bfs_eval *state) { + char *con = bfs_getfilecon(state->ftwbuf); + if (!con) { + eval_report_error(state); + return false; + } + + bool ret = eval_fnmatch(expr, con); + bfs_freecon(con); + return ret; +} + /** * Get the given timespec field out of a stat buffer. */ @@ -546,20 +575,6 @@ bool eval_links(const struct bfs_expr *expr, struct bfs_eval *state) { return bfs_expr_cmp(expr, statbuf->nlink); } -/** Common code for fnmatch() tests. */ -static bool eval_fnmatch(const struct bfs_expr *expr, const char *str) { - if (expr->literal) { -#ifdef FNM_CASEFOLD - if (expr->fnm_flags & FNM_CASEFOLD) { - return strcasecmp(expr->pattern, str) == 0; - } -#endif - return strcmp(expr->pattern, str) == 0; - } else { - return fnmatch(expr->pattern, str, expr->fnm_flags) == 0; - } -} - /** * -i?lname test. */ diff --git a/src/eval.h b/src/eval.h index f7f6c77..ae43628 100644 --- a/src/eval.h +++ b/src/eval.h @@ -49,6 +49,7 @@ bool eval_false(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_access(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_acl(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_capable(const struct bfs_expr *expr, struct bfs_eval *state); +bool eval_context(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_perm(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_xattr(const struct bfs_expr *expr, struct bfs_eval *state); bool eval_xattrname(const struct bfs_expr *expr, struct bfs_eval *state); diff --git a/src/parse.c b/src/parse.c index 38ebf3f..a3e32fe 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1075,6 +1075,67 @@ static struct bfs_expr *parse_color(struct bfs_parser *parser, int color, int ar return expr; } +/** + * Common code for fnmatch() tests. + */ +static struct bfs_expr *parse_fnmatch(const struct bfs_parser *parser, struct bfs_expr *expr, bool casefold) { + if (!expr) { + return NULL; + } + + expr->pattern = expr->argv[1]; + + if (casefold) { +#ifdef FNM_CASEFOLD + expr->fnm_flags = FNM_CASEFOLD; +#else + parse_expr_error(parser, expr, "Missing platform support.\n"); + return NULL; +#endif + } else { + expr->fnm_flags = 0; + } + + // POSIX says, about fnmatch(): + // + // If pattern ends with an unescaped , fnmatch() shall + // return a non-zero value (indicating either no match or an error). + // + // But not all implementations obey this, so check for it ourselves. + size_t i, len = strlen(expr->pattern); + for (i = 0; i < len; ++i) { + if (expr->pattern[len - i - 1] != '\\') { + break; + } + } + if (i % 2 != 0) { + parse_expr_warning(parser, expr, "Unescaped trailing backslash.\n\n"); + expr->eval_fn = eval_false; + return expr; + } + + // strcmp() can be much faster than fnmatch() since it doesn't have to + // parse the pattern, so special-case patterns with no wildcards. + // + // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 + expr->literal = strcspn(expr->pattern, "?*\\[") == len; + + return expr; +} + +/** + * Parse -context. + */ +static struct bfs_expr *parse_context(struct bfs_parser *parser, int flag, int arg2) { +#if BFS_CAN_CHECK_CONTEXT + struct bfs_expr *expr = parse_unary_test(parser, eval_context); + return parse_fnmatch(parser, expr, false); +#else + parse_error(parser, "Missing platform support.\n"); + return NULL; +#endif +} + /** * Parse -{false,true}. */ @@ -1631,54 +1692,6 @@ static struct bfs_expr *parse_mount(struct bfs_parser *parser, int arg1, int arg return expr; } -/** - * Common code for fnmatch() tests. - */ -static struct bfs_expr *parse_fnmatch(const struct bfs_parser *parser, struct bfs_expr *expr, bool casefold) { - if (!expr) { - return NULL; - } - - expr->pattern = expr->argv[1]; - - if (casefold) { -#ifdef FNM_CASEFOLD - expr->fnm_flags = FNM_CASEFOLD; -#else - parse_expr_error(parser, expr, "Missing platform support.\n"); - return NULL; -#endif - } else { - expr->fnm_flags = 0; - } - - // POSIX says, about fnmatch(): - // - // If pattern ends with an unescaped , fnmatch() shall - // return a non-zero value (indicating either no match or an error). - // - // But not all implementations obey this, so check for it ourselves. - size_t i, len = strlen(expr->pattern); - for (i = 0; i < len; ++i) { - if (expr->pattern[len - i - 1] != '\\') { - break; - } - } - if (i % 2 != 0) { - parse_expr_warning(parser, expr, "Unescaped trailing backslash.\n\n"); - expr->eval_fn = eval_false; - return expr; - } - - // strcmp() can be much faster than fnmatch() since it doesn't have to - // parse the pattern, so special-case patterns with no wildcards. - // - // https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18_13_01 - expr->literal = strcspn(expr->pattern, "?*\\[") == len; - - return expr; -} - /** * Parse -i?name. */ @@ -2767,6 +2780,10 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2 #if BFS_CAN_CHECK_CAPABILITIES cfprintf(cout, " ${blu}-capable${rs}\n"); cfprintf(cout, " Find files with POSIX.1e capabilities set\n"); +#endif +#if BFS_CAN_CHECK_CONTEXT + cfprintf(cout, " ${blu}-context${rs} ${bld}GLOB${rs}\n"); + cfprintf(cout, " Find files with SELinux context matching a glob pattern\n"); #endif cfprintf(cout, " ${blu}-depth${rs} ${bld}[-+]N${rs}\n"); cfprintf(cout, " Find files with depth ${bld}N${rs}\n"); @@ -2961,6 +2978,7 @@ static const struct table_entry parse_table[] = { {"-cmin", T_TEST, parse_min, BFS_STAT_CTIME}, {"-cnewer", T_TEST, parse_newer, BFS_STAT_CTIME}, {"-color", T_OPTION, parse_color, true}, + {"-context", T_TEST, parse_context, true}, {"-csince", T_TEST, parse_since, BFS_STAT_CTIME}, {"-ctime", T_TEST, parse_time, BFS_STAT_CTIME}, {"-d", T_FLAG, parse_depth}, -- cgit v1.2.3 From 35656951c896281a1ca581fdc63daecb1681a2d4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 Apr 2024 10:11:30 -0400 Subject: printf: Implement %Z --- src/printf.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/printf.c b/src/printf.c index 487f039..3b8269e 100644 --- a/src/printf.c +++ b/src/printf.c @@ -12,6 +12,7 @@ #include "dir.h" #include "dstring.h" #include "expr.h" +#include "fsade.h" #include "mtab.h" #include "pwcache.h" #include "stat.h" @@ -571,6 +572,19 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return ret; } +/** %Z: SELinux context */ +attr(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) { + return -1; + } + + int ret = dyn_fprintf(cfile->file, fmt, con); + bfs_freecon(con); + return ret; +} + /** * Append a literal string to the chain. */ @@ -840,6 +854,15 @@ int bfs_printf_parse(const struct bfs_ctx *ctx, struct bfs_expr *expr, const cha case 'Y': fmt.fn = bfs_printf_Y; break; + case 'Z': +#if BFS_CAN_CHECK_CONTEXT + fmt.fn = bfs_printf_Z; + break; +#else + bfs_expr_error(ctx, expr); + bfs_error(ctx, "Missing platform support for '%%%c'.\n", c); + goto fmt_error; +#endif case 'A': fmt.stat_field = BFS_STAT_ATIME; -- cgit v1.2.3 From 8f5ce115d51d31756b6911310a0deb1bafba46c0 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 Apr 2024 11:25:43 -0400 Subject: config/cc.sh: Pass all the flags when running the compiler --- config/cc.sh | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/cc.sh b/config/cc.sh index 04b142a..7e5c0d5 100755 --- a/config/cc.sh +++ b/config/cc.sh @@ -5,8 +5,12 @@ # Run the compiler and check if it succeeded -printf '$ %s' "$XCC" >&2 -printf ' %q' "$@" >&2 -printf ' -o /dev/null\n' >&2 +set -eux -$XCC "$@" -o /dev/null +$XCC \ + $BFS_CPPFLAGS $XCPPFLAGS ${EXTRA_CPPFLAGS:-} \ + $BFS_CFLAGS $XCFLAGS ${EXTRA_CFLAGS:-} \ + $XLDFLAGS ${EXTRA_LDFLAGS:-} \ + "$@" \ + $XLDLIBS ${EXTRA_LDLIBS:-} $BFS_LDLIBS \ + -o /dev/null -- cgit v1.2.3 From 6c8d11e8e5b3457286fcda75b6516e93f1f12f17 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 Apr 2024 13:13:19 -0400 Subject: build: Run pkg-config with all packages at once --- Makefile | 107 ++++++++++++++++++++++++++---------------------------- config/cc.sh | 8 +--- config/deps.mk | 13 +++++++ config/pkg.mk | 11 ++++++ config/pkg.sh | 24 ++---------- config/pkgconf.sh | 51 ++++++++++++++++++-------- config/pkgs.mk | 14 +++++++ config/vars.mk | 21 +++++++++++ 8 files changed, 150 insertions(+), 99 deletions(-) create mode 100644 config/deps.mk create mode 100644 config/pkg.mk create mode 100644 config/pkgs.mk create mode 100644 config/vars.mk diff --git a/Makefile b/Makefile index ed59126..0df7ce5 100644 --- a/Makefile +++ b/Makefile @@ -166,12 +166,6 @@ _XCFLAGS := ${CFLAGS} _XLDFLAGS := ${LDFLAGS} _XLDLIBS := ${LDLIBS} -_XUSE_LIBACL := ${USE_LIBACL} -_XUSE_LIBCAP := ${USE_LIBCAP} -_XUSE_LIBSELINUX := ${USE_LIBSELINUX} -_XUSE_LIBURING := ${USE_LIBURING} -_XUSE_ONIGURUMA := ${USE_ONIGURUMA} - # GNU make supports `export VAR`, but BSD make requires `export VAR=value`. # Sadly, GNU make gives a recursion error on `export VAR=${VAR}`. @@ -197,40 +191,26 @@ export XCFLAGS=${_XCFLAGS} export XLDFLAGS=${_XLDFLAGS} export XLDLIBS=${_XLDLIBS} -export XUSE_LIBACL=${_XUSE_LIBACL} -export XUSE_LIBCAP=${_XUSE_LIBCAP} -export XUSE_LIBSELINUX=${_XUSE_LIBSELINUX} -export XUSE_LIBURING=${_XUSE_LIBURING} -export XUSE_ONIGURUMA=${_XUSE_ONIGURUMA} - # The configuration file generated by `make config` CONFIG := ${GEN}/config.mk -include ${CONFIG} ## Configuration phase (`make config`) -# External dependencies -PKGS := \ - ${GEN}/libacl.mk \ - ${GEN}/libcap.mk \ - ${GEN}/libselinux.mk \ - ${GEN}/liburing.mk \ - ${GEN}/oniguruma.mk - # Makefile fragments generated by `make config` MKS := \ ${GEN}/vars.mk \ ${GEN}/deps.mk \ ${GEN}/objs.mk \ - ${PKGS} + ${GEN}/pkgs.mk # The configuration goal itself config: ${MKS} - @printf 'include $${GEN}/%s\n' ${MKS:${GEN}/%=%} >${CONFIG} + @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >${CONFIG} .PHONY: config # Saves the configurable variables -${GEN}/vars.mk:: +${GEN}/vars.mk: @${XMKDIR} ${@D} @printf 'PREFIX := %s\n' "$$XPREFIX" >$@ @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ @@ -240,50 +220,67 @@ ${GEN}/vars.mk:: @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ @printf 'RM := %s\n' "$$XRM" >>$@ - @printf 'CPPFLAGS := %s\n' "$$BFS_CPPFLAGS" >>$@ - @test "${TSAN}" != y || printf 'CPPFLAGS += %s\n' "$$TSAN_CPPFLAGS" >>$@ - @test "${LINT}" != y || printf 'CPPFLAGS += %s\n' "$$LINT_CPPFLAGS" >>$@ - @test "${RELEASE}" != y || printf 'CPPFLAGS += %s\n' "$$RELEASE_CPPFLAGS" >>$@ - @test -z "$$XCPPFLAGS" || printf 'CPPFLAGS += %s\n' "$$XCPPFLAGS" >>$@ - @test -z "$$EXTRA_CPPFLAGS" || printf 'CPPFLAGS += %s\n' "$$EXTRA_CPPFLAGS" >>$@ - @printf 'CFLAGS := %s\n' "$$BFS_CFLAGS" >>$@ - @test "${ASAN}" != y || printf 'CFLAGS += %s\n' "$$ASAN_CFLAGS" >>$@ - @test "${LSAN}" != y || printf 'CFLAGS += %s\n' "$$LSAN_CFLAGS" >>$@ - @test "${MSAN}" != y || printf 'CFLAGS += %s\n' "$$MSAN_CFLAGS" >>$@ - @test "${TSAN}" != y || printf 'CFLAGS += %s\n' "$$TSAN_CFLAGS" >>$@ - @test "${UBSAN}" != y || printf 'CFLAGS += %s\n' "$$UBSAN_CFLAGS" >>$@ - @case "${SAN}" in *y*) printf 'CFLAGS += %s\n' "$$SAN_CFLAGS" >>$@ ;; esac - @test "${GCOV}" != y || printf 'CFLAGS += %s\n' "$$GCOV_CFLAGS" >>$@ - @test "${LINT}" != y || printf 'CFLAGS += %s\n' "$$LINT_CFLAGS" >>$@ - @test "${RELEASE}" != y || printf 'CFLAGS += %s\n' "$$RELEASE_CFLAGS" >>$@ - @test -z "$$XCFLAGS" || printf 'CFLAGS += %s\n' "$$XCFLAGS" >>$@ - @test -z "$$EXTRA_CFLAGS" || printf 'CFLAGS += %s\n' "$$EXTRA_CFLAGS" >>$@ - @printf 'LDFLAGS := %s\n' "$$XLDFLAGS" >>$@ - @test -z "$$EXTRA_LDFLAGS" || printf 'LDFLAGS += %s\n' "$$EXTRA_LDFLAGS" >>$@ - @printf 'LDLIBS := %s\n' "$$XLDLIBS" >>$@ - @test -z "$$EXTRA_LDLIBS" || printf 'LDLIBS += %s\n' "$$EXTRA_LDLIBS" >>$@ - @test -z "$$BFS_LDLIBS" || printf 'LDLIBS += %s\n' "$$BFS_LDLIBS" >>$@ + @printf '# BFS_CPPFLAGS\nCPPFLAGS := %s\n' "$$BFS_CPPFLAGS" >>$@ + @test "${TSAN}" != y || printf '# TSAN\nCPPFLAGS += %s\n' "$$TSAN_CPPFLAGS" >>$@ + @test "${LINT}" != y || printf '# LINT\nCPPFLAGS += %s\n' "$$LINT_CPPFLAGS" >>$@ + @test "${RELEASE}" != y || printf '# RELEASE\nCPPFLAGS += %s\n' "$$RELEASE_CPPFLAGS" >>$@ + @printf '# CPPFLAGS\nCPPFLAGS += %s\n' "$$XCPPFLAGS" >>$@ + @printf '# EXTRA_CPPFLAGS\nCPPFLAGS += %s\n' "$$EXTRA_CPPFLAGS" >>$@ + @printf '# BFS_CFLAGS\nCFLAGS := %s\n' "$$BFS_CFLAGS" >>$@ + @test "${ASAN}" != y || printf '# ASAN\nCFLAGS += %s\n' "$$ASAN_CFLAGS" >>$@ + @test "${LSAN}" != y || printf '# LSAN\nCFLAGS += %s\n' "$$LSAN_CFLAGS" >>$@ + @test "${MSAN}" != y || printf '# MSAN\nCFLAGS += %s\n' "$$MSAN_CFLAGS" >>$@ + @test "${TSAN}" != y || printf '# TSAN\nCFLAGS += %s\n' "$$TSAN_CFLAGS" >>$@ + @test "${UBSAN}" != y || printf '# UBSAN\nCFLAGS += %s\n' "$$UBSAN_CFLAGS" >>$@ + @case "${SAN}" in *y*) printf '# *SAN\nCFLAGS += %s\n' "$$SAN_CFLAGS" >>$@ ;; esac + @test "${GCOV}" != y || printf '# GCOV\nCFLAGS += %s\n' "$$GCOV_CFLAGS" >>$@ + @test "${LINT}" != y || printf '# LINT\nCFLAGS += %s\n' "$$LINT_CFLAGS" >>$@ + @test "${RELEASE}" != y || printf '# RELEASE\nCFLAGS += %s\n' "$$RELEASE_CFLAGS" >>$@ + @printf '# CFLAGS\nCFLAGS += %s\n' "$$XCFLAGS" >>$@ + @printf '# EXTRA_CFLAGS\nCFLAGS += %s\n' "$$EXTRA_CFLAGS" >>$@ + @printf '# LDFLAGS\nLDFLAGS := %s\n' "$$XLDFLAGS" >>$@ + @printf '# EXTRA_LDFLAGS\nLDFLAGS += %s\n' "$$EXTRA_LDFLAGS" >>$@ + @printf '# LDLIBS\nLDLIBS := %s\n' "$$XLDLIBS" >>$@ + @printf '# EXTRA_LDLIBS\nLDLIBS += %s\n' "$$EXTRA_LDLIBS" >>$@ + @printf '# BFS_LDLIBS\nLDLIBS += %s\n' "$$BFS_LDLIBS" >>$@ + @printf 'PKGS :=\n' >>$@ @case "${OS}-${SAN}" in FreeBSD-*y*) printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ ;; esac @cat $@ +.PHONY: ${GEN}/vars.mk # Check for dependency generation support -${GEN}/deps.mk:: - @${MKDIR} ${@D} - @if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ - echo 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}'; \ - fi 2>$@.log | tee $@ +${GEN}/deps.mk: ${GEN}/vars.mk + @+${MAKE} -rs -f config/deps.mk TARGET=$@ + @cat $@ @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ +.PHONY: ${GEN}/deps.mk # Lists file.o: file.c dependencies -${GEN}/objs.mk:: +${GEN}/objs.mk: @${MKDIR} ${@D} @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >$@ +.PHONY: ${GEN}/objs.mk + +# External dependencies +PKG_MKS := \ + ${GEN}/libacl.mk \ + ${GEN}/libcap.mk \ + ${GEN}/libselinux.mk \ + ${GEN}/liburing.mk \ + ${GEN}/oniguruma.mk # Auto-detect dependencies and their build flags -${PKGS}:: - @${MKDIR} ${@D} - @config/pkg.sh ${@:${GEN}/%.mk=%} >$@ 2>$@.log +${GEN}/pkgs.mk: ${PKG_MKS} + @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >$@ + @+${MAKE} -rs -f config/pkgs.mk TARGET=$@ + @grep -v '^include' $@ || : +.PHONY: ${GEN}/pkgs.mk + +# Auto-detect dependencies +${PKG_MKS}: ${GEN}/vars.mk + @+${MAKE} -rs -f config/pkg.mk TARGET=$@ @cat $@ +.PHONY: ${PKG_MKS} # bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.). # Direct users to the new configuration system. diff --git a/config/cc.sh b/config/cc.sh index 7e5c0d5..2b340c0 100755 --- a/config/cc.sh +++ b/config/cc.sh @@ -7,10 +7,4 @@ set -eux -$XCC \ - $BFS_CPPFLAGS $XCPPFLAGS ${EXTRA_CPPFLAGS:-} \ - $BFS_CFLAGS $XCFLAGS ${EXTRA_CFLAGS:-} \ - $XLDFLAGS ${EXTRA_LDFLAGS:-} \ - "$@" \ - $XLDLIBS ${EXTRA_LDLIBS:-} $BFS_LDLIBS \ - -o /dev/null +$CC $CPPFLAGS $CFLAGS $LDFLAGS "$@" $LDLIBS -o /dev/null diff --git a/config/deps.mk b/config/deps.mk new file mode 100644 index 0000000..7d991ab --- /dev/null +++ b/config/deps.mk @@ -0,0 +1,13 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/deps.mk + +.OBJDIR: . + +include config/vars.mk + +default:: + if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ + printf 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}\n'; \ + fi >${TARGET} 2>${TARGET}.log diff --git a/config/pkg.mk b/config/pkg.mk new file mode 100644 index 0000000..9b32b42 --- /dev/null +++ b/config/pkg.mk @@ -0,0 +1,11 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/lib*.mk + +.OBJDIR: . + +include config/vars.mk + +default:: + config/pkg.sh ${TARGET:${GEN}/%.mk=%} >${TARGET} 2>${TARGET}.log diff --git a/config/pkg.sh b/config/pkg.sh index 6335b4b..2ca533e 100755 --- a/config/pkg.sh +++ b/config/pkg.sh @@ -8,37 +8,19 @@ set -eu NAME="${1^^}" -declare -n XUSE="XUSE_$NAME" +declare -n XUSE="USE_$NAME" -if [ "$XUSE" ]; then +if [ "${XUSE:-}" ]; then USE="$XUSE" -elif [[ "$NOLIBS" == *y* ]]; then - USE=n elif config/pkgconf.sh "$1"; then USE=y else USE=n fi -printf '%s := %s\n' "USE_$NAME" "$USE" - if [ "$USE" = y ]; then + printf 'PKGS += %s\n' "$1" printf 'CPPFLAGS += -DBFS_USE_%s=1\n' "$NAME" - - CFLAGS=$(config/pkgconf.sh --cflags "$1") - if [ "$CFLAGS" ]; then - printf 'CFLAGS += %s\n' "$CFLAGS" - fi - - LDFLAGS=$(config/pkgconf.sh --ldflags "$1") - if [ "$LDFLAGS" ]; then - printf 'LDFLAGS += %s\n' "$LDFLAGS" - fi - - LDLIBS=$(config/pkgconf.sh --ldlibs "$1") - if [ "$LDLIBS" ]; then - printf 'LDLIBS += %s\n' "$LDLIBS" - fi else printf 'CPPFLAGS += -DBFS_USE_%s=0\n' "$NAME" fi diff --git a/config/pkgconf.sh b/config/pkgconf.sh index 286c19c..a13b30f 100755 --- a/config/pkgconf.sh +++ b/config/pkgconf.sh @@ -8,29 +8,47 @@ set -eu MODE= -if [[ "$1" == --* ]]; then +if [[ "${1:-}" == --* ]]; then MODE="$1" shift fi +if (($# < 1)); then + exit +fi + +if [[ "$NOLIBS" == *y* ]]; then + exit 1 +fi + if command -v "${PKG_CONFIG:-}" &>/dev/null; then case "$MODE" in + "") + "$PKG_CONFIG" "$@" + ;; --cflags) - "$PKG_CONFIG" --cflags "$@" + OUT=$("$PKG_CONFIG" --cflags "$@") + if [ "$OUT" ]; then + printf 'CFLAGS += %s\n' "$OUT" + fi ;; --ldflags) - "$PKG_CONFIG" --libs-only-L --libs-only-other "$@" + OUT=$("$PKG_CONFIG" --libs-only-L --libs-only-other "$@") + if [ "$OUT" ]; then + printf 'LDFLAGS += %s\n' "$OUT" + fi ;; --ldlibs) - "$PKG_CONFIG" --libs-only-l "$@" - ;; - "") - "$PKG_CONFIG" "$@" + OUT=$("$PKG_CONFIG" --libs-only-l "$@") + if [ "$OUT" ]; then + printf 'LDLIBS := %s ${LDLIBS}\n' "$OUT" + fi ;; esac else - for lib; do - case "$lib" in + LDLIBS="" + for LIB; do + case "$LIB" in libacl) LDLIB=-lacl ;; @@ -47,21 +65,22 @@ else LDLIB=-lonig ;; *) - printf 'error: Unknown package %s\n' "$lib" >&2 + printf 'error: Unknown package %s\n' "$LIB" >&2 exit 1 + ;; esac case "$MODE" in - --ldlibs) - printf ' %s' "$LDLIB" - ;; "") - config/cc.sh "config/$lib.c" "$LDLIB" || exit $? + config/cc.sh "config/$LIB.c" "$LDLIB" || exit $? + ;; + --ldlibs) + LDLIBS="$LDLIBS $LDLIB" ;; esac done - if [ "$MODE" = "--ldlibs" ]; then - printf '\n' + if [ "$MODE" = "--ldlibs" ] && [ "$LDLIBS" ]; then + printf 'LDLIBS :=%s ${LDLIBS}\n' "$LDLIBS" fi fi diff --git a/config/pkgs.mk b/config/pkgs.mk new file mode 100644 index 0000000..54024b2 --- /dev/null +++ b/config/pkgs.mk @@ -0,0 +1,14 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/pkgs.mk + +.OBJDIR: . + +include config/vars.mk +include ${GEN}/pkgs.mk + +default:: + config/pkgconf.sh --cflags ${PKGS} >>${TARGET} 2>>${TARGET}.log + config/pkgconf.sh --ldflags ${PKGS} >>${TARGET} 2>>${TARGET}.log + config/pkgconf.sh --ldlibs ${PKGS} >>${TARGET} 2>>${TARGET}.log diff --git a/config/vars.mk b/config/vars.mk new file mode 100644 index 0000000..a8fae9d --- /dev/null +++ b/config/vars.mk @@ -0,0 +1,21 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile fragment loads and exports variables for config steps + +GEN := ${BUILDDIR}/gen +GEN := ${GEN:./%=%} + +include ${GEN}/vars.mk + +_CC := ${CC} +_CPPFLAGS := ${CPPFLAGS} +_CFLAGS := ${CFLAGS} +_LDFLAGS := ${LDFLAGS} +_LDLIBS := ${LDLIBS} + +export CC=${_CC} +export CPPFLAGS=${_CPPFLAGS} +export CFLAGS=${_CFLAGS} +export LDFLAGS=${_LDFLAGS} +export LDLIBS=${_LDLIBS} -- cgit v1.2.3 From 000ceddf7b607c70297d2dbdee4a800423975fc1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 10 Apr 2024 14:53:59 -0400 Subject: build: Factor out vars.mk generation into a script --- Makefile | 36 ++------------------------ config/vars.sh | 81 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 83 insertions(+), 34 deletions(-) create mode 100755 config/vars.sh diff --git a/Makefile b/Makefile index 0df7ce5..9117fe9 100644 --- a/Makefile +++ b/Makefile @@ -123,7 +123,7 @@ export UBSAN_CFLAGS= -fsanitize=undefined export TSAN_CPPFLAGS= -DBFS_USE_TARGET_CLONES=0 export TSAN_CFLAGS= -fsanitize=thread -SAN := ${ASAN}${LSAN}${MSAN}${TSAN}${UBSAN} +export SAN=${ASAN}${LSAN}${MSAN}${TSAN}${UBSAN} export SAN_CFLAGS= -fno-sanitize-recover=all # MSAN and TSAN both need all code to be instrumented @@ -212,39 +212,7 @@ config: ${MKS} # Saves the configurable variables ${GEN}/vars.mk: @${XMKDIR} ${@D} - @printf 'PREFIX := %s\n' "$$XPREFIX" >$@ - @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ - @printf 'OS := %s\n' "$$XOS" >>$@ - @printf 'ARCH := %s\n' "$$XARCH" >>$@ - @printf 'CC := %s\n' "$$XCC" >>$@ - @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ - @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ - @printf 'RM := %s\n' "$$XRM" >>$@ - @printf '# BFS_CPPFLAGS\nCPPFLAGS := %s\n' "$$BFS_CPPFLAGS" >>$@ - @test "${TSAN}" != y || printf '# TSAN\nCPPFLAGS += %s\n' "$$TSAN_CPPFLAGS" >>$@ - @test "${LINT}" != y || printf '# LINT\nCPPFLAGS += %s\n' "$$LINT_CPPFLAGS" >>$@ - @test "${RELEASE}" != y || printf '# RELEASE\nCPPFLAGS += %s\n' "$$RELEASE_CPPFLAGS" >>$@ - @printf '# CPPFLAGS\nCPPFLAGS += %s\n' "$$XCPPFLAGS" >>$@ - @printf '# EXTRA_CPPFLAGS\nCPPFLAGS += %s\n' "$$EXTRA_CPPFLAGS" >>$@ - @printf '# BFS_CFLAGS\nCFLAGS := %s\n' "$$BFS_CFLAGS" >>$@ - @test "${ASAN}" != y || printf '# ASAN\nCFLAGS += %s\n' "$$ASAN_CFLAGS" >>$@ - @test "${LSAN}" != y || printf '# LSAN\nCFLAGS += %s\n' "$$LSAN_CFLAGS" >>$@ - @test "${MSAN}" != y || printf '# MSAN\nCFLAGS += %s\n' "$$MSAN_CFLAGS" >>$@ - @test "${TSAN}" != y || printf '# TSAN\nCFLAGS += %s\n' "$$TSAN_CFLAGS" >>$@ - @test "${UBSAN}" != y || printf '# UBSAN\nCFLAGS += %s\n' "$$UBSAN_CFLAGS" >>$@ - @case "${SAN}" in *y*) printf '# *SAN\nCFLAGS += %s\n' "$$SAN_CFLAGS" >>$@ ;; esac - @test "${GCOV}" != y || printf '# GCOV\nCFLAGS += %s\n' "$$GCOV_CFLAGS" >>$@ - @test "${LINT}" != y || printf '# LINT\nCFLAGS += %s\n' "$$LINT_CFLAGS" >>$@ - @test "${RELEASE}" != y || printf '# RELEASE\nCFLAGS += %s\n' "$$RELEASE_CFLAGS" >>$@ - @printf '# CFLAGS\nCFLAGS += %s\n' "$$XCFLAGS" >>$@ - @printf '# EXTRA_CFLAGS\nCFLAGS += %s\n' "$$EXTRA_CFLAGS" >>$@ - @printf '# LDFLAGS\nLDFLAGS := %s\n' "$$XLDFLAGS" >>$@ - @printf '# EXTRA_LDFLAGS\nLDFLAGS += %s\n' "$$EXTRA_LDFLAGS" >>$@ - @printf '# LDLIBS\nLDLIBS := %s\n' "$$XLDLIBS" >>$@ - @printf '# EXTRA_LDLIBS\nLDLIBS += %s\n' "$$EXTRA_LDLIBS" >>$@ - @printf '# BFS_LDLIBS\nLDLIBS += %s\n' "$$BFS_LDLIBS" >>$@ - @printf 'PKGS :=\n' >>$@ - @case "${OS}-${SAN}" in FreeBSD-*y*) printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ ;; esac + @config/vars.sh >$@ @cat $@ .PHONY: ${GEN}/vars.mk diff --git a/config/vars.sh b/config/vars.sh new file mode 100755 index 0000000..c0fb124 --- /dev/null +++ b/config/vars.sh @@ -0,0 +1,81 @@ +#!/usr/bin/env bash + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Writes the saved variables to gen/vars.mk + +set -eu + +print() { + NAME="$1" + OP="${2:-:=}" + + if (($# >= 3)); then + printf '# %s\n' "${3#X}" + declare -n VAR="$3" + VALUE="${VAR:-}" + else + # Try X$NAME, $NAME, "" + local -n XVAR="X$NAME" + local -n VAR="$NAME" + VALUE="${XVAR:-${VAR:-}}" + fi + + printf '%s %s %s\n' "$NAME" "$OP" "$VALUE" +} + +cond_flags() { + local -n COND="$1" + + if [[ "${COND:-}" == *y* ]]; then + print "$2" += "${1}_${2}" + fi +} + +print PREFIX +print MANDIR + +print OS +print ARCH + +print CC +print INSTALL +print MKDIR +print RM + +print CPPFLAGS := BFS_CPPFLAGS +cond_flags TSAN CPPFLAGS +cond_flags LINT CPPFLAGS +cond_flags RELEASE CPPFLAGS +print CPPFLAGS += XCPPFLAGS +print CPPFLAGS += EXTRA_CPPFLAGS + +print CFLAGS := BFS_CFLAGS +cond_flags ASAN CFLAGS +cond_flags LSAN CFLAGS +cond_flags MSAN CFLAGS +cond_flags TSAN CFLAGS +cond_flags UBSAN CFLAGS +cond_flags SAN CFLAGS +cond_flags GCOV CFLAGS +cond_flags LINT CFLAGS +cond_flags RELEASE CFLAGS +print CFLAGS += XCFLAGS +print CFLAGS += EXTRA_CFLAGS + +print LDFLAGS := XLDFLAGS +print LDFLAGS += EXTRA_LDFLAGS + +print LDLIBS := XLDLIBS +print LDLIBS += EXTRA_LDLIBS +print LDLIBS += BFS_LDLIBS + +print PKGS + +# Disable ASLR on FreeBSD when sanitizers are enabled +case "$XOS-$SAN" in + FreeBSD-*y*) + printf 'POSTLINK = elfctl -e +noaslr $$@\n' + ;; +esac -- cgit v1.2.3 From 12ce2b2e8547477fe5b544b2be823bc5365507d9 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 11 Apr 2024 14:25:37 -0400 Subject: build: Fix FreeBSD *SAN builds --- config/vars.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/vars.sh b/config/vars.sh index c0fb124..8a781bb 100755 --- a/config/vars.sh +++ b/config/vars.sh @@ -76,6 +76,6 @@ print PKGS # Disable ASLR on FreeBSD when sanitizers are enabled case "$XOS-$SAN" in FreeBSD-*y*) - printf 'POSTLINK = elfctl -e +noaslr $$@\n' + printf 'POSTLINK = elfctl -e +noaslr $@\n' ;; esac -- cgit v1.2.3 From a28b04cfbce8948cb1b216da065a229009a99f01 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 15 Apr 2024 11:54:39 -0400 Subject: build: Remove unneeded USE_* defaults --- Makefile | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 9117fe9..453aeab 100644 --- a/Makefile +++ b/Makefile @@ -138,15 +138,6 @@ export LINT_CFLAGS= -Werror -O2 export RELEASE_CPPFLAGS= -DNDEBUG export RELEASE_CFLAGS= -O3 -flto=auto -# Auto-detected library dependencies. Can be set manually with -# -# $ make config USE_LIBURING=n USE_ONIGURUMA=y -USE_LIBACL ?= -USE_LIBCAP ?= -USE_LIBSELINUX ?= -USE_LIBURING ?= -USE_ONIGURUMA ?= - # Save the new value of these variables, before they potentially get overridden # by `-include ${CONFIG}` below @@ -447,7 +438,7 @@ distcheck: ${DISTCHECKS} DISTCHECK_CONFIG_asan := ASAN=y UBSAN=y DISTCHECK_CONFIG_msan := MSAN=y UBSAN=y CC=clang DISTCHECK_CONFIG_tsan := TSAN=y UBSAN=y CC=clang -DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_LIBDIR=/usr/lib32/pkgconfig USE_LIBURING=n +DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_LIBDIR=/usr/lib32/pkgconfig DISTCHECK_CONFIG_release := RELEASE=y ${DISTCHECKS}:: -- cgit v1.2.3 From eb7dffa166a01240ba50dd23b1f086339b6eb619 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 15 Apr 2024 12:02:21 -0400 Subject: build: Show a nicer error if the user forgets to run make config --- Makefile | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 453aeab..94aba73 100644 --- a/Makefile +++ b/Makefile @@ -244,10 +244,17 @@ ${PKG_MKS}: ${GEN}/vars.mk # bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.). # Direct users to the new configuration system. asan lsan msan tsan ubsan gcov lint release:: - @printf 'error: `make %s` is no longer supported. ' $@ >&2 - @printf 'Use `make config %s=y` instead.\n' $$(echo $@ | tr '[a-z]' '[A-Z]') >&2 + @printf 'error: `%s %s` is no longer supported. ' "${MAKE}" $@ >&2 + @printf 'Use `%s config %s=y` instead.\n' "${MAKE}" $$(echo $@ | tr '[a-z]' '[A-Z]') >&2 @false +# Print an error if `make` is run before `make config` +${CONFIG}:: + @if ! [ -e $@ ]; then \ + printf 'error: You must run `%s config` before `%s`.\n' "${MAKE}" "${MAKE}" >&2; \ + false; \ + fi + ## Build phase (`make`) # The main binary -- cgit v1.2.3 From e21ba7fdf1216f9790292394b254e7d82a46cb3e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 15 Apr 2024 12:07:09 -0400 Subject: build: Make distclean also clean the distcheck dirs --- Makefile | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index 94aba73..4f4bf5c 100644 --- a/Makefile +++ b/Makefile @@ -348,15 +348,6 @@ ${GEN}/VERSION: ${GEN}/NEWVERSION ${OBJ}/src/version.o: ${GEN}/VERSION ${OBJ}/src/version.o: CPPFLAGS := ${CPPFLAGS} -DBFS_VERSION='"${VERSION}"' -# Clean all build products -clean:: - ${RM} -r ${BIN} ${OBJ} - -# Clean everything, including generated files -distclean: clean - ${RM} -r ${GEN} -.PHONY: distclean - ## Test phase (`make check`) # Unit test binaries @@ -482,3 +473,14 @@ check-install:: +${MAKE} uninstall DESTDIR=${BUILDDIR}/pkg ${BIN}/bfs ${BUILDDIR}/pkg -not -type d -print -exit 1 ${RM} -r ${BUILDDIR}/pkg + +## Cleanup (`make clean`) + +# Clean all build products +clean:: + ${RM} -r ${BIN} ${OBJ} + +# Clean everything, including generated files +distclean: clean + ${RM} -r ${GEN} ${DISTCHECKS} +.PHONY: distclean -- cgit v1.2.3 From c82d3dce6e857fad8d0142a99e604e3e35af896d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 15 Apr 2024 15:32:08 -0400 Subject: build: Be quieter by default --- Makefile | 96 +++++++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 22 deletions(-) diff --git a/Makefile b/Makefile index 4f4bf5c..b4c41a1 100644 --- a/Makefile +++ b/Makefile @@ -24,6 +24,30 @@ default: bfs # BSD's ${.ALLSRC} to GNU. .ALLSRC ?= $^ +# GNU and BSD make have incompatible syntax for conditionals, but we can do a +# lot with just recursive variable expansion. Inspired by +# https://github.com/wahern/autoguess +TRUTHY,y := y +TRUTHY,1 := y + +# Normalize ${V} to either "y" or "" +IS_V := ${TRUTHY,${V}} + +# Suppress output unless V=1 +Q, := @ +Q := ${Q,${IS_V}} + +# Show full commands with `make V=1`, otherwise short summaries +MSG = @msg() { \ + MSG="$$1"; \ + shift; \ + test "${IS_V}" || printf '%s\n' "$$MSG"; \ + test "$${1:-}" || return 0; \ + test "${IS_V}" && printf '%s\n' "$$*"; \ + "$$@"; \ + }; \ + msg + # Platform detection OS != uname ARCH != uname -m @@ -195,28 +219,38 @@ MKS := \ ${GEN}/objs.mk \ ${GEN}/pkgs.mk +# cat a file if V=1 +VCAT,y := @cat +VCAT, := @: +VCAT := ${VCAT,${IS_V}} + # The configuration goal itself config: ${MKS} + ${MSG} "[ GEN] ${CONFIG}" @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >${CONFIG} + ${VCAT} ${CONFIG} .PHONY: config # Saves the configurable variables ${GEN}/vars.mk: @${XMKDIR} ${@D} + ${MSG} "[ GEN] $@" @config/vars.sh >$@ - @cat $@ + ${VCAT} $@ .PHONY: ${GEN}/vars.mk # Check for dependency generation support ${GEN}/deps.mk: ${GEN}/vars.mk + ${MSG} "[ GEN] $@" @+${MAKE} -rs -f config/deps.mk TARGET=$@ - @cat $@ + ${VCAT} $@ @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ .PHONY: ${GEN}/deps.mk # Lists file.o: file.c dependencies ${GEN}/objs.mk: - @${MKDIR} ${@D} + @${XMKDIR} ${@D} + ${MSG} "[ GEN] $@" @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >$@ .PHONY: ${GEN}/objs.mk @@ -230,15 +264,22 @@ PKG_MKS := \ # Auto-detect dependencies and their build flags ${GEN}/pkgs.mk: ${PKG_MKS} + ${MSG} "[ GEN] $@" @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >$@ @+${MAKE} -rs -f config/pkgs.mk TARGET=$@ - @grep -v '^include' $@ || : + ${VCAT} $@ .PHONY: ${GEN}/pkgs.mk # Auto-detect dependencies ${PKG_MKS}: ${GEN}/vars.mk @+${MAKE} -rs -f config/pkg.mk TARGET=$@ - @cat $@ + @if [ "${IS_V}" ]; then \ + cat $@; \ + elif grep -q PKGS $@; then \ + printf '[ GEN] %-18s [y]\n' $@; \ + else \ + printf '[ GEN] %-18s [n]\n' $@; \ + fi .PHONY: ${PKG_MKS} # bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.). @@ -311,7 +352,7 @@ ${BIN}/bfs: ${LIBBFS} ${OBJ}/src/main.o ${BINS}: @${MKDIR} ${@D} - +${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ + +${MSG} "[ LD] $@" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ ${POSTLINK} # All object files @@ -330,11 +371,14 @@ OBJS := \ ${OBJ}/tests/xtouch.o \ ${LIBBFS} +# Get the .c file for a .o file +CSRC = ${@:${OBJ}/%.o=%.c} + # Depend on ${CONFIG} to make sure `make config` runs first, and to rebuild when # the configuration changes ${OBJS}: ${CONFIG} @${MKDIR} ${@D} - ${CC} ${ALL_CFLAGS} -c ${@:${OBJ}/%.o=%.c} -o $@ + ${MSG} "[ CC] ${CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ # Save the version number to this file, but only update VERSION if it changes ${GEN}/NEWVERSION:: @@ -370,7 +414,7 @@ check: unit-tests integration-tests # Run the unit tests unit-tests: ${UTEST_BINS} - ${BIN}/tests/units + ${MSG} "[TEST] tests/units" ${BIN}/tests/units .PHONY: unit-tests ${BIN}/tests/units: \ @@ -393,15 +437,18 @@ INTEGRATION_TESTS := ${INTEGRATIONS:%=check-%} # Check just `bfs` check-default: ${BIN}/bfs ${ITEST_BINS} - +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs" ${TEST_FLAGS} + +${MSG} "[TEST] bfs" \ + ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs" ${TEST_FLAGS} # Check the different search strategies check-dfs check-ids check-eds: ${BIN}/bfs ${ITEST_BINS} - +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -S ${@:check-%=%}" ${TEST_FLAGS} + +${MSG} "[TEST] bfs -S ${@:check-%=%}" \ + ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -S ${@:check-%=%}" ${TEST_FLAGS} # Check various flags check-j1 check-j2 check-j3 check-s: ${BIN}/bfs ${ITEST_BINS} - +./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -${@:check-%=%}" ${TEST_FLAGS} + +${MSG} "[TEST] bfs -${@:check-%=%}" \ + ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -${@:check-%=%}" ${TEST_FLAGS} # Run the integration tests integration-tests: ${INTEGRATION_TESTS} @@ -441,7 +488,7 @@ DISTCHECK_CONFIG_release := RELEASE=y ${DISTCHECKS}:: +${MAKE} -rs BUILDDIR=${BUILDDIR}/$@ config ${DISTCHECK_CONFIG_${@:distcheck-%=%}} - +${MAKE} -s BUILDDIR=${BUILDDIR}/$@ check TEST_FLAGS="--sudo --verbose=skipped" + +${MAKE} -s BUILDDIR=${BUILDDIR}/$@ check TEST_FLAGS="--sudo --verbose=skipped posix/basic" ## Packaging (`make install`) @@ -449,16 +496,21 @@ DEST_PREFIX := ${DESTDIR}${PREFIX} DEST_MANDIR := ${DESTDIR}${MANDIR} install:: - ${MKDIR} ${DEST_PREFIX}/bin - ${INSTALL} -m755 ${BIN}/bfs ${DEST_PREFIX}/bin/bfs - ${MKDIR} ${DEST_MANDIR}/man1 - ${INSTALL} -m644 docs/bfs.1 ${DEST_MANDIR}/man1/bfs.1 - ${MKDIR} ${DEST_PREFIX}/share/bash-completion/completions - ${INSTALL} -m644 completions/bfs.bash ${DEST_PREFIX}/share/bash-completion/completions/bfs - ${MKDIR} ${DEST_PREFIX}/share/zsh/site-functions - ${INSTALL} -m644 completions/bfs.zsh ${DEST_PREFIX}/share/zsh/site-functions/_bfs - ${MKDIR} ${DEST_PREFIX}/share/fish/vendor_completions.d - ${INSTALL} -m644 completions/bfs.fish ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish + ${Q}${MKDIR} ${DEST_PREFIX}/bin + ${MSG} "[INSTALL] bin/bfs" \ + ${INSTALL} -m755 ${BIN}/bfs ${DEST_PREFIX}/bin/bfs + ${Q}${MKDIR} ${DEST_MANDIR}/man1 + ${MSG} "[INSTALL] man/man1/bfs.1" \ + ${INSTALL} -m644 docs/bfs.1 ${DEST_MANDIR}/man1/bfs.1 + ${Q}${MKDIR} ${DEST_PREFIX}/share/bash-completion/completions + ${MSG} "[INSTALL] completions/bfs.bash" \ + ${INSTALL} -m644 completions/bfs.bash ${DEST_PREFIX}/share/bash-completion/completions/bfs + ${Q}${MKDIR} ${DEST_PREFIX}/share/zsh/site-functions + ${MSG} "[INSTALL] completions/bfs.zsh" \ + ${INSTALL} -m644 completions/bfs.zsh ${DEST_PREFIX}/share/zsh/site-functions/_bfs + ${Q}${MKDIR} ${DEST_PREFIX}/share/fish/vendor_completions.d + ${MSG} "[INSTALL] completions/bfs.fish" \ + ${INSTALL} -m644 completions/bfs.fish ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish uninstall:: ${RM} ${DEST_PREFIX}/share/bash-completion/completions/bfs -- cgit v1.2.3 From 3268b64dfa29ba3919c068b38369983638536616 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 15 Apr 2024 15:53:08 -0400 Subject: distcheck: Run all the checks Oops. --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index b4c41a1..006e93b 100644 --- a/Makefile +++ b/Makefile @@ -488,7 +488,7 @@ DISTCHECK_CONFIG_release := RELEASE=y ${DISTCHECKS}:: +${MAKE} -rs BUILDDIR=${BUILDDIR}/$@ config ${DISTCHECK_CONFIG_${@:distcheck-%=%}} - +${MAKE} -s BUILDDIR=${BUILDDIR}/$@ check TEST_FLAGS="--sudo --verbose=skipped posix/basic" + +${MAKE} -s BUILDDIR=${BUILDDIR}/$@ check TEST_FLAGS="--sudo --verbose=skipped" ## Packaging (`make install`) -- cgit v1.2.3 From 1c1efd5c5ebc94be3b45958bfacb5ed9db99f8c7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 Apr 2024 11:56:23 -0400 Subject: build: Center [ CC ] / [ LD ] --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 006e93b..7cddc51 100644 --- a/Makefile +++ b/Makefile @@ -352,7 +352,7 @@ ${BIN}/bfs: ${LIBBFS} ${OBJ}/src/main.o ${BINS}: @${MKDIR} ${@D} - +${MSG} "[ LD] $@" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ + +${MSG} "[ LD ] $@" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ ${POSTLINK} # All object files @@ -378,7 +378,7 @@ CSRC = ${@:${OBJ}/%.o=%.c} # the configuration changes ${OBJS}: ${CONFIG} @${MKDIR} ${@D} - ${MSG} "[ CC] ${CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ + ${MSG} "[ CC ] ${CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ # Save the version number to this file, but only update VERSION if it changes ${GEN}/NEWVERSION:: -- cgit v1.2.3 From 98c539eeda8f9adfd22a3b2b6ece4fe1ca06b3b4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 Apr 2024 11:52:44 -0400 Subject: tests: Quiet diff On Solaris/Illumos, `diff` prints "No differences encountered" if the files are the same. Guard it with `cmp -s` so we get no output for passing tests. --- tests/run.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/run.sh b/tests/run.sh index ab1ed6d..ad9c0be 100644 --- a/tests/run.sh +++ b/tests/run.sh @@ -430,7 +430,7 @@ make_xattrs() { EX_DIFF=20 # Detect colored diff support -if diff --color /dev/null /dev/null 2>/dev/null; then +if diff --color /dev/null /dev/null &>/dev/null; then DIFF="diff --color" else DIFF="diff" @@ -447,7 +447,7 @@ diff_output() { if ((UPDATE)); then cp "$OUT" "$GOLD" - else + elif ! cmp -s "$GOLD" "$OUT"; then $DIFF -u "$GOLD" "$OUT" >&$DUPERR fi } -- cgit v1.2.3 From c4c063e9844f2bd2271b2e3391f59f872c66f69a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 16 Apr 2024 18:43:49 -0400 Subject: build: Refactor configuration We now use a recursive make invocation to do the work of `make config`. The new implementation is also compatible with GNU make 3.81 found on macOS. --- .github/workflows/ci.yml | 5 +- Makefile | 369 +++++------------------------------------------ config/cc.sh | 2 +- config/config.mk | 77 ++++++++++ config/deps.mk | 15 +- config/exports.mk | 19 +++ config/flags.mk | 119 +++++++++++++++ config/pkg.mk | 17 ++- config/pkgconf.sh | 12 +- config/pkgs.mk | 17 ++- config/prelude.mk | 164 +++++++++++++++++++++ config/vars.mk | 21 --- config/vars.sh | 81 ----------- 13 files changed, 454 insertions(+), 464 deletions(-) create mode 100644 config/config.mk create mode 100644 config/exports.mk create mode 100644 config/flags.mk create mode 100644 config/prelude.mk delete mode 100644 config/vars.mk delete mode 100755 config/vars.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 78aa196..3ad924f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -53,13 +53,12 @@ jobs: run: | brew install \ bash \ - expect \ - make + expect - name: Run tests run: | jobs=$(sysctl -n hw.ncpu) - gmake -j$jobs distcheck + make -j$jobs distcheck freebsd: name: FreeBSD diff --git a/Makefile b/Makefile index 7cddc51..e9efef3 100644 --- a/Makefile +++ b/Makefile @@ -2,285 +2,27 @@ # SPDX-License-Identifier: 0BSD # This Makefile implements the configuration and build steps for bfs. It is -# portable to both GNU make and the BSD make implementations (how that works -# is documented below). To build bfs, run +# portable to both GNU make and most BSD make implementations. To build bfs, +# run # # $ make config # $ make +# Utilities and GNU/BSD portability +include config/prelude.mk + # The default build target default: bfs .PHONY: default -# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to -.OBJDIR: . - -# We don't use any suffix rules -.SUFFIXES: - -# GNU make has $^ for the full list of targets, while BSD make has $> and the -# long-form ${.ALLSRC}. We could write $^ $> to get them both, but that would -# break if one of them implemented support for the other. So instead, bring -# BSD's ${.ALLSRC} to GNU. -.ALLSRC ?= $^ - -# GNU and BSD make have incompatible syntax for conditionals, but we can do a -# lot with just recursive variable expansion. Inspired by -# https://github.com/wahern/autoguess -TRUTHY,y := y -TRUTHY,1 := y - -# Normalize ${V} to either "y" or "" -IS_V := ${TRUTHY,${V}} - -# Suppress output unless V=1 -Q, := @ -Q := ${Q,${IS_V}} - -# Show full commands with `make V=1`, otherwise short summaries -MSG = @msg() { \ - MSG="$$1"; \ - shift; \ - test "${IS_V}" || printf '%s\n' "$$MSG"; \ - test "$${1:-}" || return 0; \ - test "${IS_V}" && printf '%s\n' "$$*"; \ - "$$@"; \ - }; \ - msg - -# Platform detection -OS != uname -ARCH != uname -m - -# For out-of-tree builds, e.g. -# -# $ make config BUILDDIR=/path/to/build/dir -# $ make BUILDDIR=/path/to/build/dir -BUILDDIR ?= . - -# Shorthand for build subdirectories -BIN := ${BUILDDIR}/bin -GEN := ${BUILDDIR}/gen -OBJ := ${BUILDDIR}/obj - -# GNU make strips a leading ./ from target names, so do the same for BSD make -BIN := ${BIN:./%=%} -GEN := ${GEN:./%=%} -OBJ := ${OBJ:./%=%} - -# Installation paths -DESTDIR ?= -PREFIX ?= /usr -MANDIR ?= ${PREFIX}/share/man - -# Configurable executables; can be overridden with -# -# $ make config CC=clang -CC ?= cc -INSTALL ?= install -MKDIR ?= mkdir -p -PKG_CONFIG ?= pkg-config -RM ?= rm -f - -# Configurable flags - -CPPFLAGS ?= -CFLAGS ?= \ - -g \ - -Wall \ - -Wformat=2 \ - -Werror=implicit \ - -Wimplicit-fallthrough \ - -Wmissing-declarations \ - -Wshadow \ - -Wsign-compare \ - -Wstrict-prototypes -LDFLAGS ?= -LDLIBS ?= - -EXTRA_CPPFLAGS ?= -EXTRA_CFLAGS ?= -EXTRA_LDFLAGS ?= -EXTRA_LDLIBS ?= - -GIT_VERSION != test -d .git && command -v git >/dev/null 2>&1 && git describe --always --dirty || echo 3.1.3 -VERSION ?= ${GIT_VERSION} - -# Immutable flags -export BFS_CPPFLAGS= \ - -D__EXTENSIONS__ \ - -D_ATFILE_SOURCE \ - -D_BSD_SOURCE \ - -D_DARWIN_C_SOURCE \ - -D_DEFAULT_SOURCE \ - -D_GNU_SOURCE \ - -D_LARGEFILE64_SOURCE \ - -D_POSIX_PTHREAD_SEMANTICS \ - -D_FILE_OFFSET_BITS=64 \ - -D_TIME_BITS=64 -export BFS_CFLAGS= -std=c17 -pthread - -# Platform-specific system libraries -LDLIBS,DragonFly := -lposix1e -LDLIBS,Linux := -lrt -LDLIBS,NetBSD := -lutil -LDLIBS,SunOS := -lsocket -lnsl -_BFS_LDLIBS := ${LDLIBS,${OS}} -export BFS_LDLIBS=${_BFS_LDLIBS} - -# Build profiles -ASAN ?= n -LSAN ?= n -MSAN ?= n -TSAN ?= n -UBSAN ?= n -GCOV ?= n -LINT ?= n -RELEASE ?= n - -export ASAN_CFLAGS= -fsanitize=address -export LSAN_CFLAGS= -fsanitize=leak -export MSAN_CFLAGS= -fsanitize=memory -fsanitize-memory-track-origins -export UBSAN_CFLAGS= -fsanitize=undefined - -# https://github.com/google/sanitizers/issues/342 -export TSAN_CPPFLAGS= -DBFS_USE_TARGET_CLONES=0 -export TSAN_CFLAGS= -fsanitize=thread - -export SAN=${ASAN}${LSAN}${MSAN}${TSAN}${UBSAN} -export SAN_CFLAGS= -fno-sanitize-recover=all - -# MSAN and TSAN both need all code to be instrumented -export NOLIBS= ${MSAN}${TSAN} - -# gcov only intercepts fork()/exec() with -std=gnu* -export GCOV_CFLAGS= --coverage -std=gnu17 - -export LINT_CPPFLAGS= -D_FORTIFY_SOURCE=3 -DBFS_LINT -export LINT_CFLAGS= -Werror -O2 - -export RELEASE_CPPFLAGS= -DNDEBUG -export RELEASE_CFLAGS= -O3 -flto=auto - -# Save the new value of these variables, before they potentially get overridden -# by `-include ${CONFIG}` below - -_XPREFIX := ${PREFIX} -_XMANDIR := ${MANDIR} - -_XOS := ${OS} -_XARCH := ${ARCH} - -_XCC := ${CC} -_XINSTALL := ${INSTALL} -_XMKDIR := ${MKDIR} -_XRM := ${RM} - -_XCPPFLAGS := ${CPPFLAGS} -_XCFLAGS := ${CFLAGS} -_XLDFLAGS := ${LDFLAGS} -_XLDLIBS := ${LDLIBS} - -# GNU make supports `export VAR`, but BSD make requires `export VAR=value`. -# Sadly, GNU make gives a recursion error on `export VAR=${VAR}`. - -_BUILDDIR := ${BUILDDIR} -_PKG_CONFIG := ${PKG_CONFIG} - -export BUILDDIR=${_BUILDDIR} -export PKG_CONFIG=${_PKG_CONFIG} - -export XPREFIX=${_XPREFIX} -export XMANDIR=${_XMANDIR} - -export XOS=${_XOS} -export XARCH=${_XARCH} - -export XCC=${_XCC} -export XINSTALL=${_XINSTALL} -export XMKDIR=${_XMKDIR} -export XRM=${_XRM} - -export XCPPFLAGS=${_XCPPFLAGS} -export XCFLAGS=${_XCFLAGS} -export XLDFLAGS=${_XLDFLAGS} -export XLDLIBS=${_XLDLIBS} - -# The configuration file generated by `make config` -CONFIG := ${GEN}/config.mk +# Include the generated build config, if it exists -include ${CONFIG} ## Configuration phase (`make config`) -# Makefile fragments generated by `make config` -MKS := \ - ${GEN}/vars.mk \ - ${GEN}/deps.mk \ - ${GEN}/objs.mk \ - ${GEN}/pkgs.mk - -# cat a file if V=1 -VCAT,y := @cat -VCAT, := @: -VCAT := ${VCAT,${IS_V}} - # The configuration goal itself -config: ${MKS} - ${MSG} "[ GEN] ${CONFIG}" - @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >${CONFIG} - ${VCAT} ${CONFIG} -.PHONY: config - -# Saves the configurable variables -${GEN}/vars.mk: - @${XMKDIR} ${@D} - ${MSG} "[ GEN] $@" - @config/vars.sh >$@ - ${VCAT} $@ -.PHONY: ${GEN}/vars.mk - -# Check for dependency generation support -${GEN}/deps.mk: ${GEN}/vars.mk - ${MSG} "[ GEN] $@" - @+${MAKE} -rs -f config/deps.mk TARGET=$@ - ${VCAT} $@ - @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ -.PHONY: ${GEN}/deps.mk - -# Lists file.o: file.c dependencies -${GEN}/objs.mk: - @${XMKDIR} ${@D} - ${MSG} "[ GEN] $@" - @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >$@ -.PHONY: ${GEN}/objs.mk - -# External dependencies -PKG_MKS := \ - ${GEN}/libacl.mk \ - ${GEN}/libcap.mk \ - ${GEN}/libselinux.mk \ - ${GEN}/liburing.mk \ - ${GEN}/oniguruma.mk - -# Auto-detect dependencies and their build flags -${GEN}/pkgs.mk: ${PKG_MKS} - ${MSG} "[ GEN] $@" - @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >$@ - @+${MAKE} -rs -f config/pkgs.mk TARGET=$@ - ${VCAT} $@ -.PHONY: ${GEN}/pkgs.mk - -# Auto-detect dependencies -${PKG_MKS}: ${GEN}/vars.mk - @+${MAKE} -rs -f config/pkg.mk TARGET=$@ - @if [ "${IS_V}" ]; then \ - cat $@; \ - elif grep -q PKGS $@; then \ - printf '[ GEN] %-18s [y]\n' $@; \ - else \ - printf '[ GEN] %-18s [n]\n' $@; \ - fi -.PHONY: ${PKG_MKS} +config:: + @+${MAKE} -sf config/config.mk # bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.). # Direct users to the new configuration system. @@ -313,36 +55,6 @@ BINS := \ all: ${BINS} .PHONY: all -# All object files except the entry point -LIBBFS := \ - ${OBJ}/src/alloc.o \ - ${OBJ}/src/bar.o \ - ${OBJ}/src/bfstd.o \ - ${OBJ}/src/bftw.o \ - ${OBJ}/src/color.o \ - ${OBJ}/src/ctx.o \ - ${OBJ}/src/diag.o \ - ${OBJ}/src/dir.o \ - ${OBJ}/src/dstring.o \ - ${OBJ}/src/eval.o \ - ${OBJ}/src/exec.o \ - ${OBJ}/src/expr.o \ - ${OBJ}/src/fsade.o \ - ${OBJ}/src/ioq.o \ - ${OBJ}/src/mtab.o \ - ${OBJ}/src/opt.o \ - ${OBJ}/src/parse.o \ - ${OBJ}/src/printf.o \ - ${OBJ}/src/pwcache.o \ - ${OBJ}/src/stat.o \ - ${OBJ}/src/thread.o \ - ${OBJ}/src/trie.o \ - ${OBJ}/src/typo.o \ - ${OBJ}/src/version.o \ - ${OBJ}/src/xregex.o \ - ${OBJ}/src/xspawn.o \ - ${OBJ}/src/xtime.o - # Group relevant flags together ALL_CFLAGS = ${CPPFLAGS} ${CFLAGS} ${DEPFLAGS} ALL_LDFLAGS = ${CFLAGS} ${LDFLAGS} @@ -355,22 +67,6 @@ ${BINS}: +${MSG} "[ LD ] $@" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ ${POSTLINK} -# All object files -OBJS := \ - ${OBJ}/src/main.o \ - ${OBJ}/tests/alloc.o \ - ${OBJ}/tests/bfstd.o \ - ${OBJ}/tests/bit.o \ - ${OBJ}/tests/ioq.o \ - ${OBJ}/tests/main.o \ - ${OBJ}/tests/mksock.o \ - ${OBJ}/tests/trie.o \ - ${OBJ}/tests/xspawn.o \ - ${OBJ}/tests/xspawnee.o \ - ${OBJ}/tests/xtime.o \ - ${OBJ}/tests/xtouch.o \ - ${LIBBFS} - # Get the .c file for a .o file CSRC = ${@:${OBJ}/%.o=%.c} @@ -383,14 +79,20 @@ ${OBJS}: ${CONFIG} # Save the version number to this file, but only update VERSION if it changes ${GEN}/NEWVERSION:: @${MKDIR} ${@D} - @printf '%s\n' '${VERSION}' >$@ + @if [ "$$VERSION" ]; then \ + printf '%s\n' "$$VERSION"; \ + elif test -d .git && command -v git >/dev/null 2>&1; then \ + git describe --always --dirty; \ + else \ + echo "3.1.3"; \ + fi >$@ ${GEN}/VERSION: ${GEN}/NEWVERSION @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@ # Rebuild version.c whenever the version number changes ${OBJ}/src/version.o: ${GEN}/VERSION -${OBJ}/src/version.o: CPPFLAGS := ${CPPFLAGS} -DBFS_VERSION='"${VERSION}"' +${OBJ}/src/version.o: CPPFLAGS := ${CPPFLAGS} -DBFS_VERSION='"$$(cat ${GEN}/VERSION)"' ## Test phase (`make check`) @@ -418,14 +120,7 @@ unit-tests: ${UTEST_BINS} .PHONY: unit-tests ${BIN}/tests/units: \ - ${OBJ}/tests/alloc.o \ - ${OBJ}/tests/bfstd.o \ - ${OBJ}/tests/bit.o \ - ${OBJ}/tests/ioq.o \ - ${OBJ}/tests/main.o \ - ${OBJ}/tests/trie.o \ - ${OBJ}/tests/xspawn.o \ - ${OBJ}/tests/xtime.o \ + ${UNIT_OBJS} \ ${LIBBFS} ${BIN}/tests/xspawnee: \ @@ -463,20 +158,20 @@ ${BIN}/tests/xtouch: \ ${LIBBFS} # `make distcheck` configurations -DISTCHECKS := distcheck-asan distcheck-tsan distcheck-release - -# Don't use msan on macOS -IS_DARWIN,Darwin := y -IS_DARWIN := ${IS_DARWIN,${OS}} -DISTCHECK_MSAN, := distcheck-msan -DISTCHECKS += ${DISTCHECK_MSAN,${IS_DARWIN}} - -# Only add a 32-bit build on 64-bit Linux -DISTCHECK_M32,Linux,x86_64 := distcheck-m32 -DISTCHECKS += ${DISTCHECK_M32,${OS},${ARCH}} +DISTCHECKS := \ + distcheck-asan \ + distcheck-msan \ + distcheck-tsan \ + distcheck-m32 \ + distcheck-release # Test multiple configurations -distcheck: ${DISTCHECKS} +distcheck: + @+${MAKE} -s distcheck-asan + @+test "$$(uname)" = Darwin || ${MAKE} -s distcheck-msan + @+${MAKE} -s distcheck-tsan + @+test "$$(uname)-$$(uname -m)" != Linux-x86_64 || ${MAKE} -s distcheck-m32 + @+${MAKE} -s distcheck-release .PHONY: distcheck # Per-distcheck configuration @@ -530,9 +225,11 @@ check-install:: # Clean all build products clean:: - ${RM} -r ${BIN} ${OBJ} + ${MSG} "[ RM ] bin obj" \ + ${RM} -r ${BIN} ${OBJ} # Clean everything, including generated files distclean: clean - ${RM} -r ${GEN} ${DISTCHECKS} + ${MSG} "[ RM ] gen" \ + ${RM} -r ${GEN} ${DISTCHECKS} .PHONY: distclean diff --git a/config/cc.sh b/config/cc.sh index 2b340c0..abce508 100755 --- a/config/cc.sh +++ b/config/cc.sh @@ -7,4 +7,4 @@ set -eux -$CC $CPPFLAGS $CFLAGS $LDFLAGS "$@" $LDLIBS -o /dev/null +$XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o /dev/null diff --git a/config/config.mk b/config/config.mk new file mode 100644 index 0000000..c8d7f19 --- /dev/null +++ b/config/config.mk @@ -0,0 +1,77 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile fragment that implements `make config` + +include config/prelude.mk +include config/exports.mk + +# Makefile fragments generated by `make config` +MKS := \ + ${GEN}/vars.mk \ + ${GEN}/flags.mk \ + ${GEN}/deps.mk \ + ${GEN}/objs.mk \ + ${GEN}/pkgs.mk + +# The main configuration file, which includes the others +${CONFIG}: ${MKS} + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@ + ${VCAT} ${CONFIG} +.PHONY: ${CONFIG} + +# Saves the configurable variables +${GEN}/vars.mk:: + @${MKDIR} ${@D} + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@ + @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ + @printf 'OS := %s\n' "$${OS:-$$(uname)}" >>$@ + @printf 'ARCH := %s\n' "$${ARCH:-$$(uname -m)}" >>$@ + @printf 'CC := %s\n' "$$XCC" >>$@ + @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ + @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ + @printf 'RM := %s\n' "$$XRM" >>$@ + @printf 'PKGS :=\n' >>$@ + ${VCAT} $@ + +# Sets the build flags. This depends on vars.mk and uses a recursive make so +# that the default flags can depend on variables like ${OS}. +${GEN}/flags.mk: ${GEN}/vars.mk + @+${MAKE} -sf config/flags.mk +.PHONY: ${GEN}/flags.mk + +# Check for dependency generation support +${GEN}/deps.mk: ${GEN}/flags.mk + @+${MAKE} -sf config/deps.mk +.PHONY: ${GEN}/deps.mk + +# Lists file.o: file.c dependencies +${GEN}/objs.mk:: + @${MKDIR} ${@D} + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >>$@ + +# External dependencies +PKG_MKS := \ + ${GEN}/libacl.mk \ + ${GEN}/libcap.mk \ + ${GEN}/libselinux.mk \ + ${GEN}/liburing.mk \ + ${GEN}/oniguruma.mk + +# Auto-detect dependencies and their build flags +${GEN}/pkgs.mk: ${PKG_MKS} + @printf '# %s\n' "$@" >$@ + @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@ + @+${MAKE} -sf config/pkgs.mk +.PHONY: ${GEN}/pkgs.mk + +# Auto-detect dependencies +${PKG_MKS}: ${GEN}/flags.mk + @+${MAKE} -sf config/pkg.mk TARGET=$@ +.PHONY: ${PKG_MKS} diff --git a/config/deps.mk b/config/deps.mk index 7d991ab..52ee0e1 100644 --- a/config/deps.mk +++ b/config/deps.mk @@ -3,11 +3,16 @@ # Makefile that generates gen/deps.mk -.OBJDIR: . +include config/prelude.mk +include ${GEN}/vars.mk +include ${GEN}/flags.mk +include config/exports.mk -include config/vars.mk - -default:: +${GEN}/deps.mk:: + ${MSG} "[ GEN] $@" + printf '# %s\n' "$@" >$@ if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ printf 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}\n'; \ - fi >${TARGET} 2>${TARGET}.log + fi >>$@ 2>$@.log + ${VCAT} $@ + @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ diff --git a/config/exports.mk b/config/exports.mk new file mode 100644 index 0000000..9128568 --- /dev/null +++ b/config/exports.mk @@ -0,0 +1,19 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile fragment that exports variables used by configuration scripts + +export XPREFIX=${PREFIX} +export XMANDIR=${MANDIR} + +export XCC=${CC} +export XINSTALL=${INSTALL} +export XMKDIR=${MKDIR} +export XRM=${RM} + +export XCPPFLAGS=${CPPFLAGS} +export XCFLAGS=${CFLAGS} +export XLDFLAGS=${LDFLAGS} +export XLDLIBS=${LDLIBS} + +export XNOLIBS=${NOLIBS} diff --git a/config/flags.mk b/config/flags.mk new file mode 100644 index 0000000..e62e26e --- /dev/null +++ b/config/flags.mk @@ -0,0 +1,119 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/flags.mk + +include config/prelude.mk +include ${GEN}/vars.mk + +# Immutable flags +export BFS_CPPFLAGS= \ + -D__EXTENSIONS__ \ + -D_ATFILE_SOURCE \ + -D_BSD_SOURCE \ + -D_DARWIN_C_SOURCE \ + -D_DEFAULT_SOURCE \ + -D_GNU_SOURCE \ + -D_LARGEFILE64_SOURCE \ + -D_POSIX_PTHREAD_SEMANTICS \ + -D_FILE_OFFSET_BITS=64 \ + -D_TIME_BITS=64 +export BFS_CFLAGS= -std=c17 -pthread + +# Platform-specific system libraries +LDLIBS,DragonFly := -lposix1e +LDLIBS,Linux := -lrt +LDLIBS,NetBSD := -lutil +LDLIBS,SunOS := -lsocket -lnsl +export BFS_LDLIBS=${LDLIBS,${OS}} + +# Make sure we pick up any default flags from e.g. sys.mk +export XCPPFLAGS=${CPPFLAGS} +export XCFLAGS=${CFLAGS} +export XLDFLAGS=${LDFLAGS} +export XLDLIBS=${LDLIBS} + +# Build profiles +_ASAN := ${TRUTHY,${ASAN}} +_LSAN := ${TRUTHY,${LSAN}} +_MSAN := ${TRUTHY,${MSAN}} +_TSAN := ${TRUTHY,${TSAN}} +_UBSAN := ${TRUTHY,${UBSAN}} +_GCOV := ${TRUTHY,${GCOV}} +_LINT := ${TRUTHY,${LINT}} +_RELEASE := ${TRUTHY,${RELEASE}} + +# https://github.com/google/sanitizers/issues/342 +TSAN_CPPFLAGS,y := -DBFS_USE_TARGET_CLONES=0 +export TSAN_CPPFLAGS=${TSAN_CPPFLAGS,${_TSAN}} + +ASAN_CFLAGS,y := -fsanitize=address +LSAN_CFLAGS,y := -fsanitize=leak +MSAN_CFLAGS,y := -fsanitize=memory -fsanitize-memory-track-origins +TSAN_CFLAGS,y := -fsanitize=thread +UBSAN_CFLAGS.y := -fsanitize=undefined + +export ASAN_CFLAGS=${ASAN_CFLAGS,${_ASAN}} +export LSAN_CFLAGS=${LSAN_CFLAGS,${_LSAN}} +export MSAN_CFLAGS=${MSAN_CFLAGS,${_MSAN}} +export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}} +export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}} + +SAN_CFLAGS,y := -fno-sanitize-recover=all +SAN := ${NOT,${NOR,${_ASAN},${_LSAN},${_MSAN},${_TSAN},${_UBSAN}}} +export SAN_CFLAGS=${SAN_CFLAGS,${SAN}} + +# MSAN and TSAN both need all code to be instrumented +NOLIBS ?= ${NOT,${NOR,${_MSAN},${_TSAN}}} +export XNOLIBS=${NOLIBS} + +# gcov only intercepts fork()/exec() with -std=gnu* +GCOV_CFLAGS,y := -std=gnu17 --coverage +export GCOV_CFLAGS=${GCOV_CFLAGS,${_GCOV}} + +LINT_CPPFLAGS,y := -D_FORTIFY_SOURCE=3 -DBFS_LINT +LINT_CFLAGS,y := -Werror -O2 + +export LINT_CPPFLAGS=${LINT_CPPFLAGS,${_LINT}} +export LINT_CFLAGS=${LINT_CFLAGS,${_LINT}} + +RELEASE_CPPFLAGS,y := -DNDEBUG +RELEASE_CFLAGS,y := -O3 -flto=auto + +export RELEASE_CPPFLAGS=${RELEASE_CPPFLAGS,${_RELEASE}} +export RELEASE_CFLAGS=${RELEASE_CFLAGS,${_RELEASE}} + +# Set a variable +SETVAR = printf '%s := %s\n' >>$@ + +# Append to a variable, if non-empty +APPEND = append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append + +${GEN}/flags.mk:: + ${MSG} "[ GEN] $@" + printf '# %s\n' "$@" >$@ + ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS" + ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS" + ${APPEND} CPPFLAGS "$$LINT_CPPFLAGS" + ${APPEND} CPPFLAGS "$$RELEASE_CPPFLAGS" + ${APPEND} CPPFLAGS "$$XCPPFLAGS" + ${APPEND} CPPFLAGS "$$EXTRA_CPPFLAGS" + ${SETVAR} CFLAGS "$$BFS_CFLAGS" + ${APPEND} CFLAGS "$$ASAN_CFLAGS" + ${APPEND} CFLAGS "$$LSAN_CFLAGS" + ${APPEND} CFLAGS "$$MSAN_CFLAGS" + ${APPEND} CFLAGS "$$TSAN_CFLAGS" + ${APPEND} CFLAGS "$$UBSAN_CFLAGS" + ${APPEND} CFLAGS "$$SAN_CFLAGS" + ${APPEND} CFLAGS "$$GCOV_CFLAGS" + ${APPEND} CFLAGS "$$LINT_CFLAGS" + ${APPEND} CFLAGS "$$RELEASE_CFLAGS" + ${APPEND} CFLAGS "$$XCFLAGS" + ${APPEND} CFLAGS "$$EXTRA_CFLAGS" + ${SETVAR} LDFLAGS "$$XLDFLAGS" + ${SETVAR} LDLIBS "$$XLDLIBS" + ${APPEND} LDLIBS "$$EXTRA_LDLIBS" + ${APPEND} LDLIBS "$$BFS_LDLIBS" + ${SETVAR} NOLIBS "$$XNOLIBS" + test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ + ${VCAT} $@ diff --git a/config/pkg.mk b/config/pkg.mk index 9b32b42..2086555 100644 --- a/config/pkg.mk +++ b/config/pkg.mk @@ -3,9 +3,18 @@ # Makefile that generates gen/lib*.mk -.OBJDIR: . - -include config/vars.mk +include config/prelude.mk +include ${GEN}/vars.mk +include ${GEN}/flags.mk +include config/exports.mk default:: - config/pkg.sh ${TARGET:${GEN}/%.mk=%} >${TARGET} 2>${TARGET}.log + @printf '# %s\n' "${TARGET}" >${TARGET} + config/pkg.sh ${TARGET:${GEN}/%.mk=%} >>${TARGET} 2>${TARGET}.log + @if [ "${IS_V}" ]; then \ + cat ${TARGET}; \ + elif grep -q PKGS ${TARGET}; then \ + printf '[ GEN] %-18s [y]\n' ${TARGET}; \ + else \ + printf '[ GEN] %-18s [n]\n' ${TARGET}; \ + fi diff --git a/config/pkgconf.sh b/config/pkgconf.sh index a13b30f..80dcbee 100755 --- a/config/pkgconf.sh +++ b/config/pkgconf.sh @@ -17,29 +17,29 @@ if (($# < 1)); then exit fi -if [[ "$NOLIBS" == *y* ]]; then +if [[ "$XNOLIBS" == *y* ]]; then exit 1 fi -if command -v "${PKG_CONFIG:-}" &>/dev/null; then +if command -v "${XPKG_CONFIG:-}" &>/dev/null; then case "$MODE" in "") - "$PKG_CONFIG" "$@" + "$XPKG_CONFIG" "$@" ;; --cflags) - OUT=$("$PKG_CONFIG" --cflags "$@") + OUT=$("$XPKG_CONFIG" --cflags "$@") if [ "$OUT" ]; then printf 'CFLAGS += %s\n' "$OUT" fi ;; --ldflags) - OUT=$("$PKG_CONFIG" --libs-only-L --libs-only-other "$@") + OUT=$("$XPKG_CONFIG" --libs-only-L --libs-only-other "$@") if [ "$OUT" ]; then printf 'LDFLAGS += %s\n' "$OUT" fi ;; --ldlibs) - OUT=$("$PKG_CONFIG" --libs-only-l "$@") + OUT=$("$XPKG_CONFIG" --libs-only-l "$@") if [ "$OUT" ]; then printf 'LDLIBS := %s ${LDLIBS}\n' "$OUT" fi diff --git a/config/pkgs.mk b/config/pkgs.mk index 54024b2..5ebbaec 100644 --- a/config/pkgs.mk +++ b/config/pkgs.mk @@ -3,12 +3,15 @@ # Makefile that generates gen/pkgs.mk -.OBJDIR: . - -include config/vars.mk +include config/prelude.mk +include ${GEN}/vars.mk +include ${GEN}/flags.mk include ${GEN}/pkgs.mk +include config/exports.mk -default:: - config/pkgconf.sh --cflags ${PKGS} >>${TARGET} 2>>${TARGET}.log - config/pkgconf.sh --ldflags ${PKGS} >>${TARGET} 2>>${TARGET}.log - config/pkgconf.sh --ldlibs ${PKGS} >>${TARGET} 2>>${TARGET}.log +${GEN}/pkgs.mk:: + ${MSG} "[ GEN] $@" + config/pkgconf.sh --cflags ${PKGS} >>$@ 2>>$@.log + config/pkgconf.sh --ldflags ${PKGS} >>$@ 2>>$@.log + config/pkgconf.sh --ldlibs ${PKGS} >>$@ 2>>$@.log + ${VCAT} $@ diff --git a/config/prelude.mk b/config/prelude.mk new file mode 100644 index 0000000..1d5fb83 --- /dev/null +++ b/config/prelude.mk @@ -0,0 +1,164 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Common makefile utilities. Compatible with both GNU make and most BSD makes. + +# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to +.OBJDIR: . + +# We don't use any suffix rules +.SUFFIXES: + +# GNU make has $^ for the full list of targets, while BSD make has $> and the +# long-form ${.ALLSRC}. We could write $^ $> to get them both, but that would +# break if one of them implemented support for the other. So instead, bring +# BSD's ${.ALLSRC} to GNU. +.ALLSRC ?= $^ + +# For out-of-tree builds, e.g. +# +# $ make config BUILDDIR=/path/to/build/dir +# $ make BUILDDIR=/path/to/build/dir +BUILDDIR ?= . + +# Shorthand for build subdirectories +BIN := ${BUILDDIR}/bin +GEN := ${BUILDDIR}/gen +OBJ := ${BUILDDIR}/obj + +# GNU make strips a leading ./ from target names, so do the same for BSD make +BIN := ${BIN:./%=%} +GEN := ${GEN:./%=%} +OBJ := ${OBJ:./%=%} + +# The configuration file generated by `make config` +CONFIG := ${GEN}/config.mk + +# Installation paths +DESTDIR ?= +PREFIX ?= /usr +MANDIR ?= ${PREFIX}/share/man + +# GNU make supports `export VAR`, but BSD make requires `export VAR=value`. +# Sadly, GNU make gives a recursion error on `export VAR=${VAR}`. +_BUILDDIR := ${BUILDDIR} +export BUILDDIR=${_BUILDDIR} + +# Configurable executables; can be overridden with +# +# $ make config CC=clang +CC ?= cc +INSTALL ?= install +MKDIR ?= mkdir -p +PKG_CONFIG ?= pkg-config +RM ?= rm -f + +# GNU and BSD make have incompatible syntax for conditionals, but we can do a +# lot with just nested variable expansion. We use "y" as the canonical +# truthy value, and "" (the empty string) as the canonical falsey value. +# +# To normalize a boolean, use ${TRUTHY,${VAR}}, which expands like this: +# +# VAR=y ${TRUTHY,${VAR}} => ${TRUTHY,y} => y +# VAR=1 ${TRUTHY,${VAR}} => ${TRUTHY,1} => y +# VAR=n ${TRUTHY,${VAR}} => ${TRUTHY,n} => [empty] +# VAR=other ${TRUTHY,${VAR}} => ${TRUTHY,other} => [empty] +# VAR= ${TRUTHY,${VAR}} => ${TRUTHY,} => [emtpy] +# +# Inspired by https://github.com/wahern/autoguess +TRUTHY,y := y +TRUTHY,1 := y + +# Boolean operators are also implemented with nested expansion +NOT,y := +NOT, := y + +# Support up to 5 arguments +AND,y := y +AND,y,y := y +AND,y,y,y := y +AND,y,y,y,y := y +AND,y,y,y,y,y := y + +# NOR can be defined without combinatorial explosion. +# OR is just ${NOT,${NOR,...}} +NOR, := y +NOR,, := y +NOR,,, := y +NOR,,,, := y +NOR,,,,, := y + +# Normalize ${V} to either "y" or "" +IS_V := ${TRUTHY,${V}} + +# Suppress output unless V=1 +Q, := @ +Q := ${Q,${IS_V}} + +# Show full commands with `make V=1`, otherwise short summaries +MSG = @msg() { \ + MSG="$$1"; \ + shift; \ + test "${IS_V}" || printf '%s\n' "$$MSG"; \ + test "$${1:-}" || return 0; \ + test "${IS_V}" && printf '%s\n' "$$*"; \ + "$$@"; \ + }; \ + msg + +# cat a file if V=1 +VCAT,y := @cat +VCAT, := @: +VCAT := ${VCAT,${IS_V}} + +# List all object files here, as they're needed by both `make config` and `make` + +# All object files except the entry point +LIBBFS := \ + ${OBJ}/src/alloc.o \ + ${OBJ}/src/bar.o \ + ${OBJ}/src/bfstd.o \ + ${OBJ}/src/bftw.o \ + ${OBJ}/src/color.o \ + ${OBJ}/src/ctx.o \ + ${OBJ}/src/diag.o \ + ${OBJ}/src/dir.o \ + ${OBJ}/src/dstring.o \ + ${OBJ}/src/eval.o \ + ${OBJ}/src/exec.o \ + ${OBJ}/src/expr.o \ + ${OBJ}/src/fsade.o \ + ${OBJ}/src/ioq.o \ + ${OBJ}/src/mtab.o \ + ${OBJ}/src/opt.o \ + ${OBJ}/src/parse.o \ + ${OBJ}/src/printf.o \ + ${OBJ}/src/pwcache.o \ + ${OBJ}/src/stat.o \ + ${OBJ}/src/thread.o \ + ${OBJ}/src/trie.o \ + ${OBJ}/src/typo.o \ + ${OBJ}/src/version.o \ + ${OBJ}/src/xregex.o \ + ${OBJ}/src/xspawn.o \ + ${OBJ}/src/xtime.o + +# Unit test objects +UNIT_OBJS := \ + ${OBJ}/tests/alloc.o \ + ${OBJ}/tests/bfstd.o \ + ${OBJ}/tests/bit.o \ + ${OBJ}/tests/ioq.o \ + ${OBJ}/tests/main.o \ + ${OBJ}/tests/trie.o \ + ${OBJ}/tests/xspawn.o \ + ${OBJ}/tests/xtime.o + +# All object files +OBJS := \ + ${OBJ}/src/main.o \ + ${OBJ}/tests/mksock.o \ + ${OBJ}/tests/xspawnee.o \ + ${OBJ}/tests/xtouch.o \ + ${LIBBFS} \ + ${UNIT_OBJS} diff --git a/config/vars.mk b/config/vars.mk deleted file mode 100644 index a8fae9d..0000000 --- a/config/vars.mk +++ /dev/null @@ -1,21 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Makefile fragment loads and exports variables for config steps - -GEN := ${BUILDDIR}/gen -GEN := ${GEN:./%=%} - -include ${GEN}/vars.mk - -_CC := ${CC} -_CPPFLAGS := ${CPPFLAGS} -_CFLAGS := ${CFLAGS} -_LDFLAGS := ${LDFLAGS} -_LDLIBS := ${LDLIBS} - -export CC=${_CC} -export CPPFLAGS=${_CPPFLAGS} -export CFLAGS=${_CFLAGS} -export LDFLAGS=${_LDFLAGS} -export LDLIBS=${_LDLIBS} diff --git a/config/vars.sh b/config/vars.sh deleted file mode 100755 index 8a781bb..0000000 --- a/config/vars.sh +++ /dev/null @@ -1,81 +0,0 @@ -#!/usr/bin/env bash - -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Writes the saved variables to gen/vars.mk - -set -eu - -print() { - NAME="$1" - OP="${2:-:=}" - - if (($# >= 3)); then - printf '# %s\n' "${3#X}" - declare -n VAR="$3" - VALUE="${VAR:-}" - else - # Try X$NAME, $NAME, "" - local -n XVAR="X$NAME" - local -n VAR="$NAME" - VALUE="${XVAR:-${VAR:-}}" - fi - - printf '%s %s %s\n' "$NAME" "$OP" "$VALUE" -} - -cond_flags() { - local -n COND="$1" - - if [[ "${COND:-}" == *y* ]]; then - print "$2" += "${1}_${2}" - fi -} - -print PREFIX -print MANDIR - -print OS -print ARCH - -print CC -print INSTALL -print MKDIR -print RM - -print CPPFLAGS := BFS_CPPFLAGS -cond_flags TSAN CPPFLAGS -cond_flags LINT CPPFLAGS -cond_flags RELEASE CPPFLAGS -print CPPFLAGS += XCPPFLAGS -print CPPFLAGS += EXTRA_CPPFLAGS - -print CFLAGS := BFS_CFLAGS -cond_flags ASAN CFLAGS -cond_flags LSAN CFLAGS -cond_flags MSAN CFLAGS -cond_flags TSAN CFLAGS -cond_flags UBSAN CFLAGS -cond_flags SAN CFLAGS -cond_flags GCOV CFLAGS -cond_flags LINT CFLAGS -cond_flags RELEASE CFLAGS -print CFLAGS += XCFLAGS -print CFLAGS += EXTRA_CFLAGS - -print LDFLAGS := XLDFLAGS -print LDFLAGS += EXTRA_LDFLAGS - -print LDLIBS := XLDLIBS -print LDLIBS += EXTRA_LDLIBS -print LDLIBS += BFS_LDLIBS - -print PKGS - -# Disable ASLR on FreeBSD when sanitizers are enabled -case "$XOS-$SAN" in - FreeBSD-*y*) - printf 'POSTLINK = elfctl -e +noaslr $@\n' - ;; -esac -- cgit v1.2.3 From 83bfed52e523ef942da82a083584a4548182aaa4 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Apr 2024 10:09:38 -0400 Subject: build: Support NOLIBS=1 --- config/pkgconf.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/pkgconf.sh b/config/pkgconf.sh index 80dcbee..361852e 100755 --- a/config/pkgconf.sh +++ b/config/pkgconf.sh @@ -17,7 +17,7 @@ if (($# < 1)); then exit fi -if [[ "$XNOLIBS" == *y* ]]; then +if [[ "$XNOLIBS" == [y1] ]]; then exit 1 fi -- cgit v1.2.3 From 2838fd4a4e7fa40dd9cd95c81eb852190371ec5a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Apr 2024 10:18:31 -0400 Subject: build: Make the config scripts POSIX-compliant --- Makefile | 2 +- config/cc.sh | 2 +- config/pkg.sh | 8 ++++---- config/pkgconf.sh | 22 ++++++++++++---------- 4 files changed, 18 insertions(+), 16 deletions(-) diff --git a/Makefile b/Makefile index e9efef3..f42c59b 100644 --- a/Makefile +++ b/Makefile @@ -28,7 +28,7 @@ config:: # Direct users to the new configuration system. asan lsan msan tsan ubsan gcov lint release:: @printf 'error: `%s %s` is no longer supported. ' "${MAKE}" $@ >&2 - @printf 'Use `%s config %s=y` instead.\n' "${MAKE}" $$(echo $@ | tr '[a-z]' '[A-Z]') >&2 + @printf 'Use `%s config %s=y` instead.\n' "${MAKE}" $$(echo $@ | tr 'a-z' 'A-Z') >&2 @false # Print an error if `make` is run before `make config` diff --git a/config/cc.sh b/config/cc.sh index abce508..e6883f5 100755 --- a/config/cc.sh +++ b/config/cc.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD diff --git a/config/pkg.sh b/config/pkg.sh index 2ca533e..4ebea64 100755 --- a/config/pkg.sh +++ b/config/pkg.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD @@ -7,10 +7,10 @@ set -eu -NAME="${1^^}" -declare -n XUSE="USE_$NAME" +NAME=$(printf '%s' "$1" | tr 'a-z' 'A-Z') +eval "XUSE=\"\${USE_$NAME:-}\"" -if [ "${XUSE:-}" ]; then +if [ "$XUSE" ]; then USE="$XUSE" elif config/pkgconf.sh "$1"; then USE=y diff --git a/config/pkgconf.sh b/config/pkgconf.sh index 361852e..d6c45c7 100755 --- a/config/pkgconf.sh +++ b/config/pkgconf.sh @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD @@ -8,20 +8,22 @@ set -eu MODE= -if [[ "${1:-}" == --* ]]; then - MODE="$1" - shift -fi +case "${1:-}" in + --*) + MODE="$1" + shift +esac -if (($# < 1)); then +if [ $# -lt 1 ]; then exit fi -if [[ "$XNOLIBS" == [y1] ]]; then - exit 1 -fi +case "$XNOLIBS" in + y|1) + exit 1 +esac -if command -v "${XPKG_CONFIG:-}" &>/dev/null; then +if command -v "${XPKG_CONFIG:-}" >/dev/null 2>&1; then case "$MODE" in "") "$XPKG_CONFIG" "$@" -- cgit v1.2.3 From 6917d790e1a76b82e5bc21ed38a38a64cc994d54 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Apr 2024 10:22:31 -0400 Subject: build: Properly export PKG_CONFIG --- config/config.mk | 1 + config/exports.mk | 1 + 2 files changed, 2 insertions(+) diff --git a/config/config.mk b/config/config.mk index c8d7f19..164af76 100644 --- a/config/config.mk +++ b/config/config.mk @@ -34,6 +34,7 @@ ${GEN}/vars.mk:: @printf 'CC := %s\n' "$$XCC" >>$@ @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ + @printf 'PKG_CONFIG := %s\n' "$$XPKG_CONFIG" >>$@ @printf 'RM := %s\n' "$$XRM" >>$@ @printf 'PKGS :=\n' >>$@ ${VCAT} $@ diff --git a/config/exports.mk b/config/exports.mk index 9128568..ed19134 100644 --- a/config/exports.mk +++ b/config/exports.mk @@ -9,6 +9,7 @@ export XMANDIR=${MANDIR} export XCC=${CC} export XINSTALL=${INSTALL} export XMKDIR=${MKDIR} +export XPKG_CONFIG=${PKG_CONFIG} export XRM=${RM} export XCPPFLAGS=${CPPFLAGS} -- cgit v1.2.3 From 77f376ef9d4b62747ec3b8cc0abb9c3d2e0bbd6d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Apr 2024 11:35:52 -0400 Subject: build: Add back the default CFLAGS --- config/flags.mk | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/config/flags.mk b/config/flags.mk index e62e26e..2c91691 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -6,6 +6,26 @@ include config/prelude.mk include ${GEN}/vars.mk +# Configurable flags +CPPFLAGS ?= +CFLAGS ?= \ + -g \ + -Wall \ + -Wformat=2 \ + -Werror=implicit \ + -Wimplicit-fallthrough \ + -Wmissing-declarations \ + -Wshadow \ + -Wsign-compare \ + -Wstrict-prototypes +LDFLAGS ?= +LDLIBS ?= + +export XCPPFLAGS=${CPPFLAGS} +export XCFLAGS=${CFLAGS} +export XLDFLAGS=${LDFLAGS} +export XLDLIBS=${LDLIBS} + # Immutable flags export BFS_CPPFLAGS= \ -D__EXTENSIONS__ \ @@ -27,12 +47,6 @@ LDLIBS,NetBSD := -lutil LDLIBS,SunOS := -lsocket -lnsl export BFS_LDLIBS=${LDLIBS,${OS}} -# Make sure we pick up any default flags from e.g. sys.mk -export XCPPFLAGS=${CPPFLAGS} -export XCFLAGS=${CFLAGS} -export XLDFLAGS=${LDFLAGS} -export XLDLIBS=${LDLIBS} - # Build profiles _ASAN := ${TRUTHY,${ASAN}} _LSAN := ${TRUTHY,${LSAN}} -- cgit v1.2.3 From adf0ab47c2d18d807bb21345b92be9cc5525d539 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Apr 2024 11:39:20 -0400 Subject: mtab: Arena-allocate struct bfs_mount --- src/mtab.c | 43 ++++++++++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 15 deletions(-) diff --git a/src/mtab.c b/src/mtab.c index c76f403..86ae151 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -41,11 +41,16 @@ struct bfs_mount { char *path; /** The filesystem type. */ char *type; + /** Buffer for the strings. */ + char buf[]; }; struct bfs_mtab { + /** Mount point arena. */ + struct varena varena; + /** The array of mount points. */ - struct bfs_mount *mounts; + struct bfs_mount **mounts; /** The number of mount points. */ size_t nmounts; @@ -63,28 +68,37 @@ struct bfs_mtab { */ attr(maybe_unused) static int bfs_mtab_add(struct bfs_mtab *mtab, const char *path, const char *type) { - struct bfs_mount *mount = RESERVE(struct bfs_mount, &mtab->mounts, &mtab->nmounts); + size_t path_size = strlen(path) + 1; + size_t type_size = strlen(type) + 1; + size_t size = path_size + type_size; + struct bfs_mount *mount = varena_alloc(&mtab->varena, size); if (!mount) { return -1; } - mount->path = strdup(path); - mount->type = strdup(type); - if (!mount->path || !mount->type) { - goto fail; + struct bfs_mount **ptr = RESERVE(struct bfs_mount *, &mtab->mounts, &mtab->nmounts); + if (!ptr) { + goto free; } + *ptr = mount; + + mount->path = mount->buf; + memcpy(mount->path, path, path_size); + + mount->type = mount->buf + path_size; + memcpy(mount->type, type, type_size); const char *name = path + xbaseoff(path); if (!trie_insert_str(&mtab->names, name)) { - goto fail; + goto shrink; } return 0; -fail: - free(mount->type); - free(mount->path); +shrink: --mtab->nmounts; +free: + varena_free(&mtab->varena, mount, size); return -1; } @@ -94,6 +108,8 @@ struct bfs_mtab *bfs_mtab_parse(void) { return NULL; } + VARENA_INIT(&mtab->varena, struct bfs_mount, buf); + trie_init(&mtab->names); trie_init(&mtab->types); @@ -195,7 +211,7 @@ static int bfs_mtab_fill_types(struct bfs_mtab *mtab) { struct bfs_stat parent_stat; for (size_t i = 0; i < mtab->nmounts; ++i) { - struct bfs_mount *mount = &mtab->mounts[i]; + struct bfs_mount *mount = mtab->mounts[i]; const char *path = mount->path; int fd = AT_FDCWD; @@ -280,11 +296,8 @@ void bfs_mtab_free(struct bfs_mtab *mtab) { trie_destroy(&mtab->types); trie_destroy(&mtab->names); - for (size_t i = 0; i < mtab->nmounts; ++i) { - free(mtab->mounts[i].type); - free(mtab->mounts[i].path); - } free(mtab->mounts); + varena_destroy(&mtab->varena); free(mtab); } -- cgit v1.2.3 From 94f26cec62be7f0d766f80f7376ea5e0040948ae Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Apr 2024 12:09:11 -0400 Subject: build: Dont include ${BUILDDIR} in short messages --- Makefile | 2 +- config/config.mk | 6 +++--- config/deps.mk | 2 +- config/flags.mk | 2 +- config/pkg.mk | 7 +++++-- config/pkgs.mk | 2 +- config/prelude.mk | 3 +++ 7 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Makefile b/Makefile index f42c59b..52f3aee 100644 --- a/Makefile +++ b/Makefile @@ -64,7 +64,7 @@ ${BIN}/bfs: ${LIBBFS} ${OBJ}/src/main.o ${BINS}: @${MKDIR} ${@D} - +${MSG} "[ LD ] $@" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ + +${MSG} "[ LD ] ${TGT}" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ ${POSTLINK} # Get the .c file for a .o file diff --git a/config/config.mk b/config/config.mk index 164af76..9d64eec 100644 --- a/config/config.mk +++ b/config/config.mk @@ -16,7 +16,7 @@ MKS := \ # The main configuration file, which includes the others ${CONFIG}: ${MKS} - ${MSG} "[ GEN] $@" + ${MSG} "[ GEN] ${TGT}" @printf '# %s\n' "$@" >$@ @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@ ${VCAT} ${CONFIG} @@ -25,7 +25,7 @@ ${CONFIG}: ${MKS} # Saves the configurable variables ${GEN}/vars.mk:: @${MKDIR} ${@D} - ${MSG} "[ GEN] $@" + ${MSG} "[ GEN] ${TGT}" @printf '# %s\n' "$@" >$@ @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@ @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ @@ -53,7 +53,7 @@ ${GEN}/deps.mk: ${GEN}/flags.mk # Lists file.o: file.c dependencies ${GEN}/objs.mk:: @${MKDIR} ${@D} - ${MSG} "[ GEN] $@" + ${MSG} "[ GEN] ${TGT}" @printf '# %s\n' "$@" >$@ @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >>$@ diff --git a/config/deps.mk b/config/deps.mk index 52ee0e1..ca178de 100644 --- a/config/deps.mk +++ b/config/deps.mk @@ -9,7 +9,7 @@ include ${GEN}/flags.mk include config/exports.mk ${GEN}/deps.mk:: - ${MSG} "[ GEN] $@" + ${MSG} "[ GEN] ${TGT}" printf '# %s\n' "$@" >$@ if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ printf 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}\n'; \ diff --git a/config/flags.mk b/config/flags.mk index 2c91691..c9d7913 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -104,7 +104,7 @@ SETVAR = printf '%s := %s\n' >>$@ APPEND = append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append ${GEN}/flags.mk:: - ${MSG} "[ GEN] $@" + ${MSG} "[ GEN] ${TGT}" printf '# %s\n' "$@" >$@ ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS" ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS" diff --git a/config/pkg.mk b/config/pkg.mk index 2086555..482ca17 100644 --- a/config/pkg.mk +++ b/config/pkg.mk @@ -8,13 +8,16 @@ include ${GEN}/vars.mk include ${GEN}/flags.mk include config/exports.mk +# Like ${TGT} but for ${TARGET}, not $@ +SHORT = ${TARGET:${BUILDDIR}/%=%} + default:: @printf '# %s\n' "${TARGET}" >${TARGET} config/pkg.sh ${TARGET:${GEN}/%.mk=%} >>${TARGET} 2>${TARGET}.log @if [ "${IS_V}" ]; then \ cat ${TARGET}; \ elif grep -q PKGS ${TARGET}; then \ - printf '[ GEN] %-18s [y]\n' ${TARGET}; \ + printf '[ GEN] %-18s [y]\n' ${SHORT}; \ else \ - printf '[ GEN] %-18s [n]\n' ${TARGET}; \ + printf '[ GEN] %-18s [n]\n' ${SHORT}; \ fi diff --git a/config/pkgs.mk b/config/pkgs.mk index 5ebbaec..de9e16e 100644 --- a/config/pkgs.mk +++ b/config/pkgs.mk @@ -10,7 +10,7 @@ include ${GEN}/pkgs.mk include config/exports.mk ${GEN}/pkgs.mk:: - ${MSG} "[ GEN] $@" + ${MSG} "[ GEN] ${TGT}" config/pkgconf.sh --cflags ${PKGS} >>$@ 2>>$@.log config/pkgconf.sh --ldflags ${PKGS} >>$@ 2>>$@.log config/pkgconf.sh --ldlibs ${PKGS} >>$@ 2>>$@.log diff --git a/config/prelude.mk b/config/prelude.mk index 1d5fb83..2bcb208 100644 --- a/config/prelude.mk +++ b/config/prelude.mk @@ -95,6 +95,9 @@ IS_V := ${TRUTHY,${V}} Q, := @ Q := ${Q,${IS_V}} +# The current target, with ${BUILDDIR} stripped for shorter messages +TGT = ${@:${BUILDDIR}/%=%} + # Show full commands with `make V=1`, otherwise short summaries MSG = @msg() { \ MSG="$$1"; \ -- cgit v1.2.3 From 9f1107cf041d44e79f566f41fc117321fb42881f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Apr 2024 12:32:22 -0400 Subject: build: Directly generate version.c --- Makefile | 21 ++++++++++----------- config/config.mk | 12 +++++++----- config/deps.mk | 2 +- config/flags.mk | 2 +- config/pkg.mk | 2 +- config/prelude.mk | 4 ++-- src/main.c | 1 - src/version.c | 6 ------ 8 files changed, 22 insertions(+), 28 deletions(-) delete mode 100644 src/version.c diff --git a/Makefile b/Makefile index 52f3aee..1d26d12 100644 --- a/Makefile +++ b/Makefile @@ -68,32 +68,31 @@ ${BINS}: ${POSTLINK} # Get the .c file for a .o file -CSRC = ${@:${OBJ}/%.o=%.c} +_CSRC = ${@:${OBJ}/%.o=%.c} +CSRC = ${_CSRC:gen/%=${GEN}/%} # Depend on ${CONFIG} to make sure `make config` runs first, and to rebuild when # the configuration changes ${OBJS}: ${CONFIG} @${MKDIR} ${@D} - ${MSG} "[ CC ] ${CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ + ${MSG} "[ CC ] ${_CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ -# Save the version number to this file, but only update VERSION if it changes -${GEN}/NEWVERSION:: +# Save the version number to this file, but only update version.c if it changes +${GEN}/version.c.new:: @${MKDIR} ${@D} + @printf 'const char bfs_version[] = "' >$@ @if [ "$$VERSION" ]; then \ - printf '%s\n' "$$VERSION"; \ + printf '%s' "$$VERSION"; \ elif test -d .git && command -v git >/dev/null 2>&1; then \ git describe --always --dirty; \ else \ echo "3.1.3"; \ - fi >$@ + fi | tr -d '\n' >>$@ + @printf '";\n' >>$@ -${GEN}/VERSION: ${GEN}/NEWVERSION +${GEN}/version.c: ${GEN}/version.c.new @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@ -# Rebuild version.c whenever the version number changes -${OBJ}/src/version.o: ${GEN}/VERSION -${OBJ}/src/version.o: CPPFLAGS := ${CPPFLAGS} -DBFS_VERSION='"$$(cat ${GEN}/VERSION)"' - ## Test phase (`make check`) # Unit test binaries diff --git a/config/config.mk b/config/config.mk index 9d64eec..4771a5e 100644 --- a/config/config.mk +++ b/config/config.mk @@ -17,7 +17,7 @@ MKS := \ # The main configuration file, which includes the others ${CONFIG}: ${MKS} ${MSG} "[ GEN] ${TGT}" - @printf '# %s\n' "$@" >$@ + @printf '# %s\n' "${TGT}" >$@ @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@ ${VCAT} ${CONFIG} .PHONY: ${CONFIG} @@ -26,7 +26,7 @@ ${CONFIG}: ${MKS} ${GEN}/vars.mk:: @${MKDIR} ${@D} ${MSG} "[ GEN] ${TGT}" - @printf '# %s\n' "$@" >$@ + @printf '# %s\n' "${TGT}" >$@ @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@ @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ @printf 'OS := %s\n' "$${OS:-$$(uname)}" >>$@ @@ -54,8 +54,10 @@ ${GEN}/deps.mk: ${GEN}/flags.mk ${GEN}/objs.mk:: @${MKDIR} ${@D} ${MSG} "[ GEN] ${TGT}" - @printf '# %s\n' "$@" >$@ - @for obj in ${OBJS:${OBJ}/%.o=%}; do printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; done >>$@ + @printf '# %s\n' "${TGT}" >$@ + @for obj in ${OBJS:${OBJ}/%.o=%}; do \ + printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; \ + done | sed 's|: gen/|: $${GEN}/|' >>$@ # External dependencies PKG_MKS := \ @@ -67,7 +69,7 @@ PKG_MKS := \ # Auto-detect dependencies and their build flags ${GEN}/pkgs.mk: ${PKG_MKS} - @printf '# %s\n' "$@" >$@ + @printf '# %s\n' "${TGT}" >$@ @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@ @+${MAKE} -sf config/pkgs.mk .PHONY: ${GEN}/pkgs.mk diff --git a/config/deps.mk b/config/deps.mk index ca178de..e963b6e 100644 --- a/config/deps.mk +++ b/config/deps.mk @@ -10,7 +10,7 @@ include config/exports.mk ${GEN}/deps.mk:: ${MSG} "[ GEN] ${TGT}" - printf '# %s\n' "$@" >$@ + printf '# %s\n' "${TGT}" >$@ if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ printf 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}\n'; \ fi >>$@ 2>$@.log diff --git a/config/flags.mk b/config/flags.mk index c9d7913..48f1a08 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -105,7 +105,7 @@ APPEND = append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; ap ${GEN}/flags.mk:: ${MSG} "[ GEN] ${TGT}" - printf '# %s\n' "$@" >$@ + printf '# %s\n' "${TGT}" >$@ ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS" ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS" ${APPEND} CPPFLAGS "$$LINT_CPPFLAGS" diff --git a/config/pkg.mk b/config/pkg.mk index 482ca17..d2d77b0 100644 --- a/config/pkg.mk +++ b/config/pkg.mk @@ -12,7 +12,7 @@ include config/exports.mk SHORT = ${TARGET:${BUILDDIR}/%=%} default:: - @printf '# %s\n' "${TARGET}" >${TARGET} + @printf '# %s\n' "${SHORT}" >${TARGET} config/pkg.sh ${TARGET:${GEN}/%.mk=%} >>${TARGET} 2>${TARGET}.log @if [ "${IS_V}" ]; then \ cat ${TARGET}; \ diff --git a/config/prelude.mk b/config/prelude.mk index 2bcb208..dbc6875 100644 --- a/config/prelude.mk +++ b/config/prelude.mk @@ -141,10 +141,10 @@ LIBBFS := \ ${OBJ}/src/thread.o \ ${OBJ}/src/trie.o \ ${OBJ}/src/typo.o \ - ${OBJ}/src/version.o \ ${OBJ}/src/xregex.o \ ${OBJ}/src/xspawn.o \ - ${OBJ}/src/xtime.o + ${OBJ}/src/xtime.o \ + ${OBJ}/gen/version.o # Unit test objects UNIT_OBJS := \ diff --git a/src/main.c b/src/main.c index b4d65ce..e120f03 100644 --- a/src/main.c +++ b/src/main.c @@ -40,7 +40,6 @@ * - thread.h (multi-threading) * - trie.[ch] (a trie set/map implementation) * - typo.[ch] (fuzzy matching for typos) - * - version.c (defines the version number) * - xregex.[ch] (regular expression support) * - xspawn.[ch] (spawns processes) * - xtime.[ch] (date/time handling utilities) diff --git a/src/version.c b/src/version.c deleted file mode 100644 index 736f3d5..0000000 --- a/src/version.c +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include "config.h" - -const char bfs_version[] = BFS_VERSION; -- cgit v1.2.3 From 10f665bd0ea6954fce620325aeeecb5e869f7479 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 17 Apr 2024 12:39:59 -0400 Subject: config: Delete gen/objs.mk Rather than explicitly listing all these dependencies, we can rely on DEPFLAGS to generate them for us. --- Makefile | 2 ++ config/config.mk | 10 ---------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/Makefile b/Makefile index 1d26d12..981c322 100644 --- a/Makefile +++ b/Makefile @@ -93,6 +93,8 @@ ${GEN}/version.c.new:: ${GEN}/version.c: ${GEN}/version.c.new @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@ +${OBJ}/gen/version.o: ${GEN}/version.c + ## Test phase (`make check`) # Unit test binaries diff --git a/config/config.mk b/config/config.mk index 4771a5e..bfcb559 100644 --- a/config/config.mk +++ b/config/config.mk @@ -11,7 +11,6 @@ MKS := \ ${GEN}/vars.mk \ ${GEN}/flags.mk \ ${GEN}/deps.mk \ - ${GEN}/objs.mk \ ${GEN}/pkgs.mk # The main configuration file, which includes the others @@ -50,15 +49,6 @@ ${GEN}/deps.mk: ${GEN}/flags.mk @+${MAKE} -sf config/deps.mk .PHONY: ${GEN}/deps.mk -# Lists file.o: file.c dependencies -${GEN}/objs.mk:: - @${MKDIR} ${@D} - ${MSG} "[ GEN] ${TGT}" - @printf '# %s\n' "${TGT}" >$@ - @for obj in ${OBJS:${OBJ}/%.o=%}; do \ - printf '$${OBJ}/%s.o: %s.c\n' "$$obj" "$$obj"; \ - done | sed 's|: gen/|: $${GEN}/|' >>$@ - # External dependencies PKG_MKS := \ ${GEN}/libacl.mk \ -- cgit v1.2.3 From 9dc954f168e225a8044c99c2b267572ea228c456 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 Apr 2024 12:58:16 -0400 Subject: config: Don't build config tests with -o /dev/null macOS doesn't like it, complaining that error: cannot parse the debug map for '/dev/null': The file was not recognized as a valid object file clang: error: dsymutil command failed with exit code 1 (use -v to see invocation) Use a temporary file instead. --- config/cc.sh | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/config/cc.sh b/config/cc.sh index e6883f5..45d51ca 100755 --- a/config/cc.sh +++ b/config/cc.sh @@ -5,6 +5,12 @@ # Run the compiler and check if it succeeded -set -eux +set -eu -$XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o /dev/null +TMP=$(mktemp) +trap 'rm -f "$TMP"' EXIT + +( + set -x + $XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o "$TMP" +) -- cgit v1.2.3 From e59be892d234135179dd7c3b072f8f12a41b6e1a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 16:49:57 -0400 Subject: config: Fix ${SAN} on BSD make This seems to have hit some BSD make limitation, making it set `SAN := y` unconditionally for some reason. Breaking up the expression fixes it. --- config/flags.mk | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/flags.mk b/config/flags.mk index 48f1a08..1043779 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -74,7 +74,8 @@ export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}} export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}} SAN_CFLAGS,y := -fno-sanitize-recover=all -SAN := ${NOT,${NOR,${_ASAN},${_LSAN},${_MSAN},${_TSAN},${_UBSAN}}} +NO_SAN := ${NOR,${_ASAN},${_LSAN},${_MSAN},${_TSAN},${_UBSAN}} +SAN := ${NOT,${NO_SAN}} export SAN_CFLAGS=${SAN_CFLAGS,${SAN}} # MSAN and TSAN both need all code to be instrumented -- cgit v1.2.3 From 00a36e84e385bc02f04e335d8e5f786a2e962c9d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 08:03:43 -0400 Subject: config: Remove explicit -MF from DEPFLAGS We use the default name anyway. --- config/deps.mk | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/deps.mk b/config/deps.mk index e963b6e..2201f06 100644 --- a/config/deps.mk +++ b/config/deps.mk @@ -12,7 +12,7 @@ ${GEN}/deps.mk:: ${MSG} "[ GEN] ${TGT}" printf '# %s\n' "${TGT}" >$@ if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ - printf 'DEPFLAGS = -MD -MP -MF $${@:.o=.d}\n'; \ + printf 'DEPFLAGS = -MD -MP\n'; \ fi >>$@ 2>$@.log ${VCAT} $@ @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ -- cgit v1.2.3 From 3eb6e9c065007553ba75b473611becf8b64b3b46 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 12:27:49 -0400 Subject: config: Remove unused ${ARCH} variable --- config/config.mk | 1 - 1 file changed, 1 deletion(-) diff --git a/config/config.mk b/config/config.mk index bfcb559..5a6f8d2 100644 --- a/config/config.mk +++ b/config/config.mk @@ -29,7 +29,6 @@ ${GEN}/vars.mk:: @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@ @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ @printf 'OS := %s\n' "$${OS:-$$(uname)}" >>$@ - @printf 'ARCH := %s\n' "$${ARCH:-$$(uname -m)}" >>$@ @printf 'CC := %s\n' "$$XCC" >>$@ @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ -- cgit v1.2.3 From aa0843ae50b09bad3ece21bd310b849d034efac2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 14:38:37 -0400 Subject: config: Use ✔/✘ rather than [y]/[n] MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/pkg.mk | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/pkg.mk b/config/pkg.mk index d2d77b0..6f868be 100644 --- a/config/pkg.mk +++ b/config/pkg.mk @@ -17,7 +17,7 @@ default:: @if [ "${IS_V}" ]; then \ cat ${TARGET}; \ elif grep -q PKGS ${TARGET}; then \ - printf '[ GEN] %-18s [y]\n' ${SHORT}; \ + printf '[ GEN] %-18s ✔\n' ${SHORT}; \ else \ - printf '[ GEN] %-18s [n]\n' ${SHORT}; \ + printf '[ GEN] %-18s ✘\n' ${SHORT}; \ fi -- cgit v1.2.3 From 1501910dd0d09c50057c8358901663a87333bd8f Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 14:51:42 -0400 Subject: tests: Add ../src to the include path --- config/flags.mk | 1 + tests/alloc.c | 6 +++--- tests/bfstd.c | 6 +++--- tests/bit.c | 6 +++--- tests/ioq.c | 10 +++++----- tests/main.c | 6 +++--- tests/mksock.c | 2 +- tests/tests.h | 4 ++-- tests/trie.c | 6 +++--- tests/xspawn.c | 10 +++++----- tests/xtime.c | 8 ++++---- tests/xtouch.c | 8 ++++---- 12 files changed, 37 insertions(+), 36 deletions(-) diff --git a/config/flags.mk b/config/flags.mk index 1043779..d02cc8b 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -28,6 +28,7 @@ export XLDLIBS=${LDLIBS} # Immutable flags export BFS_CPPFLAGS= \ + -Isrc \ -D__EXTENSIONS__ \ -D_ATFILE_SOURCE \ -D_BSD_SOURCE \ diff --git a/tests/alloc.c b/tests/alloc.c index 9f08111..54b84ba 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -2,9 +2,9 @@ // SPDX-License-Identifier: 0BSD #include "tests.h" -#include "../src/alloc.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "alloc.h" +#include "config.h" +#include "diag.h" #include #include #include diff --git a/tests/bfstd.c b/tests/bfstd.c index dc5ceaa..5e408ca 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -2,9 +2,9 @@ // SPDX-License-Identifier: 0BSD #include "tests.h" -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "bfstd.h" +#include "config.h" +#include "diag.h" #include #include #include diff --git a/tests/bit.c b/tests/bit.c index 6548c30..b444e50 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -2,9 +2,9 @@ // SPDX-License-Identifier: 0BSD #include "tests.h" -#include "../src/bit.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "bit.h" +#include "config.h" +#include "diag.h" #include #include #include diff --git a/tests/ioq.c b/tests/ioq.c index 1ce8f75..a69f2bf 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -2,11 +2,11 @@ // SPDX-License-Identifier: 0BSD #include "tests.h" -#include "../src/ioq.h" -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/diag.h" -#include "../src/dir.h" +#include "ioq.h" +#include "bfstd.h" +#include "config.h" +#include "diag.h" +#include "dir.h" #include #include #include diff --git a/tests/main.c b/tests/main.c index 69903d4..281c417 100644 --- a/tests/main.c +++ b/tests/main.c @@ -6,9 +6,9 @@ */ #include "tests.h" -#include "../src/bfstd.h" -#include "../src/color.h" -#include "../src/config.h" +#include "bfstd.h" +#include "color.h" +#include "config.h" #include #include #include diff --git a/tests/mksock.c b/tests/mksock.c index f3b61da..5786cb6 100644 --- a/tests/mksock.c +++ b/tests/mksock.c @@ -6,7 +6,7 @@ * program does the job. */ -#include "../src/bfstd.h" +#include "bfstd.h" #include #include #include diff --git a/tests/tests.h b/tests/tests.h index 351badb..d61ffd7 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -8,8 +8,8 @@ #ifndef BFS_TESTS_H #define BFS_TESTS_H -#include "../src/config.h" -#include "../src/diag.h" +#include "config.h" +#include "diag.h" /** Unit test function type. */ typedef bool test_fn(void); diff --git a/tests/trie.c b/tests/trie.c index fec0de2..2a6eb48 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -2,9 +2,9 @@ // SPDX-License-Identifier: 0BSD #include "tests.h" -#include "../src/trie.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "trie.h" +#include "config.h" +#include "diag.h" #include #include diff --git a/tests/xspawn.c b/tests/xspawn.c index c1bac36..fd8362e 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -2,11 +2,11 @@ // SPDX-License-Identifier: 0BSD #include "tests.h" -#include "../src/alloc.h" -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/dstring.h" -#include "../src/xspawn.h" +#include "alloc.h" +#include "bfstd.h" +#include "config.h" +#include "dstring.h" +#include "xspawn.h" #include #include #include diff --git a/tests/xtime.c b/tests/xtime.c index f85402e..fd7aa0f 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -2,10 +2,10 @@ // SPDX-License-Identifier: 0BSD #include "tests.h" -#include "../src/xtime.h" -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/diag.h" +#include "xtime.h" +#include "bfstd.h" +#include "config.h" +#include "diag.h" #include #include #include diff --git a/tests/xtouch.c b/tests/xtouch.c index fad272f..b1daec7 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD -#include "../src/bfstd.h" -#include "../src/config.h" -#include "../src/sanity.h" -#include "../src/xtime.h" +#include "bfstd.h" +#include "config.h" +#include "sanity.h" +#include "xtime.h" #include #include #include -- cgit v1.2.3 From c66379749f423413913b406609dfe9311ba6e555 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 14:53:56 -0400 Subject: Rename config.h to prelude.h --- src/alloc.c | 2 +- src/alloc.h | 2 +- src/bar.c | 2 +- src/bfstd.c | 2 +- src/bfstd.h | 2 +- src/bftw.c | 2 +- src/bit.h | 2 +- src/color.c | 2 +- src/color.h | 2 +- src/config.h | 377 --------------------------------------------------------- src/ctx.h | 2 +- src/diag.c | 2 +- src/diag.h | 2 +- src/dir.c | 2 +- src/dstring.c | 2 +- src/dstring.h | 2 +- src/eval.c | 2 +- src/eval.h | 2 +- src/exec.c | 2 +- src/expr.h | 2 +- src/fsade.c | 2 +- src/fsade.h | 2 +- src/ioq.c | 2 +- src/ioq.h | 2 +- src/main.c | 4 +- src/mtab.c | 2 +- src/mtab.h | 2 +- src/opt.c | 2 +- src/parse.c | 2 +- src/prelude.h | 377 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/printf.c | 2 +- src/pwcache.c | 2 +- src/sanity.h | 2 +- src/stat.c | 2 +- src/stat.h | 2 +- src/thread.c | 2 +- src/thread.h | 2 +- src/trie.c | 2 +- src/xregex.c | 2 +- src/xspawn.c | 2 +- src/xspawn.h | 2 +- src/xtime.c | 2 +- tests/alloc.c | 2 +- tests/bfstd.c | 2 +- tests/bit.c | 2 +- tests/ioq.c | 2 +- tests/main.c | 2 +- tests/tests.h | 2 +- tests/trie.c | 2 +- tests/xspawn.c | 2 +- tests/xtime.c | 2 +- tests/xtouch.c | 2 +- 52 files changed, 428 insertions(+), 428 deletions(-) delete mode 100644 src/config.h create mode 100644 src/prelude.h diff --git a/src/alloc.c b/src/alloc.c index b65d0c5..ec8608f 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include diff --git a/src/alloc.h b/src/alloc.h index ae055bc..095134a 100644 --- a/src/alloc.h +++ b/src/alloc.h @@ -8,7 +8,7 @@ #ifndef BFS_ALLOC_H #define BFS_ALLOC_H -#include "config.h" +#include "prelude.h" #include #include #include diff --git a/src/bar.c b/src/bar.c index 8ab4112..184d9a0 100644 --- a/src/bar.c +++ b/src/bar.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "bar.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" -#include "config.h" #include "dstring.h" #include #include diff --git a/src/bfstd.c b/src/bfstd.c index 2499f00..e1b4804 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "bfstd.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "thread.h" diff --git a/src/bfstd.h b/src/bfstd.h index fc22971..42f5d5b 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -8,7 +8,7 @@ #ifndef BFS_BFSTD_H #define BFS_BFSTD_H -#include "config.h" +#include "prelude.h" #include "sanity.h" #include diff --git a/src/bftw.c b/src/bftw.c index 6130c44..c4d3c17 100644 --- a/src/bftw.c +++ b/src/bftw.c @@ -18,10 +18,10 @@ * various helper functions to take fewer parameters. */ +#include "prelude.h" #include "bftw.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "dstring.h" diff --git a/src/bit.h b/src/bit.h index 69df21e..17cfbcf 100644 --- a/src/bit.h +++ b/src/bit.h @@ -8,7 +8,7 @@ #ifndef BFS_BIT_H #define BFS_BIT_H -#include "config.h" +#include "prelude.h" #include #include diff --git a/src/color.c b/src/color.c index 8c32a68..f004bf2 100644 --- a/src/color.c +++ b/src/color.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "color.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "dstring.h" diff --git a/src/color.h b/src/color.h index e3e7973..3278cd6 100644 --- a/src/color.h +++ b/src/color.h @@ -8,7 +8,7 @@ #ifndef BFS_COLOR_H #define BFS_COLOR_H -#include "config.h" +#include "prelude.h" #include "dstring.h" #include diff --git a/src/config.h b/src/config.h deleted file mode 100644 index 2eff1fc..0000000 --- a/src/config.h +++ /dev/null @@ -1,377 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -/** - * Configuration and feature/platform detection. - */ - -#ifndef BFS_CONFIG_H -#define BFS_CONFIG_H - -// Possible __STDC_VERSION__ values - -#define C95 199409L -#define C99 199901L -#define C11 201112L -#define C17 201710L -#define C23 202311L - -#include - -#if __STDC_VERSION__ < C23 -# include -# include -# include -#endif - -// bfs packaging configuration - -#ifndef BFS_COMMAND -# define BFS_COMMAND "bfs" -#endif -#ifndef BFS_HOMEPAGE -# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" -#endif - -// This is a symbol instead of a literal so we don't have to rebuild everything -// when the version number changes -extern const char bfs_version[]; - -// Check for system headers - -#ifdef __has_include - -#if __has_include() -# define BFS_HAS_MNTENT_H true -#endif -#if __has_include() -# define BFS_HAS_PATHS_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_ACL_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_CAPABILITY_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_EXTATTR_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_MKDEV_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_PARAM_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_SYSMACROS_H true -#endif -#if __has_include() -# define BFS_HAS_SYS_XATTR_H true -#endif -#if __has_include() -# define BFS_HAS_THREADS_H true -#endif -#if __has_include() -# define BFS_HAS_UTIL_H true -#endif - -#else // !__has_include - -#define BFS_HAS_MNTENT_H __GLIBC__ -#define BFS_HAS_PATHS_H true -#define BFS_HAS_SYS_ACL_H true -#define BFS_HAS_SYS_CAPABILITY_H __linux__ -#define BFS_HAS_SYS_EXTATTR_H __FreeBSD__ -#define BFS_HAS_SYS_MKDEV_H false -#define BFS_HAS_SYS_PARAM_H true -#define BFS_HAS_SYS_SYSMACROS_H __GLIBC__ -#define BFS_HAS_SYS_XATTR_H __linux__ -#define BFS_HAS_THREADS_H (!__STDC_NO_THREADS__) -#define BFS_HAS_UTIL_H __NetBSD__ - -#endif // !__has_include - -#ifndef BFS_USE_MNTENT_H -# define BFS_USE_MNTENT_H BFS_HAS_MNTENT_H -#endif -#ifndef BFS_USE_PATHS_H -# define BFS_USE_PATHS_H BFS_HAS_PATHS_H -#endif -#ifndef BFS_USE_SYS_ACL_H -# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL)) -#endif -#ifndef BFS_USE_SYS_CAPABILITY_H -# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP)) -#endif -#ifndef BFS_USE_SYS_EXTATTR_H -# define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__) -#endif -#ifndef BFS_USE_SYS_MKDEV_H -# define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H -#endif -#ifndef BFS_USE_SYS_PARAM_H -# define BFS_USE_SYS_PARAM_H BFS_HAS_SYS_PARAM_H -#endif -#ifndef BFS_USE_SYS_SYSMACROS_H -# define BFS_USE_SYS_SYSMACROS_H BFS_HAS_SYS_SYSMACROS_H -#endif -#ifndef BFS_USE_SYS_XATTR_H -# define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H -#endif -#ifndef BFS_USE_THREADS_H -# define BFS_USE_THREADS_H BFS_HAS_THREADS_H -#endif -#ifndef BFS_USE_UTIL_H -# define BFS_USE_UTIL_H BFS_HAS_UTIL_H -#endif - -// Stub out feature detection on old/incompatible compilers - -#ifndef __has_feature -# define __has_feature(feat) false -#endif - -#ifndef __has_c_attribute -# define __has_c_attribute(attr) false -#endif - -#ifndef __has_attribute -# define __has_attribute(attr) false -#endif - -// Platform detection - -// Get the definition of BSD if available -#if BFS_USE_SYS_PARAM_H -# include -#endif - -#ifndef __GLIBC_PREREQ -# define __GLIBC_PREREQ(maj, min) false -#endif - -#ifndef __NetBSD_Prereq__ -# define __NetBSD_Prereq__(maj, min, patch) false -#endif - -// Fundamental utilities - -/** - * Get the length of an array. - */ -#define countof(array) (sizeof(array) / sizeof(0[array])) - -/** - * False sharing/destructive interference/largest cache line size. - */ -#ifdef __GCC_DESTRUCTIVE_SIZE -# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE -#else -# define FALSE_SHARING_SIZE 64 -#endif - -/** - * True sharing/constructive interference/smallest cache line size. - */ -#ifdef __GCC_CONSTRUCTIVE_SIZE -# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE -#else -# define TRUE_SHARING_SIZE 64 -#endif - -/** - * Alignment specifier that avoids false sharing. - */ -#define cache_align alignas(FALSE_SHARING_SIZE) - -#if __COSMOPOLITAN__ -typedef long double max_align_t; -#endif - -// Wrappers for attributes - -/** - * Silence warnings about switch/case fall-throughs. - */ -#if __has_attribute(fallthrough) -# define fallthru __attribute__((fallthrough)) -#else -# define fallthru ((void)0) -#endif - -/** - * Silence warnings about unused declarations. - */ -#if __has_attribute(unused) -# define attr_maybe_unused __attribute__((unused)) -#else -# define attr_maybe_unused -#endif - -/** - * Warn if a value is unused. - */ -#if __has_attribute(warn_unused_result) -# define attr_nodiscard __attribute__((warn_unused_result)) -#else -# define attr_nodiscard -#endif - -/** - * Hint to avoid inlining a function. - */ -#if __has_attribute(noinline) -# define attr_noinline __attribute__((noinline)) -#else -# define attr_noinline -#endif - -/** - * Hint that a function is unlikely to be called. - */ -#if __has_attribute(cold) -# define attr_cold attr_noinline __attribute__((cold)) -#else -# define attr_cold attr_noinline -#endif - -/** - * Adds compiler warnings for bad printf()-style function calls, if supported. - */ -#if __has_attribute(format) -# define attr_printf(fmt, args) __attribute__((format(printf, fmt, args))) -#else -# define attr_printf(fmt, args) -#endif - -/** - * Annotates allocator-like functions. - */ -#if __has_attribute(malloc) -# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC -# define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__))) -# else -# define attr_malloc(...) attr_nodiscard __attribute__((malloc)) -# endif -#else -# define attr_malloc(...) attr_nodiscard -#endif - -/** - * Specifies that a function returns allocations with a given alignment. - */ -#if __has_attribute(alloc_align) -# define attr_alloc_align(param) __attribute__((alloc_align(param))) -#else -# define attr_alloc_align(param) -#endif - -/** - * Specifies that a function returns allocations with a given size. - */ -#if __has_attribute(alloc_size) -# define attr_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) -#else -# define attr_alloc_size(...) -#endif - -/** - * Shorthand for attr_alloc_align() and attr_alloc_size(). - */ -#define attr_aligned_alloc(align, ...) \ - attr_alloc_align(align) \ - attr_alloc_size(__VA_ARGS__) - -/** - * Check if function multiversioning via GNU indirect functions (ifunc) is supported. - */ -#ifndef BFS_USE_TARGET_CLONES -# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) -# define BFS_USE_TARGET_CLONES true -# endif -#endif - -/** - * Apply the target_clones attribute, if available. - */ -#if BFS_USE_TARGET_CLONES -# define attr_target_clones(...) __attribute__((target_clones(__VA_ARGS__))) -#else -# define attr_target_clones(...) -#endif - -/** - * Shorthand for multiple attributes at once. attr(a, b(c), d) is equivalent to - * - * attr_a - * attr_b(c) - * attr_d - */ -#define attr(...) \ - attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none, ) - -/** - * attr() helper. For exposition, pretend we support only 2 args, instead of 9. - * There are a few cases: - * - * attr() - * => attr__(attr_, none, none) - * => attr_ => - * attr_none => - * attr_too_many_none() => - * - * attr(a) - * => attr__(attr_a, none, none) - * => attr_a => __attribute__((a)) - * attr_none => - * attr_too_many_none() => - * - * attr(a, b(c)) - * => attr__(attr_a, b(c), none, none) - * => attr_a => __attribute__((a)) - * attr_b(c) => __attribute__((b(c))) - * attr_too_many_none(none) => - * - * attr(a, b(c), d) - * => attr__(attr_a, b(c), d, none, none) - * => attr_a => __attribute__((a)) - * attr_b(c) => __attribute__((b(c))) - * attr_too_many_d(none, none) => error - * - * Some attribute names are the same as standard library functions, e.g. printf. - * Standard libraries are permitted to define these functions as macros, like - * - * #define printf(...) __builtin_printf(__VA_ARGS__) - * - * The token paste in - * - * #define attr(...) attr__(attr_##__VA_ARGS__, none, none) - * - * is necessary to prevent macro expansion before evaluating attr__(). - * Otherwise, we could get - * - * attr(printf(1, 2)) - * => attr__(__builtin_printf(1, 2), none, none) - * => attr____builtin_printf(1, 2) - * => error - */ -#define attr__(a1, a2, a3, a4, a5, a6, a7, a8, a9, none, ...) \ - a1 \ - attr_##a2 \ - attr_##a3 \ - attr_##a4 \ - attr_##a5 \ - attr_##a6 \ - attr_##a7 \ - attr_##a8 \ - attr_##a9 \ - attr_too_many_##none(__VA_ARGS__) - -// Ignore `attr_none` from expanding 1-9 argument attr(a1, a2, ...) -#define attr_none -// Ignore `attr_` from expanding 0-argument attr() -#define attr_ -// Only trigger an error on more than 9 arguments -#define attr_too_many_none(...) - -#endif // BFS_CONFIG_H diff --git a/src/ctx.h b/src/ctx.h index e14db21..fc3020c 100644 --- a/src/ctx.h +++ b/src/ctx.h @@ -8,9 +8,9 @@ #ifndef BFS_CTX_H #define BFS_CTX_H +#include "prelude.h" #include "alloc.h" #include "bftw.h" -#include "config.h" #include "diag.h" #include "expr.h" #include "trie.h" diff --git a/src/diag.c b/src/diag.c index cb27b92..deb6f26 100644 --- a/src/diag.c +++ b/src/diag.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "diag.h" #include "alloc.h" #include "bfstd.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "dstring.h" #include "expr.h" diff --git a/src/diag.h b/src/diag.h index 4054c48..2b13609 100644 --- a/src/diag.h +++ b/src/diag.h @@ -8,7 +8,7 @@ #ifndef BFS_DIAG_H #define BFS_DIAG_H -#include "config.h" +#include "prelude.h" #include /** diff --git a/src/dir.c b/src/dir.c index 98518f2..53c9be3 100644 --- a/src/dir.c +++ b/src/dir.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "dir.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "trie.h" diff --git a/src/dstring.c b/src/dstring.c index 10b0fad..913dda8 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "dstring.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include #include diff --git a/src/dstring.h b/src/dstring.h index 6006199..9ea7eb9 100644 --- a/src/dstring.h +++ b/src/dstring.h @@ -8,8 +8,8 @@ #ifndef BFS_DSTRING_H #define BFS_DSTRING_H +#include "prelude.h" #include "bfstd.h" -#include "config.h" #include #include diff --git a/src/eval.c b/src/eval.c index d0112c2..b103912 100644 --- a/src/eval.c +++ b/src/eval.c @@ -5,12 +5,12 @@ * Implementation of all the primary expressions. */ +#include "prelude.h" #include "eval.h" #include "bar.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" diff --git a/src/eval.h b/src/eval.h index ae43628..4dd7996 100644 --- a/src/eval.h +++ b/src/eval.h @@ -9,7 +9,7 @@ #ifndef BFS_EVAL_H #define BFS_EVAL_H -#include "config.h" +#include "prelude.h" struct bfs_ctx; struct bfs_expr; diff --git a/src/exec.c b/src/exec.c index 60bfd28..e782d49 100644 --- a/src/exec.c +++ b/src/exec.c @@ -1,12 +1,12 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "exec.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dstring.h" diff --git a/src/expr.h b/src/expr.h index 75cb5fd..7bcace7 100644 --- a/src/expr.h +++ b/src/expr.h @@ -8,8 +8,8 @@ #ifndef BFS_EXPR_H #define BFS_EXPR_H +#include "prelude.h" #include "color.h" -#include "config.h" #include "eval.h" #include "stat.h" #include diff --git a/src/fsade.c b/src/fsade.c index 0810c7f..34a4d57 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -1,11 +1,11 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "fsade.h" #include "atomic.h" #include "bfstd.h" #include "bftw.h" -#include "config.h" #include "dir.h" #include "dstring.h" #include "sanity.h" diff --git a/src/fsade.h b/src/fsade.h index 1f1dbfc..6300852 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -9,7 +9,7 @@ #ifndef BFS_FSADE_H #define BFS_FSADE_H -#include "config.h" +#include "prelude.h" #define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H diff --git a/src/ioq.c b/src/ioq.c index b936681..189bdac 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -118,12 +118,12 @@ * [1]: https://arxiv.org/abs/2201.02179 */ +#include "prelude.h" #include "ioq.h" #include "alloc.h" #include "atomic.h" #include "bfstd.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "dir.h" #include "stat.h" diff --git a/src/ioq.h b/src/ioq.h index 818eea6..d8e1573 100644 --- a/src/ioq.h +++ b/src/ioq.h @@ -8,7 +8,7 @@ #ifndef BFS_IOQ_H #define BFS_IOQ_H -#include "config.h" +#include "prelude.h" #include "dir.h" #include "stat.h" #include diff --git a/src/main.c b/src/main.c index e120f03..9d8b206 100644 --- a/src/main.c +++ b/src/main.c @@ -26,7 +26,7 @@ * - bit.h (bit manipulation) * - bfstd.[ch] (standard library wrappers/polyfills) * - color.[ch] (for pretty terminal colors) - * - config.h (configuration and feature/platform detection) + * - prelude.h (configuration and feature/platform detection) * - diag.[ch] (formats diagnostic messages) * - dir.[ch] (a directory API facade) * - dstring.[ch] (a dynamic string library) @@ -45,8 +45,8 @@ * - xtime.[ch] (date/time handling utilities) */ +#include "prelude.h" #include "bfstd.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "eval.h" diff --git a/src/mtab.c b/src/mtab.c index 86ae151..7905d14 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "mtab.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "stat.h" #include "trie.h" #include diff --git a/src/mtab.h b/src/mtab.h index d99d78f..67290c2 100644 --- a/src/mtab.h +++ b/src/mtab.h @@ -8,7 +8,7 @@ #ifndef BFS_MTAB_H #define BFS_MTAB_H -#include "config.h" +#include "prelude.h" struct bfs_stat; diff --git a/src/opt.c b/src/opt.c index b74b4e1..ffc795b 100644 --- a/src/opt.c +++ b/src/opt.c @@ -25,11 +25,11 @@ * effects are reachable at all, skipping the traversal if not. */ +#include "prelude.h" #include "opt.h" #include "bftw.h" #include "bit.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" diff --git a/src/parse.c b/src/parse.c index a3e32fe..c2ae58f 100644 --- a/src/parse.c +++ b/src/parse.c @@ -8,12 +8,12 @@ * flags like always-true options, and skipping over paths wherever they appear. */ +#include "prelude.h" #include "parse.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" diff --git a/src/prelude.h b/src/prelude.h new file mode 100644 index 0000000..c3a0752 --- /dev/null +++ b/src/prelude.h @@ -0,0 +1,377 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +/** + * Configuration and feature/platform detection. + */ + +#ifndef BFS_PRELUDE_H +#define BFS_PRELUDE_H + +// Possible __STDC_VERSION__ values + +#define C95 199409L +#define C99 199901L +#define C11 201112L +#define C17 201710L +#define C23 202311L + +#include + +#if __STDC_VERSION__ < C23 +# include +# include +# include +#endif + +// bfs packaging configuration + +#ifndef BFS_COMMAND +# define BFS_COMMAND "bfs" +#endif +#ifndef BFS_HOMEPAGE +# define BFS_HOMEPAGE "https://tavianator.com/projects/bfs.html" +#endif + +// This is a symbol instead of a literal so we don't have to rebuild everything +// when the version number changes +extern const char bfs_version[]; + +// Check for system headers + +#ifdef __has_include + +#if __has_include() +# define BFS_HAS_MNTENT_H true +#endif +#if __has_include() +# define BFS_HAS_PATHS_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_ACL_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_CAPABILITY_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_EXTATTR_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_MKDEV_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_PARAM_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_SYSMACROS_H true +#endif +#if __has_include() +# define BFS_HAS_SYS_XATTR_H true +#endif +#if __has_include() +# define BFS_HAS_THREADS_H true +#endif +#if __has_include() +# define BFS_HAS_UTIL_H true +#endif + +#else // !__has_include + +#define BFS_HAS_MNTENT_H __GLIBC__ +#define BFS_HAS_PATHS_H true +#define BFS_HAS_SYS_ACL_H true +#define BFS_HAS_SYS_CAPABILITY_H __linux__ +#define BFS_HAS_SYS_EXTATTR_H __FreeBSD__ +#define BFS_HAS_SYS_MKDEV_H false +#define BFS_HAS_SYS_PARAM_H true +#define BFS_HAS_SYS_SYSMACROS_H __GLIBC__ +#define BFS_HAS_SYS_XATTR_H __linux__ +#define BFS_HAS_THREADS_H (!__STDC_NO_THREADS__) +#define BFS_HAS_UTIL_H __NetBSD__ + +#endif // !__has_include + +#ifndef BFS_USE_MNTENT_H +# define BFS_USE_MNTENT_H BFS_HAS_MNTENT_H +#endif +#ifndef BFS_USE_PATHS_H +# define BFS_USE_PATHS_H BFS_HAS_PATHS_H +#endif +#ifndef BFS_USE_SYS_ACL_H +# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL)) +#endif +#ifndef BFS_USE_SYS_CAPABILITY_H +# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP)) +#endif +#ifndef BFS_USE_SYS_EXTATTR_H +# define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__) +#endif +#ifndef BFS_USE_SYS_MKDEV_H +# define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H +#endif +#ifndef BFS_USE_SYS_PARAM_H +# define BFS_USE_SYS_PARAM_H BFS_HAS_SYS_PARAM_H +#endif +#ifndef BFS_USE_SYS_SYSMACROS_H +# define BFS_USE_SYS_SYSMACROS_H BFS_HAS_SYS_SYSMACROS_H +#endif +#ifndef BFS_USE_SYS_XATTR_H +# define BFS_USE_SYS_XATTR_H BFS_HAS_SYS_XATTR_H +#endif +#ifndef BFS_USE_THREADS_H +# define BFS_USE_THREADS_H BFS_HAS_THREADS_H +#endif +#ifndef BFS_USE_UTIL_H +# define BFS_USE_UTIL_H BFS_HAS_UTIL_H +#endif + +// Stub out feature detection on old/incompatible compilers + +#ifndef __has_feature +# define __has_feature(feat) false +#endif + +#ifndef __has_c_attribute +# define __has_c_attribute(attr) false +#endif + +#ifndef __has_attribute +# define __has_attribute(attr) false +#endif + +// Platform detection + +// Get the definition of BSD if available +#if BFS_USE_SYS_PARAM_H +# include +#endif + +#ifndef __GLIBC_PREREQ +# define __GLIBC_PREREQ(maj, min) false +#endif + +#ifndef __NetBSD_Prereq__ +# define __NetBSD_Prereq__(maj, min, patch) false +#endif + +// Fundamental utilities + +/** + * Get the length of an array. + */ +#define countof(array) (sizeof(array) / sizeof(0[array])) + +/** + * False sharing/destructive interference/largest cache line size. + */ +#ifdef __GCC_DESTRUCTIVE_SIZE +# define FALSE_SHARING_SIZE __GCC_DESTRUCTIVE_SIZE +#else +# define FALSE_SHARING_SIZE 64 +#endif + +/** + * True sharing/constructive interference/smallest cache line size. + */ +#ifdef __GCC_CONSTRUCTIVE_SIZE +# define TRUE_SHARING_SIZE __GCC_CONSTRUCTIVE_SIZE +#else +# define TRUE_SHARING_SIZE 64 +#endif + +/** + * Alignment specifier that avoids false sharing. + */ +#define cache_align alignas(FALSE_SHARING_SIZE) + +#if __COSMOPOLITAN__ +typedef long double max_align_t; +#endif + +// Wrappers for attributes + +/** + * Silence warnings about switch/case fall-throughs. + */ +#if __has_attribute(fallthrough) +# define fallthru __attribute__((fallthrough)) +#else +# define fallthru ((void)0) +#endif + +/** + * Silence warnings about unused declarations. + */ +#if __has_attribute(unused) +# define attr_maybe_unused __attribute__((unused)) +#else +# define attr_maybe_unused +#endif + +/** + * Warn if a value is unused. + */ +#if __has_attribute(warn_unused_result) +# define attr_nodiscard __attribute__((warn_unused_result)) +#else +# define attr_nodiscard +#endif + +/** + * Hint to avoid inlining a function. + */ +#if __has_attribute(noinline) +# define attr_noinline __attribute__((noinline)) +#else +# define attr_noinline +#endif + +/** + * Hint that a function is unlikely to be called. + */ +#if __has_attribute(cold) +# define attr_cold attr_noinline __attribute__((cold)) +#else +# define attr_cold attr_noinline +#endif + +/** + * Adds compiler warnings for bad printf()-style function calls, if supported. + */ +#if __has_attribute(format) +# define attr_printf(fmt, args) __attribute__((format(printf, fmt, args))) +#else +# define attr_printf(fmt, args) +#endif + +/** + * Annotates allocator-like functions. + */ +#if __has_attribute(malloc) +# if __GNUC__ >= 11 && !__OPTIMIZE__ // malloc(deallocator) disables inlining on GCC +# define attr_malloc(...) attr_nodiscard __attribute__((malloc(__VA_ARGS__))) +# else +# define attr_malloc(...) attr_nodiscard __attribute__((malloc)) +# endif +#else +# define attr_malloc(...) attr_nodiscard +#endif + +/** + * Specifies that a function returns allocations with a given alignment. + */ +#if __has_attribute(alloc_align) +# define attr_alloc_align(param) __attribute__((alloc_align(param))) +#else +# define attr_alloc_align(param) +#endif + +/** + * Specifies that a function returns allocations with a given size. + */ +#if __has_attribute(alloc_size) +# define attr_alloc_size(...) __attribute__((alloc_size(__VA_ARGS__))) +#else +# define attr_alloc_size(...) +#endif + +/** + * Shorthand for attr_alloc_align() and attr_alloc_size(). + */ +#define attr_aligned_alloc(align, ...) \ + attr_alloc_align(align) \ + attr_alloc_size(__VA_ARGS__) + +/** + * Check if function multiversioning via GNU indirect functions (ifunc) is supported. + */ +#ifndef BFS_USE_TARGET_CLONES +# if __has_attribute(target_clones) && (__GLIBC__ || __FreeBSD__) +# define BFS_USE_TARGET_CLONES true +# endif +#endif + +/** + * Apply the target_clones attribute, if available. + */ +#if BFS_USE_TARGET_CLONES +# define attr_target_clones(...) __attribute__((target_clones(__VA_ARGS__))) +#else +# define attr_target_clones(...) +#endif + +/** + * Shorthand for multiple attributes at once. attr(a, b(c), d) is equivalent to + * + * attr_a + * attr_b(c) + * attr_d + */ +#define attr(...) \ + attr__(attr_##__VA_ARGS__, none, none, none, none, none, none, none, none, none, ) + +/** + * attr() helper. For exposition, pretend we support only 2 args, instead of 9. + * There are a few cases: + * + * attr() + * => attr__(attr_, none, none) + * => attr_ => + * attr_none => + * attr_too_many_none() => + * + * attr(a) + * => attr__(attr_a, none, none) + * => attr_a => __attribute__((a)) + * attr_none => + * attr_too_many_none() => + * + * attr(a, b(c)) + * => attr__(attr_a, b(c), none, none) + * => attr_a => __attribute__((a)) + * attr_b(c) => __attribute__((b(c))) + * attr_too_many_none(none) => + * + * attr(a, b(c), d) + * => attr__(attr_a, b(c), d, none, none) + * => attr_a => __attribute__((a)) + * attr_b(c) => __attribute__((b(c))) + * attr_too_many_d(none, none) => error + * + * Some attribute names are the same as standard library functions, e.g. printf. + * Standard libraries are permitted to define these functions as macros, like + * + * #define printf(...) __builtin_printf(__VA_ARGS__) + * + * The token paste in + * + * #define attr(...) attr__(attr_##__VA_ARGS__, none, none) + * + * is necessary to prevent macro expansion before evaluating attr__(). + * Otherwise, we could get + * + * attr(printf(1, 2)) + * => attr__(__builtin_printf(1, 2), none, none) + * => attr____builtin_printf(1, 2) + * => error + */ +#define attr__(a1, a2, a3, a4, a5, a6, a7, a8, a9, none, ...) \ + a1 \ + attr_##a2 \ + attr_##a3 \ + attr_##a4 \ + attr_##a5 \ + attr_##a6 \ + attr_##a7 \ + attr_##a8 \ + attr_##a9 \ + attr_too_many_##none(__VA_ARGS__) + +// Ignore `attr_none` from expanding 1-9 argument attr(a1, a2, ...) +#define attr_none +// Ignore `attr_` from expanding 0-argument attr() +#define attr_ +// Only trigger an error on more than 9 arguments +#define attr_too_many_none(...) + +#endif // BFS_PRELUDE_H diff --git a/src/printf.c b/src/printf.c index 3b8269e..4df399b 100644 --- a/src/printf.c +++ b/src/printf.c @@ -1,12 +1,12 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "printf.h" #include "alloc.h" #include "bfstd.h" #include "bftw.h" #include "color.h" -#include "config.h" #include "ctx.h" #include "diag.h" #include "dir.h" diff --git a/src/pwcache.c b/src/pwcache.c index 79437d8..af8c237 100644 --- a/src/pwcache.c +++ b/src/pwcache.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "pwcache.h" #include "alloc.h" -#include "config.h" #include "trie.h" #include #include diff --git a/src/sanity.h b/src/sanity.h index 423e6ff..e168b8f 100644 --- a/src/sanity.h +++ b/src/sanity.h @@ -8,7 +8,7 @@ #ifndef BFS_SANITY_H #define BFS_SANITY_H -#include "config.h" +#include "prelude.h" #include #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) diff --git a/src/stat.c b/src/stat.c index 2f2743b..eca5bab 100644 --- a/src/stat.c +++ b/src/stat.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "stat.h" #include "atomic.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include diff --git a/src/stat.h b/src/stat.h index 856a2ca..1fdd263 100644 --- a/src/stat.h +++ b/src/stat.h @@ -12,7 +12,7 @@ #ifndef BFS_STAT_H #define BFS_STAT_H -#include "config.h" +#include "prelude.h" #include #include #include diff --git a/src/thread.c b/src/thread.c index 200d8c3..3793896 100644 --- a/src/thread.c +++ b/src/thread.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "thread.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include #include diff --git a/src/thread.h b/src/thread.h index 8174fe4..db11bd8 100644 --- a/src/thread.h +++ b/src/thread.h @@ -8,7 +8,7 @@ #ifndef BFS_THREAD_H #define BFS_THREAD_H -#include "config.h" +#include "prelude.h" #include #if __STDC_VERSION__ < C23 && !defined(thread_local) diff --git a/src/trie.c b/src/trie.c index f275064..808953e 100644 --- a/src/trie.c +++ b/src/trie.c @@ -81,10 +81,10 @@ * and insert intermediate singleton "jump" nodes when necessary. */ +#include "prelude.h" #include "trie.h" #include "alloc.h" #include "bit.h" -#include "config.h" #include "diag.h" #include "list.h" #include diff --git a/src/xregex.c b/src/xregex.c index 3df27f0..c2711bc 100644 --- a/src/xregex.c +++ b/src/xregex.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xregex.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "sanity.h" #include "thread.h" diff --git a/src/xspawn.c b/src/xspawn.c index 347625d..113d7ec 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xspawn.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "list.h" #include #include diff --git a/src/xspawn.h b/src/xspawn.h index a20cbd0..6a8f54a 100644 --- a/src/xspawn.h +++ b/src/xspawn.h @@ -8,7 +8,7 @@ #ifndef BFS_XSPAWN_H #define BFS_XSPAWN_H -#include "config.h" +#include "prelude.h" #include #include #include diff --git a/src/xtime.c b/src/xtime.c index bcf6dd3..91ed915 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "xtime.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/alloc.c b/tests/alloc.c index 54b84ba..6c0defd 100644 --- a/tests/alloc.c +++ b/tests/alloc.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "alloc.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/bfstd.c b/tests/bfstd.c index 5e408ca..07b68b0 100644 --- a/tests/bfstd.c +++ b/tests/bfstd.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/bit.c b/tests/bit.c index b444e50..674d1b2 100644 --- a/tests/bit.c +++ b/tests/bit.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "bit.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/ioq.c b/tests/ioq.c index a69f2bf..ef5ee3b 100644 --- a/tests/ioq.c +++ b/tests/ioq.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "ioq.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include "dir.h" #include diff --git a/tests/main.c b/tests/main.c index 281c417..429772b 100644 --- a/tests/main.c +++ b/tests/main.c @@ -5,10 +5,10 @@ * Entry point for unit tests. */ +#include "prelude.h" #include "tests.h" #include "bfstd.h" #include "color.h" -#include "config.h" #include #include #include diff --git a/tests/tests.h b/tests/tests.h index d61ffd7..9078938 100644 --- a/tests/tests.h +++ b/tests/tests.h @@ -8,7 +8,7 @@ #ifndef BFS_TESTS_H #define BFS_TESTS_H -#include "config.h" +#include "prelude.h" #include "diag.h" /** Unit test function type. */ diff --git a/tests/trie.c b/tests/trie.c index 2a6eb48..4667322 100644 --- a/tests/trie.c +++ b/tests/trie.c @@ -1,9 +1,9 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "trie.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/xspawn.c b/tests/xspawn.c index fd8362e..7362aa5 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "alloc.h" #include "bfstd.h" -#include "config.h" #include "dstring.h" #include "xspawn.h" #include diff --git a/tests/xtime.c b/tests/xtime.c index fd7aa0f..a7c63d2 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -1,10 +1,10 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "tests.h" #include "xtime.h" #include "bfstd.h" -#include "config.h" #include "diag.h" #include #include diff --git a/tests/xtouch.c b/tests/xtouch.c index b1daec7..82d749d 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -1,8 +1,8 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include "prelude.h" #include "bfstd.h" -#include "config.h" #include "sanity.h" #include "xtime.h" #include -- cgit v1.2.3 From 1380981aa27463879faa7652a2e0a79419243cec Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 15:24:49 -0400 Subject: config: Add missing copyright headers They're probably too trivial to be copyrightable, but might as well include the SPDX tags for consistency anyway. --- config/libacl.c | 3 +++ config/libcap.c | 3 +++ config/libselinux.c | 3 +++ config/liburing.c | 3 +++ config/oniguruma.c | 3 +++ 5 files changed, 15 insertions(+) diff --git a/config/libacl.c b/config/libacl.c index 877cb69..de1fe50 100644 --- a/config/libacl.c +++ b/config/libacl.c @@ -1,3 +1,6 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + #include int main(void) { diff --git a/config/libcap.c b/config/libcap.c index 64188ac..58e832c 100644 --- a/config/libcap.c +++ b/config/libcap.c @@ -1,3 +1,6 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + #include int main(void) { diff --git a/config/libselinux.c b/config/libselinux.c index 72f5d33..bca409d 100644 --- a/config/libselinux.c +++ b/config/libselinux.c @@ -1,3 +1,6 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + #include int main(void) { diff --git a/config/liburing.c b/config/liburing.c index 456059c..bea499a 100644 --- a/config/liburing.c +++ b/config/liburing.c @@ -1,3 +1,6 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + #include int main(void) { diff --git a/config/oniguruma.c b/config/oniguruma.c index b834fac..cb17596 100644 --- a/config/oniguruma.c +++ b/config/oniguruma.c @@ -1,3 +1,6 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + #include int main(void) { -- cgit v1.2.3 From d7d5e1c474e4e110172b7180de9c41e5ebbf93f6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 16:16:36 -0400 Subject: config: Test-compile packages even if pkg-config says they exist This fixes `make config CC=musl-gcc`, for example. --- config/pkgconf.sh | 104 +++++++++++++++++++++++++++--------------------------- config/pkgs.mk | 6 ++-- 2 files changed, 55 insertions(+), 55 deletions(-) diff --git a/config/pkgconf.sh b/config/pkgconf.sh index d6c45c7..2dbad76 100755 --- a/config/pkgconf.sh +++ b/config/pkgconf.sh @@ -23,66 +23,66 @@ case "$XNOLIBS" in exit 1 esac +if [ -z "$MODE" ]; then + # Check whether the libraries exist at all + for LIB; do + CFLAGS=$("$0" --cflags "$LIB") || exit 1 + LDFLAGS=$("$0" --ldflags "$LIB") || exit 1 + LDLIBS=$("$0" --ldlibs "$LIB") || exit 1 + config/cc.sh $CFLAGS $LDFLAGS config/$LIB.c $LDLIBS || exit 1 + done +fi + +# Defer to pkg-config if possible if command -v "${XPKG_CONFIG:-}" >/dev/null 2>&1; then case "$MODE" in - "") - "$XPKG_CONFIG" "$@" - ;; --cflags) - OUT=$("$XPKG_CONFIG" --cflags "$@") - if [ "$OUT" ]; then - printf 'CFLAGS += %s\n' "$OUT" - fi + "$XPKG_CONFIG" --cflags "$@" ;; --ldflags) - OUT=$("$XPKG_CONFIG" --libs-only-L --libs-only-other "$@") - if [ "$OUT" ]; then - printf 'LDFLAGS += %s\n' "$OUT" - fi + "$XPKG_CONFIG" --libs-only-L --libs-only-other "$@" ;; --ldlibs) - OUT=$("$XPKG_CONFIG" --libs-only-l "$@") - if [ "$OUT" ]; then - printf 'LDLIBS := %s ${LDLIBS}\n' "$OUT" - fi + "$XPKG_CONFIG" --libs-only-l "$@" ;; esac -else - LDLIBS="" - for LIB; do - case "$LIB" in - libacl) - LDLIB=-lacl - ;; - libcap) - LDLIB=-lcap - ;; - libselinux) - LDLIB=-lselinux - ;; - liburing) - LDLIB=-luring - ;; - oniguruma) - LDLIB=-lonig - ;; - *) - printf 'error: Unknown package %s\n' "$LIB" >&2 - exit 1 - ;; - esac - case "$MODE" in - "") - config/cc.sh "config/$LIB.c" "$LDLIB" || exit $? - ;; - --ldlibs) - LDLIBS="$LDLIBS $LDLIB" - ;; - esac - done - - if [ "$MODE" = "--ldlibs" ] && [ "$LDLIBS" ]; then - printf 'LDLIBS :=%s ${LDLIBS}\n' "$LDLIBS" - fi + exit fi + +# pkg-config unavailable, emulate it ourselves +CFLAGS="" +LDFLAGS="" +LDLIBS="" + +for LIB; do + case "$LIB" in + libacl) + LDLIB=-lacl + ;; + libcap) + LDLIB=-lcap + ;; + libselinux) + LDLIB=-lselinux + ;; + liburing) + LDLIB=-luring + ;; + oniguruma) + LDLIB=-lonig + ;; + *) + printf 'error: Unknown package %s\n' "$LIB" >&2 + exit 1 + ;; + esac + + LDLIBS="$LDLIBS$LDLIB " +done + +case "$MODE" in + --ldlibs) + printf '%s\n' "$LDLIBS" + ;; +esac diff --git a/config/pkgs.mk b/config/pkgs.mk index de9e16e..3a18289 100644 --- a/config/pkgs.mk +++ b/config/pkgs.mk @@ -11,7 +11,7 @@ include config/exports.mk ${GEN}/pkgs.mk:: ${MSG} "[ GEN] ${TGT}" - config/pkgconf.sh --cflags ${PKGS} >>$@ 2>>$@.log - config/pkgconf.sh --ldflags ${PKGS} >>$@ 2>>$@.log - config/pkgconf.sh --ldlibs ${PKGS} >>$@ 2>>$@.log + printf 'CFLAGS += %s\n' "$$(config/pkgconf.sh --cflags ${PKGS})" >>$@ 2>>$@.log + printf 'LDFLAGS += %s\n' "$$(config/pkgconf.sh --ldflags ${PKGS})" >>$@ 2>>$@.log + printf 'LDLIBS := %s $${LDLIBS}\n' "$$(config/pkgconf.sh --ldlibs ${PKGS})" >>$@ 2>>$@.log ${VCAT} $@ -- cgit v1.2.3 From 0035cc4ff492cab91adeb49d0c577fe5982064bd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 15:27:27 -0400 Subject: config: Check for program_invocation_short_name This lets us pick it up on musl too, since there's no __MUSL__ macro. Link: https://wiki.musl-libc.org/faq#Q:-Why-is-there-no-%3Ccode%3E__MUSL__%3C/code%3E-macro? --- config/cc-define.sh | 19 +++++++++++++++++++ config/config.mk | 11 ++++++++++- config/flags.mk | 1 + config/getprogname-gnu.c | 9 +++++++++ config/getprogname.c | 9 +++++++++ config/header.mk | 38 ++++++++++++++++++++++++++++++++++++++ config/pkg.mk | 4 ++-- config/prelude.mk | 3 +++ src/bfstd.c | 6 +++--- src/prelude.h | 2 ++ 10 files changed, 96 insertions(+), 6 deletions(-) create mode 100755 config/cc-define.sh create mode 100644 config/getprogname-gnu.c create mode 100644 config/getprogname.c create mode 100644 config/header.mk diff --git a/config/cc-define.sh b/config/cc-define.sh new file mode 100755 index 0000000..edb5c87 --- /dev/null +++ b/config/cc-define.sh @@ -0,0 +1,19 @@ +#!/bin/sh + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Output a C preprocessor definition based on whether a C source file could be +# compiled successfully + +set -eu + +SLUG="${1#config/}" +SLUG="${SLUG%.c}" +MACRO="BFS_HAS_$(printf '%s' "$SLUG" | tr 'a-z-' 'A-Z_')" + +if config/cc.sh "$1"; then + printf '#define %s true\n' "$MACRO" +else + printf '#define %s false\n' "$MACRO" +fi diff --git a/config/config.mk b/config/config.mk index 5a6f8d2..5750b49 100644 --- a/config/config.mk +++ b/config/config.mk @@ -6,6 +6,10 @@ include config/prelude.mk include config/exports.mk +# All configuration steps +config: ${CONFIG} ${GEN}/config.h +.PHONY: config + # Makefile fragments generated by `make config` MKS := \ ${GEN}/vars.mk \ @@ -17,7 +21,7 @@ MKS := \ ${CONFIG}: ${MKS} ${MSG} "[ GEN] ${TGT}" @printf '# %s\n' "${TGT}" >$@ - @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@ + @printf 'include $${GEN}/%s\n' ${MKS:${GEN}/%=%} >>$@ ${VCAT} ${CONFIG} .PHONY: ${CONFIG} @@ -67,3 +71,8 @@ ${GEN}/pkgs.mk: ${PKG_MKS} ${PKG_MKS}: ${GEN}/flags.mk @+${MAKE} -sf config/pkg.mk TARGET=$@ .PHONY: ${PKG_MKS} + +# Compile-time feature detection +${GEN}/config.h: ${CONFIG} + @+${MAKE} -sf config/header.mk $@ +.PHONY: ${GEN}/config.h diff --git a/config/flags.mk b/config/flags.mk index d02cc8b..9959d10 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -29,6 +29,7 @@ export XLDLIBS=${LDLIBS} # Immutable flags export BFS_CPPFLAGS= \ -Isrc \ + -I${GEN} \ -D__EXTENSIONS__ \ -D_ATFILE_SOURCE \ -D_BSD_SOURCE \ diff --git a/config/getprogname-gnu.c b/config/getprogname-gnu.c new file mode 100644 index 0000000..6b97c5e --- /dev/null +++ b/config/getprogname-gnu.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + const char *str = program_invocation_short_name; + return str[0]; +} diff --git a/config/getprogname.c b/config/getprogname.c new file mode 100644 index 0000000..83dc8e8 --- /dev/null +++ b/config/getprogname.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + const char *str = getprogname(); + return str[0]; +} diff --git a/config/header.mk b/config/header.mk new file mode 100644 index 0000000..3a1271e --- /dev/null +++ b/config/header.mk @@ -0,0 +1,38 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/config.h + +include config/prelude.mk +include ${GEN}/config.mk +include config/exports.mk + +# All header fragments we generate +HEADERS := \ + ${GEN}/getprogname.h \ + ${GEN}/getprogname-gnu.h + +${GEN}/config.h: ${HEADERS} + ${MSG} "[ GEN] ${TGT}" + printf '// %s\n' "${TGT}" >$@ + printf '#ifndef BFS_CONFIG_H\n' >>$@ + printf '#define BFS_CONFIG_H\n' >>$@ + cat ${.ALLSRC} >>$@ + printf '#endif // BFS_CONFIG_H\n' >>$@ + cat ${.ALLSRC:%=%.log} >$@.log + ${RM} ${.ALLSRC} ${.ALLSRC:%=%.log} + ${VCAT} $@ +.PHONY: ${GEN}/config.h + +# The C source file to attempt to compile +CSRC = ${@:${GEN}/%.h=config/%.c} + +${HEADERS}:: + config/cc-define.sh ${CSRC} >$@ 2>$@.log + if ! [ "${IS_V}" ]; then \ + if grep -q 'true$$' $@; then \ + printf '[ CC ] %-${MSG_WIDTH}s ✔\n' ${CSRC}; \ + else \ + printf '[ CC ] %-${MSG_WIDTH}s ✘\n' ${CSRC}; \ + fi; \ + fi diff --git a/config/pkg.mk b/config/pkg.mk index 6f868be..fafe562 100644 --- a/config/pkg.mk +++ b/config/pkg.mk @@ -17,7 +17,7 @@ default:: @if [ "${IS_V}" ]; then \ cat ${TARGET}; \ elif grep -q PKGS ${TARGET}; then \ - printf '[ GEN] %-18s ✔\n' ${SHORT}; \ + printf '[ GEN] %-${MSG_WIDTH}s ✔\n' ${SHORT}; \ else \ - printf '[ GEN] %-18s ✘\n' ${SHORT}; \ + printf '[ GEN] %-${MSG_WIDTH}s ✘\n' ${SHORT}; \ fi diff --git a/config/prelude.mk b/config/prelude.mk index dbc6875..0ac5fde 100644 --- a/config/prelude.mk +++ b/config/prelude.mk @@ -109,6 +109,9 @@ MSG = @msg() { \ }; \ msg +# Maximum width of a short message, to align the [X] +MSG_WIDTH := 24 + # cat a file if V=1 VCAT,y := @cat VCAT, := @: diff --git a/src/bfstd.c b/src/bfstd.c index e1b4804..e8a927a 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -186,10 +186,10 @@ char *xgetdelim(FILE *file, char delim) { const char *xgetprogname(void) { const char *cmd = NULL; -#if __GLIBC__ - cmd = program_invocation_short_name; -#elif BSD +#if BFS_HAS_GETPROGNAME cmd = getprogname(); +#elif BFS_HAS_GETPROGNAME_GNU + cmd = program_invocation_short_name; #endif if (!cmd) { diff --git a/src/prelude.h b/src/prelude.h index c3a0752..a23b167 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -26,6 +26,8 @@ // bfs packaging configuration +#include "config.h" + #ifndef BFS_COMMAND # define BFS_COMMAND "bfs" #endif -- cgit v1.2.3 From 29ddac2bf64bb305b285db86015abebe8a0bd8b3 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 15:34:33 -0400 Subject: config: Check for posix_spawn_file_actions_addfchdir{,_np}() --- config/header.mk | 4 +++- config/posix-spawn-addfchdir-np.c | 11 +++++++++++ config/posix-spawn-addfchdir.c | 11 +++++++++++ config/prelude.mk | 2 +- src/xspawn.c | 26 ++++++-------------------- 5 files changed, 32 insertions(+), 22 deletions(-) create mode 100644 config/posix-spawn-addfchdir-np.c create mode 100644 config/posix-spawn-addfchdir.c diff --git a/config/header.mk b/config/header.mk index 3a1271e..8867200 100644 --- a/config/header.mk +++ b/config/header.mk @@ -10,7 +10,9 @@ include config/exports.mk # All header fragments we generate HEADERS := \ ${GEN}/getprogname.h \ - ${GEN}/getprogname-gnu.h + ${GEN}/getprogname-gnu.h \ + ${GEN}/posix-spawn-addfchdir.h \ + ${GEN}/posix-spawn-addfchdir-np.h ${GEN}/config.h: ${HEADERS} ${MSG} "[ GEN] ${TGT}" diff --git a/config/posix-spawn-addfchdir-np.c b/config/posix-spawn-addfchdir-np.c new file mode 100644 index 0000000..b870a53 --- /dev/null +++ b/config/posix-spawn-addfchdir-np.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addfchdir_np(&actions, 3); + return 0; +} diff --git a/config/posix-spawn-addfchdir.c b/config/posix-spawn-addfchdir.c new file mode 100644 index 0000000..c52ff81 --- /dev/null +++ b/config/posix-spawn-addfchdir.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addfchdir(&actions, 3); + return 0; +} diff --git a/config/prelude.mk b/config/prelude.mk index 0ac5fde..b9bc61b 100644 --- a/config/prelude.mk +++ b/config/prelude.mk @@ -110,7 +110,7 @@ MSG = @msg() { \ msg # Maximum width of a short message, to align the [X] -MSG_WIDTH := 24 +MSG_WIDTH := 33 # cat a file if V=1 VCAT,y := @cat diff --git a/src/xspawn.c b/src/xspawn.c index 113d7ec..0b0cea4 100644 --- a/src/xspawn.c +++ b/src/xspawn.c @@ -212,7 +212,7 @@ int bfs_spawn_adddup2(struct bfs_spawn *ctx, int oldfd, int newfd) { * but macOS and NetBSD resolve the PATH *before* file_actions (because there * posix_spawn() is its own syscall). */ -#define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD_Prereq__(10, 0, 0)) +#define BFS_POSIX_SPAWNP_AFTER_FCHDIR !(__APPLE__ || __NetBSD__) int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { struct bfs_spawn_action *action = bfs_spawn_action(BFS_SPAWN_FCHDIR); @@ -220,29 +220,15 @@ int bfs_spawn_addfchdir(struct bfs_spawn *ctx, int fd) { return -1; } -#ifndef BFS_HAS_POSIX_SPAWN_FCHDIR -# define BFS_HAS_POSIX_SPAWN_FCHDIR __NetBSD_Prereq__(10, 0, 0) -#endif - -#ifndef BFS_HAS_POSIX_SPAWN_FCHDIR_NP -# if __GLIBC__ -# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP __GLIBC_PREREQ(2, 29) -# elif __ANDROID__ -# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP (__ANDROID_API__ >= 34) -# else -# define BFS_HAS_POSIX_SPAWN_FCHDIR_NP (__linux__ || __FreeBSD__ || __APPLE__) -# endif -#endif - -#if BFS_HAS_POSIX_SPAWN_FCHDIR -# define BFS_POSIX_SPAWN_FCHDIR posix_spawn_file_actions_addfchdir -#elif BFS_HAS_POSIX_SPAWN_FCHDIR_NP -# define BFS_POSIX_SPAWN_FCHDIR posix_spawn_file_actions_addfchdir_np +#if BFS_HAS_POSIX_SPAWN_ADDFCHDIR +# define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir +#elif BFS_HAS_POSIX_SPAWN_ADDFCHDIR_NP +# define BFS_POSIX_SPAWN_ADDFCHDIR posix_spawn_file_actions_addfchdir_np #endif #if _POSIX_SPAWN > 0 && defined(BFS_POSIX_SPAWN_FCHDIR) if (ctx->flags & BFS_SPAWN_USE_POSIX) { - errno = BFS_POSIX_SPAWN_FCHDIR(&ctx->actions, fd); + errno = BFS_POSIX_SPAWN_ADDFCHDIR(&ctx->actions, fd); if (errno != 0) { free(action); return -1; -- cgit v1.2.3 From 1207086b5a6341412c647480eea68c870b18bba1 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 16:16:18 -0400 Subject: config: Check for getdents{,64}() --- config/flags.mk | 1 - config/getdents.c | 10 ++++++++++ config/getdents64-syscall.c | 12 ++++++++++++ config/getdents64.c | 10 ++++++++++ config/header.mk | 3 +++ src/dir.c | 14 ++++++++------ src/dir.h | 4 ++-- 7 files changed, 45 insertions(+), 9 deletions(-) create mode 100644 config/getdents.c create mode 100644 config/getdents64-syscall.c create mode 100644 config/getdents64.c diff --git a/config/flags.mk b/config/flags.mk index 9959d10..8a2e50e 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -36,7 +36,6 @@ export BFS_CPPFLAGS= \ -D_DARWIN_C_SOURCE \ -D_DEFAULT_SOURCE \ -D_GNU_SOURCE \ - -D_LARGEFILE64_SOURCE \ -D_POSIX_PTHREAD_SEMANTICS \ -D_FILE_OFFSET_BITS=64 \ -D_TIME_BITS=64 diff --git a/config/getdents.c b/config/getdents.c new file mode 100644 index 0000000..d0d4228 --- /dev/null +++ b/config/getdents.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct dirent de; + getdents(3, &de, 1024); + return 0; +} diff --git a/config/getdents64-syscall.c b/config/getdents64-syscall.c new file mode 100644 index 0000000..4838c14 --- /dev/null +++ b/config/getdents64-syscall.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + struct dirent64 de; + syscall(SYS_getdents64, 3, &de, 1024); + return 0; +} diff --git a/config/getdents64.c b/config/getdents64.c new file mode 100644 index 0000000..1abf36d --- /dev/null +++ b/config/getdents64.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct dirent64 de; + getdents64(3, &de, 1024); + return 0; +} diff --git a/config/header.mk b/config/header.mk index 8867200..cc4ae05 100644 --- a/config/header.mk +++ b/config/header.mk @@ -9,6 +9,9 @@ include config/exports.mk # All header fragments we generate HEADERS := \ + ${GEN}/getdents.h \ + ${GEN}/getdents64.h \ + ${GEN}/getdents64-syscall.h \ ${GEN}/getprogname.h \ ${GEN}/getprogname-gnu.h \ ${GEN}/posix-spawn-addfchdir.h \ diff --git a/src/dir.c b/src/dir.c index 53c9be3..8b1bee0 100644 --- a/src/dir.c +++ b/src/dir.c @@ -17,7 +17,7 @@ #include #if BFS_USE_GETDENTS -# if __linux__ +# if BFS_HAS_GETDENTS64_SYSCALL # include # endif @@ -25,12 +25,14 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { sanitize_uninit(buf, size); -#if (__linux__ && __GLIBC__ && !__GLIBC_PREREQ(2, 30)) || __ANDROID__ - ssize_t ret = syscall(SYS_getdents64, fd, buf, size); -#elif __linux__ +#if BFS_HAS_GETDENTS + ssize_t ret = getdents(fd, buf, size); +#elif BFS_HAS_GETDENTS64 ssize_t ret = getdents64(fd, buf, size); +#elif BFS_HAS_GETDENTS64_SYSCALL + ssize_t ret = syscall(SYS_getdents64, fd, buf, size); #else - ssize_t ret = getdents(fd, buf, size); +# error "No getdents() implementation" #endif if (ret > 0) { @@ -42,7 +44,7 @@ static ssize_t bfs_getdents(int fd, void *buf, size_t size) { #endif // BFS_USE_GETDENTS -#if BFS_USE_GETDENTS && __linux__ +#if BFS_USE_GETDENTS && !BFS_HAS_GETDENTS /** Directory entry type for bfs_getdents() */ typedef struct dirent64 sys_dirent; #else diff --git a/src/dir.h b/src/dir.h index 18d907e..19dfa98 100644 --- a/src/dir.h +++ b/src/dir.h @@ -14,8 +14,8 @@ * Whether the implementation uses the getdents() syscall directly, rather than * libc's readdir(). */ -#ifndef BFS_USE_GETDENTS -# define BFS_USE_GETDENTS (__linux__ || __FreeBSD__) +#if !defined(BFS_USE_GETDENTS) && (__linux__ || __FreeBSD__) +# define BFS_USE_GETDENTS (BFS_HAS_GETDENTS || BFS_HAS_GETDENTS64 | BFS_HAS_GETDENTS64_SYSCALL) #endif /** -- cgit v1.2.3 From 536075930140c7886c61997be31c41d651f48818 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 16:24:56 -0400 Subject: config: Check for confstr() --- config/confstr.c | 9 +++++++++ config/header.mk | 1 + src/bfstd.c | 10 +++++----- 3 files changed, 15 insertions(+), 5 deletions(-) create mode 100644 config/confstr.c diff --git a/config/confstr.c b/config/confstr.c new file mode 100644 index 0000000..58280b4 --- /dev/null +++ b/config/confstr.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + confstr(_CS_PATH, NULL, 0); + return 0; +} diff --git a/config/header.mk b/config/header.mk index cc4ae05..b6915d5 100644 --- a/config/header.mk +++ b/config/header.mk @@ -9,6 +9,7 @@ include config/exports.mk # All header fragments we generate HEADERS := \ + ${GEN}/confstr.h \ ${GEN}/getdents.h \ ${GEN}/getdents64.h \ ${GEN}/getdents64-syscall.h \ diff --git a/src/bfstd.c b/src/bfstd.c index e8a927a..3eff024 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -581,10 +581,7 @@ int xfaccessat(int fd, const char *path, int amode) { } char *xconfstr(int name) { -#if __ANDROID__ - errno = ENOTSUP; - return NULL; -#else +#if BFS_HAS_CONFSTR size_t len = confstr(name, NULL, 0); if (len == 0) { return NULL; @@ -601,7 +598,10 @@ char *xconfstr(int name) { } return str; -#endif // !__ANDROID__ +#else + errno = ENOTSUP; + return NULL; +#endif } char *xreadlinkat(int fd, const char *path, size_t size) { -- cgit v1.2.3 From 2d4ff1dcd044c09dd6368b6726d53b4f3afcd546 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 16:29:17 -0400 Subject: config: Check for acl_is_trivial_np() --- config/acl-is-trivial-np.c | 11 +++++++++++ config/header.mk | 1 + src/fsade.c | 4 ++-- 3 files changed, 14 insertions(+), 2 deletions(-) create mode 100644 config/acl-is-trivial-np.c diff --git a/config/acl-is-trivial-np.c b/config/acl-is-trivial-np.c new file mode 100644 index 0000000..4178238 --- /dev/null +++ b/config/acl-is-trivial-np.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + acl_t acl = acl_get_fd(3); + int trivial; + acl_is_trivial_np(acl, &trivial); + return 0; +} diff --git a/config/header.mk b/config/header.mk index b6915d5..20cb7ae 100644 --- a/config/header.mk +++ b/config/header.mk @@ -9,6 +9,7 @@ include config/exports.mk # All header fragments we generate HEADERS := \ + ${GEN}/acl-is-trivial-np.h \ ${GEN}/confstr.h \ ${GEN}/getdents.h \ ${GEN}/getdents64.h \ diff --git a/src/fsade.c b/src/fsade.c index 34a4d57..51b629f 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -199,7 +199,7 @@ static int bfs_check_acl_type(acl_t acl, acl_type_t type) { return bfs_check_posix1e_acl(acl, false); } -#if __FreeBSD__ +#if BFS_HAS_ACL_IS_TRIVIAL_NP int trivial; int ret = acl_is_trivial_np(acl, &trivial); @@ -213,7 +213,7 @@ static int bfs_check_acl_type(acl_t acl, acl_type_t type) { } else { return 1; } -#else // !__FreeBSD__ +#else return bfs_check_posix1e_acl(acl, true); #endif } -- cgit v1.2.3 From d7130b3eee3e59f2e5475bbac5695cee54310c20 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 17:30:08 -0400 Subject: config: Check for statx() --- config/header.mk | 4 +++- config/statx-syscall.c | 13 +++++++++++++ config/statx.c | 11 +++++++++++ src/stat.c | 4 ++-- src/stat.h | 8 ++------ 5 files changed, 31 insertions(+), 9 deletions(-) create mode 100644 config/statx-syscall.c create mode 100644 config/statx.c diff --git a/config/header.mk b/config/header.mk index 20cb7ae..ec53dfa 100644 --- a/config/header.mk +++ b/config/header.mk @@ -17,7 +17,9 @@ HEADERS := \ ${GEN}/getprogname.h \ ${GEN}/getprogname-gnu.h \ ${GEN}/posix-spawn-addfchdir.h \ - ${GEN}/posix-spawn-addfchdir-np.h + ${GEN}/posix-spawn-addfchdir-np.h \ + ${GEN}/statx.h \ + ${GEN}/statx-syscall.h ${GEN}/config.h: ${HEADERS} ${MSG} "[ GEN] ${TGT}" diff --git a/config/statx-syscall.c b/config/statx-syscall.c new file mode 100644 index 0000000..87ec869 --- /dev/null +++ b/config/statx-syscall.c @@ -0,0 +1,13 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include +#include + +int main(void) { + struct statx sb; + syscall(SYS_statx, AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); + return 0; +} diff --git a/config/statx.c b/config/statx.c new file mode 100644 index 0000000..65f1674 --- /dev/null +++ b/config/statx.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + struct statx sb; + statx(AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); + return 0; +} diff --git a/src/stat.c b/src/stat.c index eca5bab..e525f24 100644 --- a/src/stat.c +++ b/src/stat.c @@ -13,7 +13,7 @@ #include #include -#if BFS_USE_STATX && !BFS_HAS_LIBC_STATX +#if BFS_USE_STATX && !BFS_HAS_STATX # include # include # include @@ -137,7 +137,7 @@ static int bfs_stat_impl(int at_fd, const char *at_path, int at_flags, struct bf * Wrapper for the statx() system call, which had no glibc wrapper prior to 2.28. */ static int bfs_statx(int at_fd, const char *at_path, int at_flags, unsigned int mask, struct statx *buf) { -#if BFS_HAS_LIBC_STATX +#if BFS_HAS_STATX int ret = statx(at_fd, at_path, at_flags, mask, buf); #else int ret = syscall(SYS_statx, at_fd, at_path, at_flags, mask, buf); diff --git a/src/stat.h b/src/stat.h index 1fdd263..8d7144d 100644 --- a/src/stat.h +++ b/src/stat.h @@ -17,16 +17,12 @@ #include #include -#if defined(STATX_BASIC_STATS) && (!__ANDROID__ || __ANDROID_API__ >= 30) -# define BFS_HAS_LIBC_STATX true -#elif __linux__ +#if !BFS_HAS_STATX && BFS_HAS_STATX_SYSCALL # include #endif #ifndef BFS_USE_STATX -# ifdef STATX_BASIC_STATS -# define BFS_USE_STATX true -# endif +# define BFS_USE_STATX (BFS_HAS_STATX || BFS_HAS_STATX_SYSCALL) #endif #if BFS_USE_SYS_PARAM_H -- cgit v1.2.3 From 71f822ec2bf8c41f782f154d87b7b415a530dd03 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 18 Apr 2024 17:48:53 -0400 Subject: config: Check for pipe2() --- config/header.mk | 1 + config/pipe2.c | 10 ++++++++++ src/bfstd.c | 2 +- 3 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 config/pipe2.c diff --git a/config/header.mk b/config/header.mk index ec53dfa..55657f8 100644 --- a/config/header.mk +++ b/config/header.mk @@ -16,6 +16,7 @@ HEADERS := \ ${GEN}/getdents64-syscall.h \ ${GEN}/getprogname.h \ ${GEN}/getprogname-gnu.h \ + ${GEN}/pipe2.h \ ${GEN}/posix-spawn-addfchdir.h \ ${GEN}/posix-spawn-addfchdir-np.h \ ${GEN}/statx.h \ diff --git a/config/pipe2.c b/config/pipe2.c new file mode 100644 index 0000000..4cb43b5 --- /dev/null +++ b/config/pipe2.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + int fds[2]; + return pipe2(fds, O_CLOEXEC); +} diff --git a/src/bfstd.c b/src/bfstd.c index 3eff024..e2c2b97 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -489,7 +489,7 @@ int dup_cloexec(int fd) { } int pipe_cloexec(int pipefd[2]) { -#if __linux__ || (BSD && !__APPLE__) +#if BFS_HAS_PIPE2 return pipe2(pipefd, O_CLOEXEC); #else if (pipe(pipefd) != 0) { -- cgit v1.2.3 From 6cdb407aa23d8b129e9b9a49a4528c3e0def69e6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 Apr 2024 14:15:30 -0400 Subject: config: Check for strerror_[lr]() --- config/header.mk | 6 +++++- config/strerror-l.c | 11 +++++++++++ config/strerror-r-gnu.c | 11 +++++++++++ config/strerror-r-posix.c | 11 +++++++++++ config/uselocale.c | 9 +++++++++ src/bfstd.c | 42 ++++++++++++++++++++---------------------- 6 files changed, 67 insertions(+), 23 deletions(-) create mode 100644 config/strerror-l.c create mode 100644 config/strerror-r-gnu.c create mode 100644 config/strerror-r-posix.c create mode 100644 config/uselocale.c diff --git a/config/header.mk b/config/header.mk index 55657f8..36ad98e 100644 --- a/config/header.mk +++ b/config/header.mk @@ -20,7 +20,11 @@ HEADERS := \ ${GEN}/posix-spawn-addfchdir.h \ ${GEN}/posix-spawn-addfchdir-np.h \ ${GEN}/statx.h \ - ${GEN}/statx-syscall.h + ${GEN}/statx-syscall.h \ + ${GEN}/strerror-l.h \ + ${GEN}/strerror-r-gnu.h \ + ${GEN}/strerror-r-posix.h \ + ${GEN}/uselocale.h ${GEN}/config.h: ${HEADERS} ${MSG} "[ GEN] ${TGT}" diff --git a/config/strerror-l.c b/config/strerror-l.c new file mode 100644 index 0000000..3dcc4d7 --- /dev/null +++ b/config/strerror-l.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + locale_t locale = duplocale(LC_GLOBAL_LOCALE); + return !strerror_l(ENOMEM, locale); +} diff --git a/config/strerror-r-gnu.c b/config/strerror-r-gnu.c new file mode 100644 index 0000000..26ca0ee --- /dev/null +++ b/config/strerror-r-gnu.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + char buf[256]; + // Check that strerror_r() returns a pointer + return *strerror_r(ENOMEM, buf, sizeof(buf)); +} diff --git a/config/strerror-r-posix.c b/config/strerror-r-posix.c new file mode 100644 index 0000000..41b2d30 --- /dev/null +++ b/config/strerror-r-posix.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + char buf[256]; + // Check that strerror_r() returns an integer + return 2 * strerror_r(ENOMEM, buf, sizeof(buf)); +} diff --git a/config/uselocale.c b/config/uselocale.c new file mode 100644 index 0000000..a712ff8 --- /dev/null +++ b/config/uselocale.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + locale_t locale = uselocale((locale_t)0); + return locale == LC_GLOBAL_LOCALE; +} diff --git a/src/bfstd.c b/src/bfstd.c index e2c2b97..1144380 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -317,35 +317,33 @@ const char *xstrerror(int errnum) { const char *ret = NULL; static thread_local char buf[256]; - // - __APPLE__ - // - __COSMOPOLITAN__ - // - No strerror_l() - // - __FreeBSD__ && SANITIZE_MEMORY - // - duplocale() triggers https://github.com/llvm/llvm-project/issues/65532 -#if __APPLE__ || __COSMOPOLITAN__ || (__FreeBSD__ && SANITIZE_MEMORY) - if (strerror_r(errnum, buf, sizeof(buf)) == 0) { - ret = buf; - } -#else -# if __NetBSD__ - // NetBSD has no thread-specific locales - locale_t loc = LC_GLOBAL_LOCALE; -# else + // On FreeBSD with MemorySanitizer, duplocale() triggers + // https://github.com/llvm/llvm-project/issues/65532 +#if BFS_HAS_STRERROR_L && !(__FreeBSD__ && SANITIZE_MEMORY) +# if BFS_HAS_USELOCALE locale_t loc = uselocale((locale_t)0); +# else + locale_t loc = LC_GLOBAL_LOCALE; # endif - locale_t copy = loc; - if (copy == LC_GLOBAL_LOCALE) { - copy = duplocale(copy); + bool free_loc = false; + if (loc == LC_GLOBAL_LOCALE) { + loc = duplocale(loc); + free_loc = true; } - if (copy != (locale_t)0) { - ret = strerror_l(errnum, copy); - - if (loc == LC_GLOBAL_LOCALE) { - freelocale(copy); + if (loc != (locale_t)0) { + ret = strerror_l(errnum, loc); + if (free_loc) { + freelocale(loc); } } +#elif BFS_HAS_STRERROR_R_POSIX + if (strerror_r(errnum, buf, sizeof(buf)) == 0) { + ret = buf; + } +#elif BFS_HAS_STRERROR_R_GNU + ret = strerror_r(errnum, buf, sizeof(buf)); #endif if (!ret) { -- cgit v1.2.3 From 17da9c855a49925333433b4308c1b3384c65c50a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 Apr 2024 14:23:16 -0400 Subject: config: Check for fdclosedir() --- config/fdclosedir.c | 8 ++++++++ config/header.mk | 1 + src/dir.c | 2 +- src/dir.h | 2 +- 4 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 config/fdclosedir.c diff --git a/config/fdclosedir.c b/config/fdclosedir.c new file mode 100644 index 0000000..f4ad1f5 --- /dev/null +++ b/config/fdclosedir.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return fdclosedir(opendir(".")); +} diff --git a/config/header.mk b/config/header.mk index 36ad98e..04fbdec 100644 --- a/config/header.mk +++ b/config/header.mk @@ -11,6 +11,7 @@ include config/exports.mk HEADERS := \ ${GEN}/acl-is-trivial-np.h \ ${GEN}/confstr.h \ + ${GEN}/fdclosedir.h \ ${GEN}/getdents.h \ ${GEN}/getdents64.h \ ${GEN}/getdents64-syscall.h \ diff --git a/src/dir.c b/src/dir.c index 8b1bee0..cfbbca4 100644 --- a/src/dir.c +++ b/src/dir.c @@ -353,7 +353,7 @@ int bfs_closedir(struct bfs_dir *dir) { int bfs_unwrapdir(struct bfs_dir *dir) { #if BFS_USE_GETDENTS int ret = dir->fd; -#elif __FreeBSD__ +#elif BFS_HAS_FDCLOSEDIR int ret = fdclosedir(dir->dir); #endif diff --git a/src/dir.h b/src/dir.h index 19dfa98..bc6c4ed 100644 --- a/src/dir.h +++ b/src/dir.h @@ -152,7 +152,7 @@ int bfs_closedir(struct bfs_dir *dir); * Whether the bfs_unwrapdir() function is supported. */ #ifndef BFS_USE_UNWRAPDIR -# define BFS_USE_UNWRAPDIR (BFS_USE_GETDENTS || __FreeBSD__) +# define BFS_USE_UNWRAPDIR (BFS_USE_GETDENTS || BFS_HAS_FDCLOSEDIR) #endif #if BFS_USE_UNWRAPDIR -- cgit v1.2.3 From 111cc3af4b6132fda47d18683a04985ca7c571c2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 Apr 2024 14:28:43 -0400 Subject: config: Check for struct tm::tm_gmtoff --- config/header.mk | 1 + config/tm-gmtoff.c | 9 +++++++++ src/parse.c | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 config/tm-gmtoff.c diff --git a/config/header.mk b/config/header.mk index 04fbdec..38f1277 100644 --- a/config/header.mk +++ b/config/header.mk @@ -25,6 +25,7 @@ HEADERS := \ ${GEN}/strerror-l.h \ ${GEN}/strerror-r-gnu.h \ ${GEN}/strerror-r-posix.h \ + ${GEN}/tm-gmtoff.h \ ${GEN}/uselocale.h ${GEN}/config.h: ${HEADERS} diff --git a/config/tm-gmtoff.c b/config/tm-gmtoff.c new file mode 100644 index 0000000..543df48 --- /dev/null +++ b/config/tm-gmtoff.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct tm tm = {0}; + return tm.tm_gmtoff; +} diff --git a/src/parse.c b/src/parse.c index c2ae58f..a1155c0 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1755,7 +1755,7 @@ static int parse_reftime(const struct bfs_parser *parser, struct bfs_expr *expr) fprintf(stderr, " - %04d-%02d-%02d\n", year, month, tm.tm_mday); fprintf(stderr, " - %04d-%02d-%02dT%02d:%02d:%02d\n", year, month, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); -#if __FreeBSD__ +#if BFS_HAS_TM_GMTOFF int gmtoff = tm.tm_gmtoff; #else int gmtoff = -timezone; -- cgit v1.2.3 From 48d91c62cd27c15fe0e928abf6d023d17e9c41f7 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 Apr 2024 14:50:48 -0400 Subject: config: Check for struct stat::st_{a,c,m,birth}{tim,timespec} --- config/header.mk | 4 ++++ config/st-acmtim.c | 12 ++++++++++++ config/st-acmtimespec.c | 12 ++++++++++++ config/st-birthtim.c | 9 +++++++++ config/st-birthtimespec.c | 9 +++++++++ src/bfstd.h | 20 +++++++++++++++----- src/stat.c | 11 +++++++---- tests/xtouch.c | 4 ++-- 8 files changed, 70 insertions(+), 11 deletions(-) create mode 100644 config/st-acmtim.c create mode 100644 config/st-acmtimespec.c create mode 100644 config/st-birthtim.c create mode 100644 config/st-birthtimespec.c diff --git a/config/header.mk b/config/header.mk index 38f1277..5b6ff11 100644 --- a/config/header.mk +++ b/config/header.mk @@ -20,6 +20,10 @@ HEADERS := \ ${GEN}/pipe2.h \ ${GEN}/posix-spawn-addfchdir.h \ ${GEN}/posix-spawn-addfchdir-np.h \ + ${GEN}/st-acmtim.h \ + ${GEN}/st-acmtimespec.h \ + ${GEN}/st-birthtim.h \ + ${GEN}/st-birthtimespec.h \ ${GEN}/statx.h \ ${GEN}/statx-syscall.h \ ${GEN}/strerror-l.h \ diff --git a/config/st-acmtim.c b/config/st-acmtim.c new file mode 100644 index 0000000..d687ab0 --- /dev/null +++ b/config/st-acmtim.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + unsigned int a = sb.st_atim.tv_sec; + unsigned int c = sb.st_ctim.tv_sec; + unsigned int m = sb.st_mtim.tv_sec; + return a + c + m; +} diff --git a/config/st-acmtimespec.c b/config/st-acmtimespec.c new file mode 100644 index 0000000..f747bc0 --- /dev/null +++ b/config/st-acmtimespec.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + unsigned int a = sb.st_atimespec.tv_sec; + unsigned int c = sb.st_ctimespec.tv_sec; + unsigned int m = sb.st_mtimespec.tv_sec; + return a + c + m; +} diff --git a/config/st-birthtim.c b/config/st-birthtim.c new file mode 100644 index 0000000..4964571 --- /dev/null +++ b/config/st-birthtim.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_birthtim.tv_sec; +} diff --git a/config/st-birthtimespec.c b/config/st-birthtimespec.c new file mode 100644 index 0000000..91a613f --- /dev/null +++ b/config/st-birthtimespec.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_birthtimespec.tv_sec; +} diff --git a/src/bfstd.h b/src/bfstd.h index 42f5d5b..f91e380 100644 --- a/src/bfstd.h +++ b/src/bfstd.h @@ -283,11 +283,21 @@ int xminor(dev_t dev); // #include -#if __APPLE__ -# define st_atim st_atimespec -# define st_ctim st_ctimespec -# define st_mtim st_mtimespec -# define st_birthtim st_birthtimespec +/** + * Get the access/change/modification time from a struct stat. + */ +#if BFS_HAS_ST_ACMTIM +# define ST_ATIM(sb) (sb).st_atim +# define ST_CTIM(sb) (sb).st_ctim +# define ST_MTIM(sb) (sb).st_mtim +#elif BFS_HAS_ST_ACMTIMESPEC +# define ST_ATIM(sb) (sb).st_atimespec +# define ST_CTIM(sb) (sb).st_ctimespec +# define ST_MTIM(sb) (sb).st_mtimespec +#else +# define ST_ATIM(sb) ((struct timespec) { .tv_sec = (sb).st_atime }) +# define ST_CTIM(sb) ((struct timespec) { .tv_sec = (sb).st_ctime }) +# define ST_MTIM(sb) ((struct timespec) { .tv_sec = (sb).st_mtime }) #endif // #include diff --git a/src/stat.c b/src/stat.c index e525f24..a32dbaa 100644 --- a/src/stat.c +++ b/src/stat.c @@ -104,18 +104,21 @@ void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { dest->mask |= BFS_STAT_ATTRS; #endif - dest->atime = src->st_atim; + dest->atime = ST_ATIM(*src); dest->mask |= BFS_STAT_ATIME; - dest->ctime = src->st_ctim; + dest->ctime = ST_CTIM(*src); dest->mask |= BFS_STAT_CTIME; - dest->mtime = src->st_mtim; + dest->mtime = ST_MTIM(*src); dest->mask |= BFS_STAT_MTIME; -#if __APPLE__ || __FreeBSD__ || __NetBSD__ +#if BFS_HAS_ST_BIRTHTIM dest->btime = src->st_birthtim; dest->mask |= BFS_STAT_BTIME; +#elif BFS_HAS_ST_BIRTHTIMESPEC + dest->btime = src->st_birthtimespec; + dest->mask |= BFS_STAT_BTIME; #endif } diff --git a/tests/xtouch.c b/tests/xtouch.c index 82d749d..cd41842 100644 --- a/tests/xtouch.c +++ b/tests/xtouch.c @@ -237,8 +237,8 @@ int main(int argc, char *argv[]) { fprintf(stderr, "%s: '%s': %s\n", cmd, rarg, xstrerror(errno)); return EXIT_FAILURE; } - times[0] = buf.st_atim; - times[1] = buf.st_mtim; + times[0] = ST_ATIM(buf); + times[1] = ST_MTIM(buf); } else if (darg) { if (xgetdate(darg, ×[0]) != 0) { fprintf(stderr, "%s: Parsing time '%s' failed: %s\n", cmd, darg, xstrerror(errno)); -- cgit v1.2.3 From c35d0c2ed5578b180e17c37588a047fd013cc619 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 Apr 2024 15:14:09 -0400 Subject: config: Check for struct stat::st_flags --- config/header.mk | 1 + config/st-flags.c | 9 +++++++++ src/stat.c | 2 +- 3 files changed, 11 insertions(+), 1 deletion(-) create mode 100644 config/st-flags.c diff --git a/config/header.mk b/config/header.mk index 5b6ff11..06b8f7f 100644 --- a/config/header.mk +++ b/config/header.mk @@ -24,6 +24,7 @@ HEADERS := \ ${GEN}/st-acmtimespec.h \ ${GEN}/st-birthtim.h \ ${GEN}/st-birthtimespec.h \ + ${GEN}/st-flags.h \ ${GEN}/statx.h \ ${GEN}/statx-syscall.h \ ${GEN}/strerror-l.h \ diff --git a/config/st-flags.c b/config/st-flags.c new file mode 100644 index 0000000..b1d0c32 --- /dev/null +++ b/config/st-flags.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_flags; +} diff --git a/src/stat.c b/src/stat.c index a32dbaa..f5cf3fe 100644 --- a/src/stat.c +++ b/src/stat.c @@ -99,7 +99,7 @@ void bfs_stat_convert(struct bfs_stat *dest, const struct stat *src) { dest->rdev = src->st_rdev; dest->mask |= BFS_STAT_RDEV; -#if BSD +#if BFS_HAS_ST_FLAGS dest->attrs = src->st_flags; dest->mask |= BFS_STAT_ATTRS; #endif -- cgit v1.2.3 From 0d8bf80364a23d9b90ab6c034625b60e4c566c61 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 Apr 2024 14:58:38 -0400 Subject: config: Check for aligned_alloc() --- config/aligned-alloc.c | 8 ++++++++ config/header.mk | 1 + src/alloc.c | 6 +++--- 3 files changed, 12 insertions(+), 3 deletions(-) create mode 100644 config/aligned-alloc.c diff --git a/config/aligned-alloc.c b/config/aligned-alloc.c new file mode 100644 index 0000000..4460038 --- /dev/null +++ b/config/aligned-alloc.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return !aligned_alloc(_Alignof(void *), sizeof(void *)); +} diff --git a/config/header.mk b/config/header.mk index 06b8f7f..86a4dc5 100644 --- a/config/header.mk +++ b/config/header.mk @@ -10,6 +10,7 @@ include config/exports.mk # All header fragments we generate HEADERS := \ ${GEN}/acl-is-trivial-np.h \ + ${GEN}/aligned-alloc.h \ ${GEN}/confstr.h \ ${GEN}/fdclosedir.h \ ${GEN}/getdents.h \ diff --git a/src/alloc.c b/src/alloc.c index ec8608f..ebaff38 100644 --- a/src/alloc.c +++ b/src/alloc.c @@ -24,12 +24,12 @@ static void *xmemalign(size_t align, size_t size) { bfs_assert(align >= sizeof(void *)); bfs_assert(is_aligned(align, size)); -#if __APPLE__ +#if BFS_HAS_ALIGNED_ALLOC + return aligned_alloc(align, size); +#else void *ptr = NULL; errno = posix_memalign(&ptr, align, size); return ptr; -#else - return aligned_alloc(align, size); #endif } -- cgit v1.2.3 From 75311e2f9aabd1e4775176e9adff5cd5f1fe42aa Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 19 Apr 2024 15:44:36 -0400 Subject: config: Check for max_align_t --- config/header.mk | 1 + config/max-align-t.c | 8 ++++++++ src/prelude.h | 19 +++++++++++++++---- 3 files changed, 24 insertions(+), 4 deletions(-) create mode 100644 config/max-align-t.c diff --git a/config/header.mk b/config/header.mk index 86a4dc5..bba03ef 100644 --- a/config/header.mk +++ b/config/header.mk @@ -18,6 +18,7 @@ HEADERS := \ ${GEN}/getdents64-syscall.h \ ${GEN}/getprogname.h \ ${GEN}/getprogname-gnu.h \ + ${GEN}/max-align-t.h \ ${GEN}/pipe2.h \ ${GEN}/posix-spawn-addfchdir.h \ ${GEN}/posix-spawn-addfchdir-np.h \ diff --git a/config/max-align-t.c b/config/max-align-t.c new file mode 100644 index 0000000..96165ce --- /dev/null +++ b/config/max-align-t.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return _Alignof(max_align_t); +} diff --git a/src/prelude.h b/src/prelude.h index a23b167..390666b 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -181,15 +181,26 @@ extern const char bfs_version[]; # define TRUE_SHARING_SIZE 64 #endif +/** + * Polyfill max_align_t if we don't already have it. + */ +#if !BFS_HAS_MAX_ALIGN_T +typedef union { +# ifdef __BIGGEST_ALIGNMENT__ + alignas(__BIGGEST_ALIGNMENT__) char c; +# else + long double ld; + long long ll; + void *ptr; +# endif +} max_align_t; +#endif + /** * Alignment specifier that avoids false sharing. */ #define cache_align alignas(FALSE_SHARING_SIZE) -#if __COSMOPOLITAN__ -typedef long double max_align_t; -#endif - // Wrappers for attributes /** -- cgit v1.2.3 From 569e6758e26e23c27d94c0d132bf5e26ee5af862 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 09:23:55 -0400 Subject: config: Check for extattr_{get,list}_{file,link}() This lets us implement -xattr on DragonFly BSD. --- config/extattr-get-file.c | 10 ++++++++ config/extattr-get-link.c | 10 ++++++++ config/extattr-list-file.c | 10 ++++++++ config/extattr-list-link.c | 10 ++++++++ config/header.mk | 4 +++ src/fsade.c | 62 ++++++++++++++++++++++++++++++++++++++-------- src/prelude.h | 2 +- 7 files changed, 97 insertions(+), 11 deletions(-) create mode 100644 config/extattr-get-file.c create mode 100644 config/extattr-get-link.c create mode 100644 config/extattr-list-file.c create mode 100644 config/extattr-list-link.c diff --git a/config/extattr-get-file.c b/config/extattr-get-file.c new file mode 100644 index 0000000..ac9cf96 --- /dev/null +++ b/config/extattr-get-file.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_get_file("file", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); +} diff --git a/config/extattr-get-link.c b/config/extattr-get-link.c new file mode 100644 index 0000000..c35be5b --- /dev/null +++ b/config/extattr-get-link.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_get_link("link", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); +} diff --git a/config/extattr-list-file.c b/config/extattr-list-file.c new file mode 100644 index 0000000..e68a8bb --- /dev/null +++ b/config/extattr-list-file.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_list_file("file", EXTATTR_NAMESPACE_USER, NULL, 0); +} diff --git a/config/extattr-list-link.c b/config/extattr-list-link.c new file mode 100644 index 0000000..49f0ec2 --- /dev/null +++ b/config/extattr-list-link.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_list_link("link", EXTATTR_NAMESPACE_USER, NULL, 0); +} diff --git a/config/header.mk b/config/header.mk index bba03ef..4da0fd2 100644 --- a/config/header.mk +++ b/config/header.mk @@ -12,6 +12,10 @@ HEADERS := \ ${GEN}/acl-is-trivial-np.h \ ${GEN}/aligned-alloc.h \ ${GEN}/confstr.h \ + ${GEN}/extattr-get-file.h \ + ${GEN}/extattr-get-link.h \ + ${GEN}/extattr-list-file.h \ + ${GEN}/extattr-list-link.h \ ${GEN}/fdclosedir.h \ ${GEN}/getdents.h \ ${GEN}/getdents64.h \ diff --git a/src/fsade.c b/src/fsade.c index 51b629f..02b12d0 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -327,17 +327,62 @@ int bfs_check_capabilities(const struct BFTW *ftwbuf) { #if BFS_CAN_CHECK_XATTRS +#if BFS_USE_SYS_EXTATTR_H + +/** Wrapper for extattr_list_{file,link}. */ +static ssize_t bfs_extattr_list(const char *path, enum bfs_type type, int namespace) { + if (type == BFS_LNK) { +#if BFS_HAS_EXTATTR_LIST_LINK + return extattr_list_link(path, namespace, NULL, 0); +#elif BFS_HAS_EXTATTR_GET_LINK + return extattr_get_link(path, namespace, "", NULL, 0); +#else + return 0; +#endif + } + +#if BFS_HAS_EXTATTR_LIST_FILE + return extattr_list_file(path, namespace, NULL, 0); +#elif BFS_HAS_EXTATTR_GET_FILE + // From man extattr(2): + // + // In earlier versions of this API, passing an empty string for the + // attribute name to extattr_get_file() would return the list of attributes + // defined for the target object. This interface has been deprecated in + // preference to using the explicit list API, and should not be used. + return extattr_get_file(path, namespace, "", NULL, 0); +#else + return 0; +#endif +} + +/** Wrapper for extattr_get_{file,link}. */ +static ssize_t bfs_extattr_get(const char *path, enum bfs_type type, int namespace, const char *name) { + if (type == BFS_LNK) { +#if BFS_HAS_EXTATTR_GET_LINK + return extattr_get_link(path, namespace, name, NULL, 0); +#else + return 0; +#endif + } + +#if BFS_HAS_EXTATTR_GET_FILE + return extattr_get_file(path, namespace, name, NULL, 0); +#else + return 0; +#endif +} + +#endif // BFS_USE_SYS_EXTATTR_H + int bfs_check_xattrs(const struct BFTW *ftwbuf) { const char *path = fake_at(ftwbuf); ssize_t len; #if BFS_USE_SYS_EXTATTR_H - ssize_t (*extattr_list)(const char *, int, void *, size_t) = - ftwbuf->type == BFS_LNK ? extattr_list_link : extattr_list_file; - - len = extattr_list(path, EXTATTR_NAMESPACE_SYSTEM, NULL, 0); + len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM); if (len <= 0) { - len = extattr_list(path, EXTATTR_NAMESPACE_USER, NULL, 0); + len = bfs_extattr_list(path, ftwbuf->type, EXTATTR_NAMESPACE_USER); } #elif __APPLE__ int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0; @@ -371,12 +416,9 @@ int bfs_check_xattr_named(const struct BFTW *ftwbuf, const char *name) { ssize_t len; #if BFS_USE_SYS_EXTATTR_H - ssize_t (*extattr_get)(const char *, int, const char *, void *, size_t) = - ftwbuf->type == BFS_LNK ? extattr_get_link : extattr_get_file; - - len = extattr_get(path, EXTATTR_NAMESPACE_SYSTEM, name, NULL, 0); + len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_SYSTEM, name); if (len < 0) { - len = extattr_get(path, EXTATTR_NAMESPACE_USER, name, NULL, 0); + len = bfs_extattr_get(path, ftwbuf->type, EXTATTR_NAMESPACE_USER, name); } #elif __APPLE__ int options = ftwbuf->type == BFS_LNK ? XATTR_NOFOLLOW : 0; diff --git a/src/prelude.h b/src/prelude.h index 390666b..9e64260 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -106,7 +106,7 @@ extern const char bfs_version[]; # define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP)) #endif #ifndef BFS_USE_SYS_EXTATTR_H -# define BFS_USE_SYS_EXTATTR_H (BFS_HAS_SYS_EXTATTR_H && !__DragonFly__) +# define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H #endif #ifndef BFS_USE_SYS_MKDEV_H # define BFS_USE_SYS_MKDEV_H BFS_HAS_SYS_MKDEV_H -- cgit v1.2.3 From b2d31717511bd21854840bd69875ca80b875137a Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 09:45:54 -0400 Subject: config: Check for acl_get_{entry,tag_type}() --- config/acl-get-entry.c | 8 ++++++++ config/acl-get-tag-type.c | 10 ++++++++++ config/header.mk | 2 ++ src/fsade.c | 32 ++++++++++++++++++++------------ 4 files changed, 40 insertions(+), 12 deletions(-) create mode 100644 config/acl-get-entry.c create mode 100644 config/acl-get-tag-type.c diff --git a/config/acl-get-entry.c b/config/acl-get-entry.c new file mode 100644 index 0000000..3cce771 --- /dev/null +++ b/config/acl-get-entry.c @@ -0,0 +1,8 @@ +#include +#include + +int main(void) { + acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); + acl_entry_t entry; + return acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); +} diff --git a/config/acl-get-tag-type.c b/config/acl-get-tag-type.c new file mode 100644 index 0000000..2901956 --- /dev/null +++ b/config/acl-get-tag-type.c @@ -0,0 +1,10 @@ +#include +#include +#include + +int main(void) { + acl_entry_t entry; + memset(&entry, 0, sizeof(entry)); + acl_tag_t tag; + return acl_get_tag_type(entry, &tag); +} diff --git a/config/header.mk b/config/header.mk index 4da0fd2..c8f8d7c 100644 --- a/config/header.mk +++ b/config/header.mk @@ -9,6 +9,8 @@ include config/exports.mk # All header fragments we generate HEADERS := \ + ${GEN}/acl-get-entry.h \ + ${GEN}/acl-get-tag-type.h \ ${GEN}/acl-is-trivial-np.h \ ${GEN}/aligned-alloc.h \ ${GEN}/confstr.h \ diff --git a/src/fsade.c b/src/fsade.c index 02b12d0..dac2ca6 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -123,9 +123,19 @@ static bool is_absence_error(int error) { /** Unified interface for incompatible acl_get_entry() implementations. */ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { -#if __DragonFly__ && !defined(ACL_FIRST_ENTRY) && !defined(ACL_NEXT_ENTRY) -# define ACL_FIRST_ENTRY 0 -# define ACL_NEXT_ENTRY 1 +#if BFS_HAS_ACL_GET_ENTRY + int ret = acl_get_entry(acl, which, entry); +# if __APPLE__ + // POSIX.1e specifies a return value of 1 for success, but macOS returns 0 instead + return !ret; +# else + return ret; +# endif +#elif __DragonFly__ +# if !defined(ACL_FIRST_ENTRY) && !defined(ACL_NEXT_ENTRY) +# define ACL_FIRST_ENTRY 0 +# define ACL_NEXT_ENTRY 1 +# endif switch (which) { case ACL_FIRST_ENTRY: @@ -142,24 +152,22 @@ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { acl_entry_t last = &acl->acl_entry[acl->acl_cnt]; return *entry == last; #else - int ret = acl_get_entry(acl, which, entry); -# if __APPLE__ - // POSIX.1e specifies a return value of 1 for success, but macOS returns 0 instead - return !ret; -# else - return ret; -# endif + errno = ENOTSUP; + return -1; #endif } /** Unified interface for acl_get_tag_type(). */ attr(maybe_unused) static int bfs_acl_tag_type(acl_entry_t entry, acl_tag_t *tag) { -#if __DragonFly__ +#if BFS_HAS_ACL_GET_TAG_TYPE + return acl_get_tag_type(entry, tag); +#elif __DragonFly__ *tag = entry->ae_tag; return 0; #else - return acl_get_tag_type(entry, tag); + errno = ENOTSUP; + return -1; #endif } -- cgit v1.2.3 From 3d58ea8f735ce57a62fc678ca36b388b9c731c83 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 10:22:57 -0400 Subject: fsade: Simplify BFS_CAN_CHECK_CAPABILITIES --- src/fsade.h | 7 +------ src/prelude.h | 7 ------- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/src/fsade.h b/src/fsade.h index 6300852..cd5c34f 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -13,12 +13,7 @@ #define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H -#if !defined(BFS_CAN_CHECK_CAPABILITIES) && BFS_USE_SYS_CAPABILITY_H -# include -# ifdef CAP_CHOWN -# define BFS_CAN_CHECK_CAPABILITIES true -# endif -#endif +#define BFS_CAN_CHECK_CAPABILITIES BFS_USE_LIBCAP #define BFS_CAN_CHECK_CONTEXT BFS_USE_LIBSELINUX diff --git a/src/prelude.h b/src/prelude.h index 9e64260..bf87386 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -52,9 +52,6 @@ extern const char bfs_version[]; #if __has_include() # define BFS_HAS_SYS_ACL_H true #endif -#if __has_include() -# define BFS_HAS_SYS_CAPABILITY_H true -#endif #if __has_include() # define BFS_HAS_SYS_EXTATTR_H true #endif @@ -82,7 +79,6 @@ extern const char bfs_version[]; #define BFS_HAS_MNTENT_H __GLIBC__ #define BFS_HAS_PATHS_H true #define BFS_HAS_SYS_ACL_H true -#define BFS_HAS_SYS_CAPABILITY_H __linux__ #define BFS_HAS_SYS_EXTATTR_H __FreeBSD__ #define BFS_HAS_SYS_MKDEV_H false #define BFS_HAS_SYS_PARAM_H true @@ -102,9 +98,6 @@ extern const char bfs_version[]; #ifndef BFS_USE_SYS_ACL_H # define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL)) #endif -#ifndef BFS_USE_SYS_CAPABILITY_H -# define BFS_USE_SYS_CAPABILITY_H (BFS_HAS_SYS_CAPABILITY_H && !__FreeBSD__ && (!__linux__ || BFS_USE_LIBCAP)) -#endif #ifndef BFS_USE_SYS_EXTATTR_H # define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H #endif -- cgit v1.2.3 From 4b8669f968aae6dc0267c8d46ef265432c681b46 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 10:49:25 -0400 Subject: config: Check for acl_get_file() --- config/acl-get-file.c | 8 ++++++++ config/header.mk | 1 + src/fsade.h | 2 +- src/prelude.h | 7 ------- 4 files changed, 10 insertions(+), 8 deletions(-) create mode 100644 config/acl-get-file.c diff --git a/config/acl-get-file.c b/config/acl-get-file.c new file mode 100644 index 0000000..89fbf23 --- /dev/null +++ b/config/acl-get-file.c @@ -0,0 +1,8 @@ +#include +#include +#include + +int main(void) { + acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); + return acl == (acl_t)NULL; +} diff --git a/config/header.mk b/config/header.mk index c8f8d7c..5da0629 100644 --- a/config/header.mk +++ b/config/header.mk @@ -10,6 +10,7 @@ include config/exports.mk # All header fragments we generate HEADERS := \ ${GEN}/acl-get-entry.h \ + ${GEN}/acl-get-file.h \ ${GEN}/acl-get-tag-type.h \ ${GEN}/acl-is-trivial-np.h \ ${GEN}/aligned-alloc.h \ diff --git a/src/fsade.h b/src/fsade.h index cd5c34f..fc0809e 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -11,7 +11,7 @@ #include "prelude.h" -#define BFS_CAN_CHECK_ACL BFS_USE_SYS_ACL_H +#define BFS_CAN_CHECK_ACL BFS_HAS_ACL_GET_FILE #define BFS_CAN_CHECK_CAPABILITIES BFS_USE_LIBCAP diff --git a/src/prelude.h b/src/prelude.h index bf87386..7a646a7 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -49,9 +49,6 @@ extern const char bfs_version[]; #if __has_include() # define BFS_HAS_PATHS_H true #endif -#if __has_include() -# define BFS_HAS_SYS_ACL_H true -#endif #if __has_include() # define BFS_HAS_SYS_EXTATTR_H true #endif @@ -78,7 +75,6 @@ extern const char bfs_version[]; #define BFS_HAS_MNTENT_H __GLIBC__ #define BFS_HAS_PATHS_H true -#define BFS_HAS_SYS_ACL_H true #define BFS_HAS_SYS_EXTATTR_H __FreeBSD__ #define BFS_HAS_SYS_MKDEV_H false #define BFS_HAS_SYS_PARAM_H true @@ -95,9 +91,6 @@ extern const char bfs_version[]; #ifndef BFS_USE_PATHS_H # define BFS_USE_PATHS_H BFS_HAS_PATHS_H #endif -#ifndef BFS_USE_SYS_ACL_H -# define BFS_USE_SYS_ACL_H (BFS_HAS_SYS_ACL_H && !__illumos__ && (!__linux__ || BFS_USE_LIBACL)) -#endif #ifndef BFS_USE_SYS_EXTATTR_H # define BFS_USE_SYS_EXTATTR_H BFS_HAS_SYS_EXTATTR_H #endif -- cgit v1.2.3 From f133d71fc49a6ae2ddd64c73e630227e1e7a9ba5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 11:00:37 -0400 Subject: fsade: Implement ACL detection on Illumos --- config/acl-is-trivial-np.c | 1 + config/acl-trivial.c | 8 ++++++++ config/flags.mk | 2 +- config/header.mk | 1 + src/fsade.c | 31 ++++++++++++++++++++----------- src/fsade.h | 2 +- 6 files changed, 32 insertions(+), 13 deletions(-) create mode 100644 config/acl-trivial.c diff --git a/config/acl-is-trivial-np.c b/config/acl-is-trivial-np.c index 4178238..9ca9fc7 100644 --- a/config/acl-is-trivial-np.c +++ b/config/acl-is-trivial-np.c @@ -1,6 +1,7 @@ // Copyright © Tavian Barnes // SPDX-License-Identifier: 0BSD +#include #include int main(void) { diff --git a/config/acl-trivial.c b/config/acl-trivial.c new file mode 100644 index 0000000..7efc838 --- /dev/null +++ b/config/acl-trivial.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return acl_trivial("."); +} diff --git a/config/flags.mk b/config/flags.mk index 8a2e50e..ee13f06 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -45,7 +45,7 @@ export BFS_CFLAGS= -std=c17 -pthread LDLIBS,DragonFly := -lposix1e LDLIBS,Linux := -lrt LDLIBS,NetBSD := -lutil -LDLIBS,SunOS := -lsocket -lnsl +LDLIBS,SunOS := -lsec -lsocket -lnsl export BFS_LDLIBS=${LDLIBS,${OS}} # Build profiles diff --git a/config/header.mk b/config/header.mk index 5da0629..7fd2f78 100644 --- a/config/header.mk +++ b/config/header.mk @@ -13,6 +13,7 @@ HEADERS := \ ${GEN}/acl-get-file.h \ ${GEN}/acl-get-tag-type.h \ ${GEN}/acl-is-trivial-np.h \ + ${GEN}/acl-trivial.h \ ${GEN}/aligned-alloc.h \ ${GEN}/confstr.h \ ${GEN}/extattr-get-file.h \ diff --git a/src/fsade.c b/src/fsade.c index dac2ca6..d56fb07 100644 --- a/src/fsade.c +++ b/src/fsade.c @@ -121,6 +121,8 @@ static bool is_absence_error(int error) { #if BFS_CAN_CHECK_ACL +#if BFS_HAS_ACL_GET_FILE + /** Unified interface for incompatible acl_get_entry() implementations. */ static int bfs_acl_entry(acl_t acl, int which, acl_entry_t *entry) { #if BFS_HAS_ACL_GET_ENTRY @@ -226,29 +228,35 @@ static int bfs_check_acl_type(acl_t acl, acl_type_t type) { #endif } +#endif // BFS_HAS_ACL_GET_FILE + int bfs_check_acl(const struct BFTW *ftwbuf) { + if (ftwbuf->type == BFS_LNK) { + return 0; + } + + const char *path = fake_at(ftwbuf); + +#if BFS_HAS_ACL_TRIVIAL + int ret = acl_trivial(path); + int error = errno; +#elif BFS_HAS_ACL_GET_FILE static const acl_type_t acl_types[] = { -#if __APPLE__ +# if __APPLE__ // macOS gives EINVAL for either of the two standard ACL types, // supporting only ACL_TYPE_EXTENDED ACL_TYPE_EXTENDED, -#else +# else // The two standard POSIX.1e ACL types ACL_TYPE_ACCESS, ACL_TYPE_DEFAULT, -#endif +# endif -#ifdef ACL_TYPE_NFS4 +# ifdef ACL_TYPE_NFS4 ACL_TYPE_NFS4, -#endif +# endif }; - if (ftwbuf->type == BFS_LNK) { - return 0; - } - - const char *path = fake_at(ftwbuf); - int ret = -1, error = 0; for (size_t i = 0; i < countof(acl_types) && ret <= 0; ++i) { acl_type_t type = acl_types[i]; @@ -272,6 +280,7 @@ int bfs_check_acl(const struct BFTW *ftwbuf) { error = errno; acl_free(acl); } +#endif free_fake_at(ftwbuf, path); errno = error; diff --git a/src/fsade.h b/src/fsade.h index fc0809e..eefef9f 100644 --- a/src/fsade.h +++ b/src/fsade.h @@ -11,7 +11,7 @@ #include "prelude.h" -#define BFS_CAN_CHECK_ACL BFS_HAS_ACL_GET_FILE +#define BFS_CAN_CHECK_ACL (BFS_HAS_ACL_GET_FILE || BFS_HAS_ACL_TRIVIAL) #define BFS_CAN_CHECK_CAPABILITIES BFS_USE_LIBCAP -- cgit v1.2.3 From 050b2eeed4c773706ee8d48a76797d78cc500602 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 14:11:35 -0400 Subject: ci/codeql: Exclude some alerts And try not to analyze config/*.c --- .github/codeql.yml | 9 +++++++++ .github/workflows/codeql.yml | 6 +++++- 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 .github/codeql.yml diff --git a/.github/codeql.yml b/.github/codeql.yml new file mode 100644 index 0000000..6ff8337 --- /dev/null +++ b/.github/codeql.yml @@ -0,0 +1,9 @@ +query-filters: + - exclude: + id: cpp/commented-out-code + - exclude: + id: cpp/long-switch + - exclude: + id: cpp/loop-variable-changed + - exclude: + id: cpp/poorly-documented-function diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index a2c224a..c21fda5 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -39,15 +39,19 @@ jobs: libonig-dev \ liburing-dev + - name: Configure + run: | + make -j$(nproc) config + - name: Initialize CodeQL uses: github/codeql-action/init@v3 with: languages: cpp queries: +security-and-quality + config-file: .github/codeql.yml - name: Build run: | - make config make -j$(nproc) all - name: Perform CodeQL Analysis -- cgit v1.2.3 From 1ab3bd78be97357ed1470ca7beee178260d3f657 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 14:26:24 -0400 Subject: ci/freebsd: Use cross-platform-actions --- .github/workflows/ci.yml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3ad924f..bd10df8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -69,27 +69,21 @@ jobs: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/freebsd-vm@v1 + uses: cross-platform-actions/action@v0.24.0 with: - release: "14.0" - usesh: true - copyback: false + operating_system: freebsd + version: "14.0" + sync_files: runner-to-vm - prepare: | - pkg install -y \ + run: | + sudo pkg install -y \ bash \ expect \ oniguruma \ pkgconf \ - sudo \ tcl-wrapper - pw useradd -n action -m -G wheel -s /usr/local/bin/bash - echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/local/etc/sudoers - mount -t fdescfs none /dev/fd - - run: | - chown -R action:action . - sudo -u action make -j$(nproc) distcheck + sudo mount -t fdescfs none /dev/fd + make -j$(nproc) distcheck openbsd: name: OpenBSD -- cgit v1.2.3 From beff30d84fd89c52754091055868efb868356e5d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 14:30:37 -0400 Subject: ci/openbsd: Use cross-platform-actions --- .github/workflows/ci.yml | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index bd10df8..b977580 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -94,27 +94,21 @@ jobs: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/openbsd-vm@v1 + uses: cross-platform-actions/action@v0.24.0 with: - release: "7.5" - usesh: true - copyback: false + operating_system: openbsd + version: "7.5" + sync_files: runner-to-vm - prepare: | - pkg_add \ + run: | + sudo pkg_add \ bash \ expect \ gmake \ oniguruma - adduser -group USER -batch action wheel >/etc/doas.conf - - run: | - chown -R action:action . jobs=$(sysctl -n hw.ncpu) - doas -u action gmake config - doas -u action gmake -j$jobs check TEST_FLAGS="--sudo=doas --verbose=skipped" + gmake -j$jobs config + gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" netbsd: name: NetBSD -- cgit v1.2.3 From 19a640cc3b274ad58194f156a7a7aa0f96c7179e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 14:41:20 -0400 Subject: ci/netbsd: Use cross-platform-actions --- .github/workflows/ci.yml | 24 ++++++++---------------- 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b977580..3890bb0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -119,30 +119,22 @@ jobs: - uses: actions/checkout@v4 - name: Run tests - uses: vmactions/netbsd-vm@v1 + uses: cross-platform-actions/action@v0.24.0 with: - release: "10.0" - usesh: true - copyback: false + operating_system: netbsd + version: "10.0" + sync_files: runner-to-vm - prepare: | + run: | PATH="/sbin:/usr/sbin:$PATH" - pkg_add \ + sudo pkgin -y install \ bash \ - clang \ oniguruma \ pkgconf \ - sudo \ tcl-expect - useradd -m -G wheel -g =uid action - echo "%wheel ALL=(ALL) NOPASSWD: ALL" >>/usr/pkg/etc/sudoers - - run: | - PATH="/sbin:/usr/sbin:$PATH" - chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action make config CC=clang - sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + make -j$jobs config + make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" dragonflybsd: name: DragonFly BSD -- cgit v1.2.3 From 911e81111c128cd059be22877d730055b1866079 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 22 Apr 2024 15:10:37 -0400 Subject: Enable Dependabot for GitHub Actions --- .github/dependabot.yml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .github/dependabot.yml diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..5ace460 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,6 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" -- cgit v1.2.3 From 30e309de41aaca259c4d0a200daa42ba74cbc5a5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Apr 2024 19:11:42 +0000 Subject: build(deps): bump codecov/codecov-action from 3 to 4 Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 3 to 4. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v3...v4) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/codecov.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2abe531..2245f40 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -29,7 +29,7 @@ jobs: make -j$(nproc) check TEST_FLAGS="--sudo" gcov -abcfpu obj/*/*.o - - uses: codecov/codecov-action@v3 + - uses: codecov/codecov-action@v4 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true -- cgit v1.2.3 From d597be5bc1ee0e76117037b7d9a52c52fd4531ad Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 Apr 2024 11:07:33 -0400 Subject: eval: Plug memory leak if bfs_opendir() fails --- src/eval.c | 40 ++++++++++++++++++++++------------------ tests/common/empty.out | 8 ++++++++ tests/common/empty.sh | 1 + tests/common/empty_error.out | 3 +++ tests/common/empty_error.sh | 7 +++++++ tests/common/empty_special.out | 20 ++++++++++++++++++++ tests/common/empty_special.sh | 1 + tests/gnu/empty.out | 8 -------- tests/gnu/empty.sh | 1 - tests/gnu/empty_special.out | 20 -------------------- tests/gnu/empty_special.sh | 1 - 11 files changed, 62 insertions(+), 48 deletions(-) create mode 100644 tests/common/empty.out create mode 100644 tests/common/empty.sh create mode 100644 tests/common/empty_error.out create mode 100644 tests/common/empty_error.sh create mode 100644 tests/common/empty_special.out create mode 100644 tests/common/empty_special.sh delete mode 100644 tests/gnu/empty.out delete mode 100644 tests/gnu/empty.sh delete mode 100644 tests/gnu/empty_special.out delete mode 100644 tests/gnu/empty_special.sh diff --git a/src/eval.c b/src/eval.c index b103912..49028b7 100644 --- a/src/eval.c +++ b/src/eval.c @@ -445,38 +445,42 @@ bool eval_depth(const struct bfs_expr *expr, struct bfs_eval *state) { * -empty test. */ bool eval_empty(const struct bfs_expr *expr, struct bfs_eval *state) { - bool ret = false; const struct BFTW *ftwbuf = state->ftwbuf; + const struct bfs_stat *statbuf; + struct bfs_dir *dir; + + switch (ftwbuf->type) { + case BFS_REG: + statbuf = eval_stat(state); + return statbuf && statbuf->size == 0; - if (ftwbuf->type == BFS_DIR) { - struct bfs_dir *dir = bfs_allocdir(); + case BFS_DIR: + dir = bfs_allocdir(); if (!dir) { - eval_report_error(state); - return ret; + goto error; } if (bfs_opendir(dir, ftwbuf->at_fd, ftwbuf->at_path, 0) != 0) { - eval_report_error(state); - return ret; + goto error; } int did_read = bfs_readdir(dir, NULL); + bfs_closedir(dir); + if (did_read < 0) { - eval_report_error(state); - } else { - ret = !did_read; + goto error; } - bfs_closedir(dir); free(dir); - } else if (ftwbuf->type == BFS_REG) { - const struct bfs_stat *statbuf = eval_stat(state); - if (statbuf) { - ret = statbuf->size == 0; - } - } + return did_read == 0; + error: + eval_report_error(state); + free(dir); + return false; - return ret; + default: + return false; + } } /** diff --git a/tests/common/empty.out b/tests/common/empty.out new file mode 100644 index 0000000..a0f4b76 --- /dev/null +++ b/tests/common/empty.out @@ -0,0 +1,8 @@ +basic/a +basic/b +basic/c/d +basic/e/f +basic/g/h +basic/i +basic/j/foo +basic/k/foo/bar diff --git a/tests/common/empty.sh b/tests/common/empty.sh new file mode 100644 index 0000000..95ee988 --- /dev/null +++ b/tests/common/empty.sh @@ -0,0 +1 @@ +bfs_diff basic -empty diff --git a/tests/common/empty_error.out b/tests/common/empty_error.out new file mode 100644 index 0000000..da45e23 --- /dev/null +++ b/tests/common/empty_error.out @@ -0,0 +1,3 @@ +./bar +./baz +./qux diff --git a/tests/common/empty_error.sh b/tests/common/empty_error.sh new file mode 100644 index 0000000..7c8049c --- /dev/null +++ b/tests/common/empty_error.sh @@ -0,0 +1,7 @@ +cd "$TEST" + +"$XTOUCH" -p foo/ bar/ baz qux +chmod -r foo baz +defer chmod +r foo baz + +! bfs_diff . -empty diff --git a/tests/common/empty_special.out b/tests/common/empty_special.out new file mode 100644 index 0000000..fa35478 --- /dev/null +++ b/tests/common/empty_special.out @@ -0,0 +1,20 @@ +rainbow// +rainbow/exec.sh +rainbow/file.dat +rainbow/file.txt +rainbow/lower.gz +rainbow/lower.tar +rainbow/lower.tar.gz +rainbow/lu.tar.GZ +rainbow/mh1 +rainbow/mh2 +rainbow/ow +rainbow/sgid +rainbow/sticky +rainbow/sticky_ow +rainbow/sugid +rainbow/suid +rainbow/ul.TAR.gz +rainbow/upper.GZ +rainbow/upper.TAR +rainbow/upper.TAR.GZ diff --git a/tests/common/empty_special.sh b/tests/common/empty_special.sh new file mode 100644 index 0000000..31e9d2e --- /dev/null +++ b/tests/common/empty_special.sh @@ -0,0 +1 @@ +bfs_diff rainbow -empty diff --git a/tests/gnu/empty.out b/tests/gnu/empty.out deleted file mode 100644 index a0f4b76..0000000 --- a/tests/gnu/empty.out +++ /dev/null @@ -1,8 +0,0 @@ -basic/a -basic/b -basic/c/d -basic/e/f -basic/g/h -basic/i -basic/j/foo -basic/k/foo/bar diff --git a/tests/gnu/empty.sh b/tests/gnu/empty.sh deleted file mode 100644 index 95ee988..0000000 --- a/tests/gnu/empty.sh +++ /dev/null @@ -1 +0,0 @@ -bfs_diff basic -empty diff --git a/tests/gnu/empty_special.out b/tests/gnu/empty_special.out deleted file mode 100644 index fa35478..0000000 --- a/tests/gnu/empty_special.out +++ /dev/null @@ -1,20 +0,0 @@ -rainbow// -rainbow/exec.sh -rainbow/file.dat -rainbow/file.txt -rainbow/lower.gz -rainbow/lower.tar -rainbow/lower.tar.gz -rainbow/lu.tar.GZ -rainbow/mh1 -rainbow/mh2 -rainbow/ow -rainbow/sgid -rainbow/sticky -rainbow/sticky_ow -rainbow/sugid -rainbow/suid -rainbow/ul.TAR.gz -rainbow/upper.GZ -rainbow/upper.TAR -rainbow/upper.TAR.GZ diff --git a/tests/gnu/empty_special.sh b/tests/gnu/empty_special.sh deleted file mode 100644 index 31e9d2e..0000000 --- a/tests/gnu/empty_special.sh +++ /dev/null @@ -1 +0,0 @@ -bfs_diff rainbow -empty -- cgit v1.2.3 From 03220724981ab4e4e2be239b65dec440f008f7da Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 Apr 2024 11:17:29 -0400 Subject: dstring: Add missing va_end() to dstrvcatf() error path --- src/dstring.c | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dstring.c b/src/dstring.c index 913dda8..b5bf3d3 100644 --- a/src/dstring.c +++ b/src/dstring.c @@ -245,6 +245,7 @@ int dstrvcatf(dchar **str, const char *format, va_list args) { return 0; fail: + va_end(copy); *tail = '\0'; return -1; } -- cgit v1.2.3 From 4a802da8d598c6fe3317e186b64a0b020cc96f01 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 Apr 2024 11:36:51 -0400 Subject: opt: Add missing NULL check in visit_shallow() visit_shallow() should propagate NULL, but look_up_visitor() dereferences expr to know which visitor to return. --- src/opt.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/opt.c b/src/opt.c index ffc795b..883d598 100644 --- a/src/opt.c +++ b/src/opt.c @@ -947,8 +947,12 @@ static struct bfs_expr *visit_shallow(struct bfs_opt *opt, struct bfs_expr *expr expr = general(opt, expr, visitor); } + if (!expr) { + return NULL; + } + visit_fn *specific = look_up_visitor(expr, visitor->table); - if (expr && specific) { + if (specific) { expr = specific(opt, expr, visitor); } -- cgit v1.2.3 From 98a026cf8b9d5da7730d5d4d1c88ba35d94771b2 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 Apr 2024 12:06:09 -0400 Subject: ctx: Escape paths when reporting errors in bfs_ctx_free() --- src/ctx.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ctx.c b/src/ctx.c index f5b28c7..aa73b35 100644 --- a/src/ctx.c +++ b/src/ctx.c @@ -237,7 +237,7 @@ int bfs_ctx_free(struct bfs_ctx *ctx) { if (bfs_ctx_fclose(ctx, ctx_file) != 0) { if (cerr) { - bfs_error(ctx, "'%s': %m.\n", ctx_file->path); + bfs_error(ctx, "%pq: %m.\n", ctx_file->path); } ret = -1; } -- cgit v1.2.3 From 3a72b93d26ad2bb06ea0b7d39257130084b1ee1e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 Apr 2024 12:47:12 -0400 Subject: printf: Refactor %y/%Y implementation --- src/bfstd.c | 68 ++++++++++++++++++++++++++++++------------------------------ src/bftw.h | 4 ++-- src/printf.c | 40 +++++++++++++++-------------------- 3 files changed, 53 insertions(+), 59 deletions(-) diff --git a/src/bfstd.c b/src/bfstd.c index 1144380..f8ce871 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -252,40 +252,6 @@ int ynprompt(void) { return ret; } -/** Get the single character describing the given file type. */ -static char type_char(mode_t mode) { - switch (mode & S_IFMT) { - case S_IFREG: - return '-'; - case S_IFBLK: - return 'b'; - case S_IFCHR: - return 'c'; - case S_IFDIR: - return 'd'; - case S_IFLNK: - return 'l'; - case S_IFIFO: - return 'p'; - case S_IFSOCK: - return 's'; -#ifdef S_IFDOOR - case S_IFDOOR: - return 'D'; -#endif -#ifdef S_IFPORT - case S_IFPORT: - return 'P'; -#endif -#ifdef S_IFWHT - case S_IFWHT: - return 'w'; -#endif - } - - return '?'; -} - void *xmemdup(const void *src, size_t size) { void *ret = malloc(size); if (ret) { @@ -356,6 +322,40 @@ const char *xstrerror(int errnum) { return ret; } +/** Get the single character describing the given file type. */ +static char type_char(mode_t mode) { + switch (mode & S_IFMT) { + case S_IFREG: + return '-'; + case S_IFBLK: + return 'b'; + case S_IFCHR: + return 'c'; + case S_IFDIR: + return 'd'; + case S_IFLNK: + return 'l'; + case S_IFIFO: + return 'p'; + case S_IFSOCK: + return 's'; +#ifdef S_IFDOOR + case S_IFDOOR: + return 'D'; +#endif +#ifdef S_IFPORT + case S_IFPORT: + return 'P'; +#endif +#ifdef S_IFWHT + case S_IFWHT: + return 'w'; +#endif + } + + return '?'; +} + void xstrmode(mode_t mode, char str[11]) { strcpy(str, "----------"); diff --git a/src/bftw.h b/src/bftw.h index 2805361..8656ca7 100644 --- a/src/bftw.h +++ b/src/bftw.h @@ -54,7 +54,7 @@ struct BFTW { /** The file type. */ enum bfs_type type; - /** The errno that occurred, if type == BFTW_ERROR. */ + /** The errno that occurred, if type == BFS_ERROR. */ int error; /** A parent file descriptor for the *at() family of calls. */ @@ -104,7 +104,7 @@ const struct bfs_stat *bftw_cached_stat(const struct BFTW *ftwbuf, enum bfs_stat * @param flags * flags for bfs_stat(). Pass ftwbuf->stat_flags for the default flags. * @return - * The type of the file, or BFTW_ERROR if an error occurred. + * The type of the file, or BFS_ERROR if an error occurred. */ enum bfs_type bftw_type(const struct BFTW *ftwbuf, enum bfs_stat_flags flags); diff --git a/src/printf.c b/src/printf.c index 4df399b..e39d756 100644 --- a/src/printf.c +++ b/src/printf.c @@ -520,10 +520,14 @@ static const char *bfs_printf_type(enum bfs_type type) { 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"; } @@ -537,34 +541,24 @@ static int bfs_printf_y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF /** %Y: target type */ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { - int error = 0; - - if (ftwbuf->type != BFS_LNK) { - return bfs_printf_y(cfile, fmt, ftwbuf); - } - - const char *type = "U"; + enum bfs_type type = bftw_type(ftwbuf, BFS_STAT_FOLLOW); + const char *str; - const struct bfs_stat *statbuf = bftw_stat(ftwbuf, BFS_STAT_FOLLOW); - if (statbuf) { - type = bfs_printf_type(bfs_mode_to_type(statbuf->mode)); - } else { - switch (errno) { - case ELOOP: - type = "L"; - break; - case ENOENT: - case ENOTDIR: - type = "N"; - break; - default: - type = "?"; + int error = 0; + if (type == BFS_ERROR) { + if (errno_is_like(ELOOP)) { + str = "L"; + } else if (errno_is_like(ENOENT)) { + str = "N"; + } else { + str = "?"; error = errno; - break; } + } else { + str = bfs_printf_type(type); } - int ret = dyn_fprintf(cfile->file, fmt, type); + int ret = dyn_fprintf(cfile->file, fmt, str); if (error != 0) { ret = -1; errno = error; -- cgit v1.2.3 From 32e50c2faa3e48acb2eae29c23feb5511df2f9ab Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 Apr 2024 13:17:18 -0400 Subject: printf: Check dynamic format strings more carefully --- src/prelude.h | 9 +++++++ src/printf.c | 80 +++++++++++++++++++++++++++++------------------------------ 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/src/prelude.h b/src/prelude.h index 7a646a7..ddeacbd 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -243,6 +243,15 @@ typedef union { # define attr_printf(fmt, args) #endif +/** + * Annotates functions that potentially modify and return format strings. + */ +#if __has_attribute(format_arg) +# define attr_format_arg(arg) __attribute__((format_arg(arg))) +#else +# define attr_format_arg(args) +#endif + /** * Annotates allocator-like functions. */ diff --git a/src/printf.c b/src/printf.c index e39d756..f8428f7 100644 --- a/src/printf.c +++ b/src/printf.c @@ -88,22 +88,20 @@ static bool should_color(CFILE *cfile, const struct bfs_fmt *fmt) { bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); \ (void)ret -/** - * Common entry point for fprintf() with a dynamic format string. - */ -static int dyn_fprintf(FILE *file, const struct bfs_fmt *fmt, ...) { - va_list args; - va_start(args, fmt); - -#if __GNUC__ -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wformat-nonliteral" -#endif - int ret = vfprintf(file, fmt->str, args); -#if __GNUC__ -# pragma GCC diagnostic pop -#endif +/** Return a dynamic format string. */ +attr(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); + return str; +} +/** Wrapper for fprintf(). */ +attr(printf(3, 4)) +static int bfs_fprintf(CFILE *cfile, const struct bfs_fmt *fmt, const char *fake, ...) { + va_list args; + va_start(args, fake); + int ret = vfprintf(cfile->file, dyn_fmt(fmt->str, fake), args); va_end(args); return ret; } @@ -139,7 +137,7 @@ static int bfs_printf_ctime(CFILE *cfile, const struct bfs_fmt *fmt, const struc (long)ts->tv_nsec, 1900 + tm.tm_year); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %A, %B/%W, %C, %T: strftime() */ @@ -214,7 +212,7 @@ static int bfs_printf_strftime(CFILE *cfile, const struct bfs_fmt *fmt, const st bfs_assert(ret >= 0 && (size_t)ret < sizeof(buf)); (void)ret; - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %b: blocks */ @@ -226,12 +224,12 @@ static int bfs_printf_b(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + 511) / 512; BFS_PRINTF_BUF(buf, "%ju", blocks); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %d: depth */ static int bfs_printf_d(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { - return dyn_fprintf(cfile->file, fmt, (intmax_t)ftwbuf->depth); + return bfs_fprintf(cfile, fmt, "%jd", (intmax_t)ftwbuf->depth); } /** %D: device */ @@ -242,7 +240,7 @@ static int bfs_printf_D(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->dev); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %f: file name */ @@ -250,7 +248,7 @@ static int bfs_printf_f(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF if (should_color(cfile, fmt)) { return cfprintf(cfile, "%pF", ftwbuf); } else { - return dyn_fprintf(cfile->file, fmt, ftwbuf->path + ftwbuf->nameoff); + return bfs_fprintf(cfile, fmt, "%s", ftwbuf->path + ftwbuf->nameoff); } } @@ -266,7 +264,7 @@ static int bfs_printf_F(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return -1; } - return dyn_fprintf(cfile->file, fmt, type); + return bfs_fprintf(cfile, fmt, "%s", type); } /** %G: gid */ @@ -277,7 +275,7 @@ static int bfs_printf_G(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->gid); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %g: group name */ @@ -293,7 +291,7 @@ static int bfs_printf_g(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return bfs_printf_G(cfile, fmt, ftwbuf); } - return dyn_fprintf(cfile->file, fmt, grp->gr_name); + return bfs_fprintf(cfile, fmt, "%s", grp->gr_name); } /** %h: leading directories */ @@ -322,7 +320,7 @@ static int bfs_printf_h(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF if (should_color(cfile, fmt)) { ret = cfprintf(cfile, "${di}%pQ${rs}", buf); } else { - ret = dyn_fprintf(cfile->file, fmt, buf); + ret = bfs_fprintf(cfile, fmt, "%s", buf); } free(copy); @@ -338,7 +336,7 @@ static int bfs_printf_H(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return cfprintf(cfile, "${di}%pQ${rs}", ftwbuf->root); } } else { - return dyn_fprintf(cfile->file, fmt, ftwbuf->root); + return bfs_fprintf(cfile, fmt, "%s", ftwbuf->root); } } @@ -350,7 +348,7 @@ static int bfs_printf_i(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->ino); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %k: 1K blocks */ @@ -362,7 +360,7 @@ static int bfs_printf_k(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF uintmax_t blocks = ((uintmax_t)statbuf->blocks * BFS_STAT_BLKSIZE + 1023) / 1024; BFS_PRINTF_BUF(buf, "%ju", blocks); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %l: link target */ @@ -384,7 +382,7 @@ static int bfs_printf_l(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } } - int ret = dyn_fprintf(cfile->file, fmt, target); + int ret = bfs_fprintf(cfile, fmt, "%s", target); free(buf); return ret; } @@ -396,7 +394,7 @@ static int bfs_printf_m(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return -1; } - return dyn_fprintf(cfile->file, fmt, (unsigned int)(statbuf->mode & 07777)); + return bfs_fprintf(cfile, fmt, "%o", (unsigned int)(statbuf->mode & 07777)); } /** %M: symbolic mode */ @@ -408,7 +406,7 @@ static int bfs_printf_M(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF char buf[11]; xstrmode(statbuf->mode, buf); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %n: link count */ @@ -419,7 +417,7 @@ static int bfs_printf_n(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->nlink); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %p: full path */ @@ -427,7 +425,7 @@ static int bfs_printf_p(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF if (should_color(cfile, fmt)) { return cfprintf(cfile, "%pP", ftwbuf); } else { - return dyn_fprintf(cfile->file, fmt, ftwbuf->path); + return bfs_fprintf(cfile, fmt, "%s", ftwbuf->path); } } @@ -448,7 +446,7 @@ static int bfs_printf_P(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF copybuf.nameoff -= offset; return cfprintf(cfile, "%pP", ©buf); } else { - return dyn_fprintf(cfile->file, fmt, ftwbuf->path + offset); + return bfs_fprintf(cfile, fmt, "%s", ftwbuf->path + offset); } } @@ -460,7 +458,7 @@ static int bfs_printf_s(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->size); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %S: sparseness */ @@ -476,7 +474,7 @@ static int bfs_printf_S(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } else { sparsity = (double)BFS_STAT_BLKSIZE * statbuf->blocks / statbuf->size; } - return dyn_fprintf(cfile->file, fmt, sparsity); + return bfs_fprintf(cfile, fmt, "%g", sparsity); } /** %U: uid */ @@ -487,7 +485,7 @@ static int bfs_printf_U(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF } BFS_PRINTF_BUF(buf, "%ju", (uintmax_t)statbuf->uid); - return dyn_fprintf(cfile->file, fmt, buf); + return bfs_fprintf(cfile, fmt, "%s", buf); } /** %u: user name */ @@ -503,7 +501,7 @@ static int bfs_printf_u(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return bfs_printf_U(cfile, fmt, ftwbuf); } - return dyn_fprintf(cfile->file, fmt, pwd->pw_name); + return bfs_fprintf(cfile, fmt, "%s", pwd->pw_name); } static const char *bfs_printf_type(enum bfs_type type) { @@ -536,7 +534,7 @@ static const char *bfs_printf_type(enum bfs_type type) { /** %y: type */ static int bfs_printf_y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BFTW *ftwbuf) { const char *type = bfs_printf_type(ftwbuf->type); - return dyn_fprintf(cfile->file, fmt, type); + return bfs_fprintf(cfile, fmt, "%s", type); } /** %Y: target type */ @@ -558,7 +556,7 @@ static int bfs_printf_Y(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF str = bfs_printf_type(type); } - int ret = dyn_fprintf(cfile->file, fmt, str); + int ret = bfs_fprintf(cfile, fmt, "%s", str); if (error != 0) { ret = -1; errno = error; @@ -574,7 +572,7 @@ static int bfs_printf_Z(CFILE *cfile, const struct bfs_fmt *fmt, const struct BF return -1; } - int ret = dyn_fprintf(cfile->file, fmt, con); + int ret = bfs_fprintf(cfile, fmt, "%s", con); bfs_freecon(con); return ret; } -- cgit v1.2.3 From 19189e3f6f18b13e4f9c947c99062e658e98827d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 24 Apr 2024 14:45:12 -0400 Subject: ioq: Fix uninitialized values in ioq_create() cleanup path I switched from ZALLOC_FLEX() to ALLOC_FLEX() in hopes that msan would catch uninitialized values in ioq_thread_create(), but in doing so, forgot to initialize all fields before the first goto fail. Fixes: f64f76b ("ioq: Copy ring_ops from the previous thread") --- src/ioq.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/ioq.c b/src/ioq.c index 189bdac..43a1b35 100644 --- a/src/ioq.c +++ b/src/ioq.c @@ -918,17 +918,14 @@ static void ioq_thread_join(struct ioq_thread *thread) { } struct ioq *ioq_create(size_t depth, size_t nthreads) { - struct ioq *ioq = ALLOC_FLEX(struct ioq, threads, nthreads); + struct ioq *ioq = ZALLOC_FLEX(struct ioq, threads, nthreads); if (!ioq) { goto fail; } ioq->depth = depth; - ioq->size = 0; - ioq->cancel = false; ARENA_INIT(&ioq->ents, struct ioq_ent); - #if BFS_USE_LIBURING && BFS_USE_STATX ARENA_INIT(&ioq->xbufs, struct statx); #endif -- cgit v1.2.3 From 339c20aea74d5b4c6b8b178b478cc5530bc9af79 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 25 Apr 2024 12:01:40 -0400 Subject: config: Add BFS_USE_LIB* to config.h instead of CPPFLAGS --- config/config.mk | 23 ++++------------------- config/deps.mk | 2 +- config/header.mk | 1 + config/pkg.mk | 23 ----------------------- config/pkg.sh | 26 -------------------------- config/pkgconf.sh | 12 ++++++++++++ config/pkgs.mk | 35 ++++++++++++++++++++++++++++++----- config/prelude.mk | 2 +- src/dir.h | 1 + 9 files changed, 50 insertions(+), 75 deletions(-) delete mode 100644 config/pkg.mk delete mode 100755 config/pkg.sh diff --git a/config/config.mk b/config/config.mk index 5750b49..280c6ac 100644 --- a/config/config.mk +++ b/config/config.mk @@ -44,34 +44,19 @@ ${GEN}/vars.mk:: # Sets the build flags. This depends on vars.mk and uses a recursive make so # that the default flags can depend on variables like ${OS}. ${GEN}/flags.mk: ${GEN}/vars.mk - @+${MAKE} -sf config/flags.mk + @+${MAKE} -sf config/flags.mk $@ .PHONY: ${GEN}/flags.mk # Check for dependency generation support ${GEN}/deps.mk: ${GEN}/flags.mk - @+${MAKE} -sf config/deps.mk + @+${MAKE} -sf config/deps.mk $@ .PHONY: ${GEN}/deps.mk -# External dependencies -PKG_MKS := \ - ${GEN}/libacl.mk \ - ${GEN}/libcap.mk \ - ${GEN}/libselinux.mk \ - ${GEN}/liburing.mk \ - ${GEN}/oniguruma.mk - # Auto-detect dependencies and their build flags -${GEN}/pkgs.mk: ${PKG_MKS} - @printf '# %s\n' "${TGT}" >$@ - @printf 'include $${GEN}/%s\n' ${.ALLSRC:${GEN}/%=%} >>$@ - @+${MAKE} -sf config/pkgs.mk +${GEN}/pkgs.mk: ${GEN}/flags.mk + @+${MAKE} -sf config/pkgs.mk $@ .PHONY: ${GEN}/pkgs.mk -# Auto-detect dependencies -${PKG_MKS}: ${GEN}/flags.mk - @+${MAKE} -sf config/pkg.mk TARGET=$@ -.PHONY: ${PKG_MKS} - # Compile-time feature detection ${GEN}/config.h: ${CONFIG} @+${MAKE} -sf config/header.mk $@ diff --git a/config/deps.mk b/config/deps.mk index 2201f06..ac394a5 100644 --- a/config/deps.mk +++ b/config/deps.mk @@ -15,4 +15,4 @@ ${GEN}/deps.mk:: printf 'DEPFLAGS = -MD -MP\n'; \ fi >>$@ 2>$@.log ${VCAT} $@ - @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ + printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ diff --git a/config/header.mk b/config/header.mk index 7fd2f78..ccc36d3 100644 --- a/config/header.mk +++ b/config/header.mk @@ -48,6 +48,7 @@ ${GEN}/config.h: ${HEADERS} printf '// %s\n' "${TGT}" >$@ printf '#ifndef BFS_CONFIG_H\n' >>$@ printf '#define BFS_CONFIG_H\n' >>$@ + printf '#define BFS_USE_%s true\n' $$(printf '%s\n' ${PKGS} | tr 'a-z-' 'A-Z_') >>$@ cat ${.ALLSRC} >>$@ printf '#endif // BFS_CONFIG_H\n' >>$@ cat ${.ALLSRC:%=%.log} >$@.log diff --git a/config/pkg.mk b/config/pkg.mk deleted file mode 100644 index fafe562..0000000 --- a/config/pkg.mk +++ /dev/null @@ -1,23 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/lib*.mk - -include config/prelude.mk -include ${GEN}/vars.mk -include ${GEN}/flags.mk -include config/exports.mk - -# Like ${TGT} but for ${TARGET}, not $@ -SHORT = ${TARGET:${BUILDDIR}/%=%} - -default:: - @printf '# %s\n' "${SHORT}" >${TARGET} - config/pkg.sh ${TARGET:${GEN}/%.mk=%} >>${TARGET} 2>${TARGET}.log - @if [ "${IS_V}" ]; then \ - cat ${TARGET}; \ - elif grep -q PKGS ${TARGET}; then \ - printf '[ GEN] %-${MSG_WIDTH}s ✔\n' ${SHORT}; \ - else \ - printf '[ GEN] %-${MSG_WIDTH}s ✘\n' ${SHORT}; \ - fi diff --git a/config/pkg.sh b/config/pkg.sh deleted file mode 100755 index 4ebea64..0000000 --- a/config/pkg.sh +++ /dev/null @@ -1,26 +0,0 @@ -#!/bin/sh - -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# pkg-config wrapper that outputs a makefile fragment - -set -eu - -NAME=$(printf '%s' "$1" | tr 'a-z' 'A-Z') -eval "XUSE=\"\${USE_$NAME:-}\"" - -if [ "$XUSE" ]; then - USE="$XUSE" -elif config/pkgconf.sh "$1"; then - USE=y -else - USE=n -fi - -if [ "$USE" = y ]; then - printf 'PKGS += %s\n' "$1" - printf 'CPPFLAGS += -DBFS_USE_%s=1\n' "$NAME" -else - printf 'CPPFLAGS += -DBFS_USE_%s=0\n' "$NAME" -fi diff --git a/config/pkgconf.sh b/config/pkgconf.sh index 2dbad76..2fb2f1e 100755 --- a/config/pkgconf.sh +++ b/config/pkgconf.sh @@ -26,6 +26,18 @@ esac if [ -z "$MODE" ]; then # Check whether the libraries exist at all for LIB; do + # Check ${USE_$LIB} + USE_LIB="USE_$(printf '%s' "$LIB" | tr 'a-z-' 'A-Z_')" + eval "USE=\"\${$USE_LIB:-}\"" + case "$USE" in + y|1) + continue + ;; + n|0) + exit 1 + ;; + esac + CFLAGS=$("$0" --cflags "$LIB") || exit 1 LDFLAGS=$("$0" --ldflags "$LIB") || exit 1 LDLIBS=$("$0" --ldlibs "$LIB") || exit 1 diff --git a/config/pkgs.mk b/config/pkgs.mk index 3a18289..2c100ab 100644 --- a/config/pkgs.mk +++ b/config/pkgs.mk @@ -6,12 +6,37 @@ include config/prelude.mk include ${GEN}/vars.mk include ${GEN}/flags.mk -include ${GEN}/pkgs.mk include config/exports.mk -${GEN}/pkgs.mk:: +# External dependencies +USE_PKGS := \ + ${GEN}/libacl.use \ + ${GEN}/libcap.use \ + ${GEN}/libselinux.use \ + ${GEN}/liburing.use \ + ${GEN}/oniguruma.use + +${GEN}/pkgs.mk: ${USE_PKGS} ${MSG} "[ GEN] ${TGT}" - printf 'CFLAGS += %s\n' "$$(config/pkgconf.sh --cflags ${PKGS})" >>$@ 2>>$@.log - printf 'LDFLAGS += %s\n' "$$(config/pkgconf.sh --ldflags ${PKGS})" >>$@ 2>>$@.log - printf 'LDLIBS := %s $${LDLIBS}\n' "$$(config/pkgconf.sh --ldlibs ${PKGS})" >>$@ 2>>$@.log + printf '# %s\n' "${TGT}" >$@ + gen() { \ + printf 'PKGS := %s\n' "$$*"; \ + printf 'CFLAGS += %s\n' "$$(config/pkgconf.sh --cflags "$$@")"; \ + printf 'LDFLAGS += %s\n' "$$(config/pkgconf.sh --ldflags "$$@")"; \ + printf 'LDLIBS := %s $${LDLIBS}\n' "$$(config/pkgconf.sh --ldlibs "$$@")"; \ + }; \ + gen $$(cat ${.ALLSRC}) >>$@ ${VCAT} $@ +.PHONY: ${GEN}/pkgs.mk + +# Convert ${GEN}/foo.use to foo +PKG = ${@:${GEN}/%.use=%} + +${USE_PKGS}:: + if config/pkgconf.sh ${PKG} 2>$@.log; then \ + printf '%s\n' ${PKG} >$@; \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' config/${PKG}.c; \ + else \ + : >$@; \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' config/${PKG}.c; \ + fi diff --git a/config/prelude.mk b/config/prelude.mk index b9bc61b..e1e7a4d 100644 --- a/config/prelude.mk +++ b/config/prelude.mk @@ -109,7 +109,7 @@ MSG = @msg() { \ }; \ msg -# Maximum width of a short message, to align the [X] +# Maximum width of a short message, to align the ✔/✘ MSG_WIDTH := 33 # cat a file if V=1 diff --git a/src/dir.h b/src/dir.h index bc6c4ed..6d5c9c5 100644 --- a/src/dir.h +++ b/src/dir.h @@ -8,6 +8,7 @@ #ifndef BFS_DIR_H #define BFS_DIR_H +#include "prelude.h" #include /** -- cgit v1.2.3 From 83dd04452498408c1d46b7282ed08e9f5a1e76a8 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 25 Apr 2024 13:49:07 -0400 Subject: bench: Run make config when building --- bench/bench.sh | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bench/bench.sh b/bench/bench.sh index e4b5511..1d379ca 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -221,7 +221,8 @@ setup() { fi echo "Building bfs ..." - as-user make -s -j"$nproc" release all + as-user make -s -j"$nproc" RELEASE=y + as-user make -s -j"$nproc" all as-user mkdir -p bench/corpus @@ -253,7 +254,13 @@ setup() { echo "Building bfs $commit ..." cd "$worktree" as-user git checkout -qd "$commit" -- - as-user make -s -j"$nproc" release + ls + if [ -e config ]; then + as-user make -s -j"$nproc" config RELEASE=1 + as-user make -s -j"$nproc" + else + as-user make -s -j"$nproc" release + fi if [ -e ./bin/bfs ]; then as-user cp ./bin/bfs "$bin/bfs-$commit" else -- cgit v1.2.3 From e061b6da5ee46a334169d4e6a52e49857ae22f55 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 26 Apr 2024 17:42:44 -0400 Subject: config: Move .c files into config/{use,has} subdirectories --- config/acl-get-entry.c | 8 ---- config/acl-get-file.c | 8 ---- config/acl-get-tag-type.c | 10 ---- config/acl-is-trivial-np.c | 12 ----- config/acl-trivial.c | 8 ---- config/aligned-alloc.c | 8 ---- config/cc-define.sh | 19 -------- config/confstr.c | 9 ---- config/define-if.sh | 20 ++++++++ config/extattr-get-file.c | 10 ---- config/extattr-get-link.c | 10 ---- config/extattr-list-file.c | 10 ---- config/extattr-list-link.c | 10 ---- config/fdclosedir.c | 8 ---- config/getdents.c | 10 ---- config/getdents64-syscall.c | 12 ----- config/getdents64.c | 10 ---- config/getprogname-gnu.c | 9 ---- config/getprogname.c | 9 ---- config/has/acl-get-entry.c | 8 ++++ config/has/acl-get-file.c | 8 ++++ config/has/acl-get-tag-type.c | 10 ++++ config/has/acl-is-trivial-np.c | 12 +++++ config/has/acl-trivial.c | 8 ++++ config/has/aligned-alloc.c | 8 ++++ config/has/confstr.c | 9 ++++ config/has/extattr-get-file.c | 10 ++++ config/has/extattr-get-link.c | 10 ++++ config/has/extattr-list-file.c | 10 ++++ config/has/extattr-list-link.c | 10 ++++ config/has/fdclosedir.c | 8 ++++ config/has/getdents.c | 10 ++++ config/has/getdents64-syscall.c | 12 +++++ config/has/getdents64.c | 10 ++++ config/has/getprogname-gnu.c | 9 ++++ config/has/getprogname.c | 9 ++++ config/has/max-align-t.c | 8 ++++ config/has/pipe2.c | 10 ++++ config/has/posix-spawn-addfchdir-np.c | 11 +++++ config/has/posix-spawn-addfchdir.c | 11 +++++ config/has/st-acmtim.c | 12 +++++ config/has/st-acmtimespec.c | 12 +++++ config/has/st-birthtim.c | 9 ++++ config/has/st-birthtimespec.c | 9 ++++ config/has/st-flags.c | 9 ++++ config/has/statx-syscall.c | 13 +++++ config/has/statx.c | 11 +++++ config/has/strerror-l.c | 11 +++++ config/has/strerror-r-gnu.c | 11 +++++ config/has/strerror-r-posix.c | 11 +++++ config/has/tm-gmtoff.c | 9 ++++ config/has/uselocale.c | 9 ++++ config/header.mk | 89 +++++++++++++++++------------------ config/libacl.c | 9 ---- config/libcap.c | 9 ---- config/libselinux.c | 9 ---- config/liburing.c | 9 ---- config/max-align-t.c | 8 ---- config/oniguruma.c | 9 ---- config/pipe2.c | 10 ---- config/pkgconf.sh | 2 +- config/pkgs.mk | 28 +++++------ config/posix-spawn-addfchdir-np.c | 11 ----- config/posix-spawn-addfchdir.c | 11 ----- config/prelude.mk | 10 +++- config/st-acmtim.c | 12 ----- config/st-acmtimespec.c | 12 ----- config/st-birthtim.c | 9 ---- config/st-birthtimespec.c | 9 ---- config/st-flags.c | 9 ---- config/statx-syscall.c | 13 ----- config/statx.c | 11 ----- config/strerror-l.c | 11 ----- config/strerror-r-gnu.c | 11 ----- config/strerror-r-posix.c | 11 ----- config/tm-gmtoff.c | 9 ---- config/use/libacl.c | 9 ++++ config/use/libcap.c | 9 ++++ config/use/libselinux.c | 9 ++++ config/use/liburing.c | 9 ++++ config/use/oniguruma.c | 9 ++++ config/uselocale.c | 9 ---- 82 files changed, 457 insertions(+), 455 deletions(-) delete mode 100644 config/acl-get-entry.c delete mode 100644 config/acl-get-file.c delete mode 100644 config/acl-get-tag-type.c delete mode 100644 config/acl-is-trivial-np.c delete mode 100644 config/acl-trivial.c delete mode 100644 config/aligned-alloc.c delete mode 100755 config/cc-define.sh delete mode 100644 config/confstr.c create mode 100755 config/define-if.sh delete mode 100644 config/extattr-get-file.c delete mode 100644 config/extattr-get-link.c delete mode 100644 config/extattr-list-file.c delete mode 100644 config/extattr-list-link.c delete mode 100644 config/fdclosedir.c delete mode 100644 config/getdents.c delete mode 100644 config/getdents64-syscall.c delete mode 100644 config/getdents64.c delete mode 100644 config/getprogname-gnu.c delete mode 100644 config/getprogname.c create mode 100644 config/has/acl-get-entry.c create mode 100644 config/has/acl-get-file.c create mode 100644 config/has/acl-get-tag-type.c create mode 100644 config/has/acl-is-trivial-np.c create mode 100644 config/has/acl-trivial.c create mode 100644 config/has/aligned-alloc.c create mode 100644 config/has/confstr.c create mode 100644 config/has/extattr-get-file.c create mode 100644 config/has/extattr-get-link.c create mode 100644 config/has/extattr-list-file.c create mode 100644 config/has/extattr-list-link.c create mode 100644 config/has/fdclosedir.c create mode 100644 config/has/getdents.c create mode 100644 config/has/getdents64-syscall.c create mode 100644 config/has/getdents64.c create mode 100644 config/has/getprogname-gnu.c create mode 100644 config/has/getprogname.c create mode 100644 config/has/max-align-t.c create mode 100644 config/has/pipe2.c create mode 100644 config/has/posix-spawn-addfchdir-np.c create mode 100644 config/has/posix-spawn-addfchdir.c create mode 100644 config/has/st-acmtim.c create mode 100644 config/has/st-acmtimespec.c create mode 100644 config/has/st-birthtim.c create mode 100644 config/has/st-birthtimespec.c create mode 100644 config/has/st-flags.c create mode 100644 config/has/statx-syscall.c create mode 100644 config/has/statx.c create mode 100644 config/has/strerror-l.c create mode 100644 config/has/strerror-r-gnu.c create mode 100644 config/has/strerror-r-posix.c create mode 100644 config/has/tm-gmtoff.c create mode 100644 config/has/uselocale.c delete mode 100644 config/libacl.c delete mode 100644 config/libcap.c delete mode 100644 config/libselinux.c delete mode 100644 config/liburing.c delete mode 100644 config/max-align-t.c delete mode 100644 config/oniguruma.c delete mode 100644 config/pipe2.c delete mode 100644 config/posix-spawn-addfchdir-np.c delete mode 100644 config/posix-spawn-addfchdir.c delete mode 100644 config/st-acmtim.c delete mode 100644 config/st-acmtimespec.c delete mode 100644 config/st-birthtim.c delete mode 100644 config/st-birthtimespec.c delete mode 100644 config/st-flags.c delete mode 100644 config/statx-syscall.c delete mode 100644 config/statx.c delete mode 100644 config/strerror-l.c delete mode 100644 config/strerror-r-gnu.c delete mode 100644 config/strerror-r-posix.c delete mode 100644 config/tm-gmtoff.c create mode 100644 config/use/libacl.c create mode 100644 config/use/libcap.c create mode 100644 config/use/libselinux.c create mode 100644 config/use/liburing.c create mode 100644 config/use/oniguruma.c delete mode 100644 config/uselocale.c diff --git a/config/acl-get-entry.c b/config/acl-get-entry.c deleted file mode 100644 index 3cce771..0000000 --- a/config/acl-get-entry.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include - -int main(void) { - acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); - acl_entry_t entry; - return acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); -} diff --git a/config/acl-get-file.c b/config/acl-get-file.c deleted file mode 100644 index 89fbf23..0000000 --- a/config/acl-get-file.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include -#include - -int main(void) { - acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); - return acl == (acl_t)NULL; -} diff --git a/config/acl-get-tag-type.c b/config/acl-get-tag-type.c deleted file mode 100644 index 2901956..0000000 --- a/config/acl-get-tag-type.c +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include -#include - -int main(void) { - acl_entry_t entry; - memset(&entry, 0, sizeof(entry)); - acl_tag_t tag; - return acl_get_tag_type(entry, &tag); -} diff --git a/config/acl-is-trivial-np.c b/config/acl-is-trivial-np.c deleted file mode 100644 index 9ca9fc7..0000000 --- a/config/acl-is-trivial-np.c +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - acl_t acl = acl_get_fd(3); - int trivial; - acl_is_trivial_np(acl, &trivial); - return 0; -} diff --git a/config/acl-trivial.c b/config/acl-trivial.c deleted file mode 100644 index 7efc838..0000000 --- a/config/acl-trivial.c +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - return acl_trivial("."); -} diff --git a/config/aligned-alloc.c b/config/aligned-alloc.c deleted file mode 100644 index 4460038..0000000 --- a/config/aligned-alloc.c +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - return !aligned_alloc(_Alignof(void *), sizeof(void *)); -} diff --git a/config/cc-define.sh b/config/cc-define.sh deleted file mode 100755 index edb5c87..0000000 --- a/config/cc-define.sh +++ /dev/null @@ -1,19 +0,0 @@ -#!/bin/sh - -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Output a C preprocessor definition based on whether a C source file could be -# compiled successfully - -set -eu - -SLUG="${1#config/}" -SLUG="${SLUG%.c}" -MACRO="BFS_HAS_$(printf '%s' "$SLUG" | tr 'a-z-' 'A-Z_')" - -if config/cc.sh "$1"; then - printf '#define %s true\n' "$MACRO" -else - printf '#define %s false\n' "$MACRO" -fi diff --git a/config/confstr.c b/config/confstr.c deleted file mode 100644 index 58280b4..0000000 --- a/config/confstr.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - confstr(_CS_PATH, NULL, 0); - return 0; -} diff --git a/config/define-if.sh b/config/define-if.sh new file mode 100755 index 0000000..059c1ac --- /dev/null +++ b/config/define-if.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Output a C preprocessor definition based on whether a command succeeds + +set -eu + +SLUG="${1#config/}" +SLUG="${SLUG%.c}" +MACRO="BFS_$(printf '%s' "$SLUG" | tr '/a-z-' '_A-Z_')" +shift + +if "$@"; then + printf '#define %s true\n' "$MACRO" +else + printf '#define %s false\n' "$MACRO" + exit 1 +fi diff --git a/config/extattr-get-file.c b/config/extattr-get-file.c deleted file mode 100644 index ac9cf96..0000000 --- a/config/extattr-get-file.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - return extattr_get_file("file", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); -} diff --git a/config/extattr-get-link.c b/config/extattr-get-link.c deleted file mode 100644 index c35be5b..0000000 --- a/config/extattr-get-link.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - return extattr_get_link("link", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); -} diff --git a/config/extattr-list-file.c b/config/extattr-list-file.c deleted file mode 100644 index e68a8bb..0000000 --- a/config/extattr-list-file.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - return extattr_list_file("file", EXTATTR_NAMESPACE_USER, NULL, 0); -} diff --git a/config/extattr-list-link.c b/config/extattr-list-link.c deleted file mode 100644 index 49f0ec2..0000000 --- a/config/extattr-list-link.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - return extattr_list_link("link", EXTATTR_NAMESPACE_USER, NULL, 0); -} diff --git a/config/fdclosedir.c b/config/fdclosedir.c deleted file mode 100644 index f4ad1f5..0000000 --- a/config/fdclosedir.c +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - return fdclosedir(opendir(".")); -} diff --git a/config/getdents.c b/config/getdents.c deleted file mode 100644 index d0d4228..0000000 --- a/config/getdents.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct dirent de; - getdents(3, &de, 1024); - return 0; -} diff --git a/config/getdents64-syscall.c b/config/getdents64-syscall.c deleted file mode 100644 index 4838c14..0000000 --- a/config/getdents64-syscall.c +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - struct dirent64 de; - syscall(SYS_getdents64, 3, &de, 1024); - return 0; -} diff --git a/config/getdents64.c b/config/getdents64.c deleted file mode 100644 index 1abf36d..0000000 --- a/config/getdents64.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct dirent64 de; - getdents64(3, &de, 1024); - return 0; -} diff --git a/config/getprogname-gnu.c b/config/getprogname-gnu.c deleted file mode 100644 index 6b97c5e..0000000 --- a/config/getprogname-gnu.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - const char *str = program_invocation_short_name; - return str[0]; -} diff --git a/config/getprogname.c b/config/getprogname.c deleted file mode 100644 index 83dc8e8..0000000 --- a/config/getprogname.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - const char *str = getprogname(); - return str[0]; -} diff --git a/config/has/acl-get-entry.c b/config/has/acl-get-entry.c new file mode 100644 index 0000000..3cce771 --- /dev/null +++ b/config/has/acl-get-entry.c @@ -0,0 +1,8 @@ +#include +#include + +int main(void) { + acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); + acl_entry_t entry; + return acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); +} diff --git a/config/has/acl-get-file.c b/config/has/acl-get-file.c new file mode 100644 index 0000000..89fbf23 --- /dev/null +++ b/config/has/acl-get-file.c @@ -0,0 +1,8 @@ +#include +#include +#include + +int main(void) { + acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); + return acl == (acl_t)NULL; +} diff --git a/config/has/acl-get-tag-type.c b/config/has/acl-get-tag-type.c new file mode 100644 index 0000000..2901956 --- /dev/null +++ b/config/has/acl-get-tag-type.c @@ -0,0 +1,10 @@ +#include +#include +#include + +int main(void) { + acl_entry_t entry; + memset(&entry, 0, sizeof(entry)); + acl_tag_t tag; + return acl_get_tag_type(entry, &tag); +} diff --git a/config/has/acl-is-trivial-np.c b/config/has/acl-is-trivial-np.c new file mode 100644 index 0000000..9ca9fc7 --- /dev/null +++ b/config/has/acl-is-trivial-np.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + acl_t acl = acl_get_fd(3); + int trivial; + acl_is_trivial_np(acl, &trivial); + return 0; +} diff --git a/config/has/acl-trivial.c b/config/has/acl-trivial.c new file mode 100644 index 0000000..7efc838 --- /dev/null +++ b/config/has/acl-trivial.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return acl_trivial("."); +} diff --git a/config/has/aligned-alloc.c b/config/has/aligned-alloc.c new file mode 100644 index 0000000..4460038 --- /dev/null +++ b/config/has/aligned-alloc.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return !aligned_alloc(_Alignof(void *), sizeof(void *)); +} diff --git a/config/has/confstr.c b/config/has/confstr.c new file mode 100644 index 0000000..58280b4 --- /dev/null +++ b/config/has/confstr.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + confstr(_CS_PATH, NULL, 0); + return 0; +} diff --git a/config/has/extattr-get-file.c b/config/has/extattr-get-file.c new file mode 100644 index 0000000..ac9cf96 --- /dev/null +++ b/config/has/extattr-get-file.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_get_file("file", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); +} diff --git a/config/has/extattr-get-link.c b/config/has/extattr-get-link.c new file mode 100644 index 0000000..c35be5b --- /dev/null +++ b/config/has/extattr-get-link.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_get_link("link", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); +} diff --git a/config/has/extattr-list-file.c b/config/has/extattr-list-file.c new file mode 100644 index 0000000..e68a8bb --- /dev/null +++ b/config/has/extattr-list-file.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_list_file("file", EXTATTR_NAMESPACE_USER, NULL, 0); +} diff --git a/config/has/extattr-list-link.c b/config/has/extattr-list-link.c new file mode 100644 index 0000000..49f0ec2 --- /dev/null +++ b/config/has/extattr-list-link.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_list_link("link", EXTATTR_NAMESPACE_USER, NULL, 0); +} diff --git a/config/has/fdclosedir.c b/config/has/fdclosedir.c new file mode 100644 index 0000000..f4ad1f5 --- /dev/null +++ b/config/has/fdclosedir.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return fdclosedir(opendir(".")); +} diff --git a/config/has/getdents.c b/config/has/getdents.c new file mode 100644 index 0000000..d0d4228 --- /dev/null +++ b/config/has/getdents.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct dirent de; + getdents(3, &de, 1024); + return 0; +} diff --git a/config/has/getdents64-syscall.c b/config/has/getdents64-syscall.c new file mode 100644 index 0000000..4838c14 --- /dev/null +++ b/config/has/getdents64-syscall.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + struct dirent64 de; + syscall(SYS_getdents64, 3, &de, 1024); + return 0; +} diff --git a/config/has/getdents64.c b/config/has/getdents64.c new file mode 100644 index 0000000..1abf36d --- /dev/null +++ b/config/has/getdents64.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct dirent64 de; + getdents64(3, &de, 1024); + return 0; +} diff --git a/config/has/getprogname-gnu.c b/config/has/getprogname-gnu.c new file mode 100644 index 0000000..6b97c5e --- /dev/null +++ b/config/has/getprogname-gnu.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + const char *str = program_invocation_short_name; + return str[0]; +} diff --git a/config/has/getprogname.c b/config/has/getprogname.c new file mode 100644 index 0000000..83dc8e8 --- /dev/null +++ b/config/has/getprogname.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + const char *str = getprogname(); + return str[0]; +} diff --git a/config/has/max-align-t.c b/config/has/max-align-t.c new file mode 100644 index 0000000..96165ce --- /dev/null +++ b/config/has/max-align-t.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return _Alignof(max_align_t); +} diff --git a/config/has/pipe2.c b/config/has/pipe2.c new file mode 100644 index 0000000..4cb43b5 --- /dev/null +++ b/config/has/pipe2.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + int fds[2]; + return pipe2(fds, O_CLOEXEC); +} diff --git a/config/has/posix-spawn-addfchdir-np.c b/config/has/posix-spawn-addfchdir-np.c new file mode 100644 index 0000000..b870a53 --- /dev/null +++ b/config/has/posix-spawn-addfchdir-np.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addfchdir_np(&actions, 3); + return 0; +} diff --git a/config/has/posix-spawn-addfchdir.c b/config/has/posix-spawn-addfchdir.c new file mode 100644 index 0000000..c52ff81 --- /dev/null +++ b/config/has/posix-spawn-addfchdir.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addfchdir(&actions, 3); + return 0; +} diff --git a/config/has/st-acmtim.c b/config/has/st-acmtim.c new file mode 100644 index 0000000..d687ab0 --- /dev/null +++ b/config/has/st-acmtim.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + unsigned int a = sb.st_atim.tv_sec; + unsigned int c = sb.st_ctim.tv_sec; + unsigned int m = sb.st_mtim.tv_sec; + return a + c + m; +} diff --git a/config/has/st-acmtimespec.c b/config/has/st-acmtimespec.c new file mode 100644 index 0000000..f747bc0 --- /dev/null +++ b/config/has/st-acmtimespec.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + unsigned int a = sb.st_atimespec.tv_sec; + unsigned int c = sb.st_ctimespec.tv_sec; + unsigned int m = sb.st_mtimespec.tv_sec; + return a + c + m; +} diff --git a/config/has/st-birthtim.c b/config/has/st-birthtim.c new file mode 100644 index 0000000..4964571 --- /dev/null +++ b/config/has/st-birthtim.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_birthtim.tv_sec; +} diff --git a/config/has/st-birthtimespec.c b/config/has/st-birthtimespec.c new file mode 100644 index 0000000..91a613f --- /dev/null +++ b/config/has/st-birthtimespec.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_birthtimespec.tv_sec; +} diff --git a/config/has/st-flags.c b/config/has/st-flags.c new file mode 100644 index 0000000..b1d0c32 --- /dev/null +++ b/config/has/st-flags.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_flags; +} diff --git a/config/has/statx-syscall.c b/config/has/statx-syscall.c new file mode 100644 index 0000000..87ec869 --- /dev/null +++ b/config/has/statx-syscall.c @@ -0,0 +1,13 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include +#include + +int main(void) { + struct statx sb; + syscall(SYS_statx, AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); + return 0; +} diff --git a/config/has/statx.c b/config/has/statx.c new file mode 100644 index 0000000..65f1674 --- /dev/null +++ b/config/has/statx.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + struct statx sb; + statx(AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); + return 0; +} diff --git a/config/has/strerror-l.c b/config/has/strerror-l.c new file mode 100644 index 0000000..3dcc4d7 --- /dev/null +++ b/config/has/strerror-l.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + locale_t locale = duplocale(LC_GLOBAL_LOCALE); + return !strerror_l(ENOMEM, locale); +} diff --git a/config/has/strerror-r-gnu.c b/config/has/strerror-r-gnu.c new file mode 100644 index 0000000..26ca0ee --- /dev/null +++ b/config/has/strerror-r-gnu.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + char buf[256]; + // Check that strerror_r() returns a pointer + return *strerror_r(ENOMEM, buf, sizeof(buf)); +} diff --git a/config/has/strerror-r-posix.c b/config/has/strerror-r-posix.c new file mode 100644 index 0000000..41b2d30 --- /dev/null +++ b/config/has/strerror-r-posix.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + char buf[256]; + // Check that strerror_r() returns an integer + return 2 * strerror_r(ENOMEM, buf, sizeof(buf)); +} diff --git a/config/has/tm-gmtoff.c b/config/has/tm-gmtoff.c new file mode 100644 index 0000000..543df48 --- /dev/null +++ b/config/has/tm-gmtoff.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct tm tm = {0}; + return tm.tm_gmtoff; +} diff --git a/config/has/uselocale.c b/config/has/uselocale.c new file mode 100644 index 0000000..a712ff8 --- /dev/null +++ b/config/has/uselocale.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + locale_t locale = uselocale((locale_t)0); + return locale == LC_GLOBAL_LOCALE; +} diff --git a/config/header.mk b/config/header.mk index ccc36d3..50ad5e8 100644 --- a/config/header.mk +++ b/config/header.mk @@ -9,62 +9,61 @@ include config/exports.mk # All header fragments we generate HEADERS := \ - ${GEN}/acl-get-entry.h \ - ${GEN}/acl-get-file.h \ - ${GEN}/acl-get-tag-type.h \ - ${GEN}/acl-is-trivial-np.h \ - ${GEN}/acl-trivial.h \ - ${GEN}/aligned-alloc.h \ - ${GEN}/confstr.h \ - ${GEN}/extattr-get-file.h \ - ${GEN}/extattr-get-link.h \ - ${GEN}/extattr-list-file.h \ - ${GEN}/extattr-list-link.h \ - ${GEN}/fdclosedir.h \ - ${GEN}/getdents.h \ - ${GEN}/getdents64.h \ - ${GEN}/getdents64-syscall.h \ - ${GEN}/getprogname.h \ - ${GEN}/getprogname-gnu.h \ - ${GEN}/max-align-t.h \ - ${GEN}/pipe2.h \ - ${GEN}/posix-spawn-addfchdir.h \ - ${GEN}/posix-spawn-addfchdir-np.h \ - ${GEN}/st-acmtim.h \ - ${GEN}/st-acmtimespec.h \ - ${GEN}/st-birthtim.h \ - ${GEN}/st-birthtimespec.h \ - ${GEN}/st-flags.h \ - ${GEN}/statx.h \ - ${GEN}/statx-syscall.h \ - ${GEN}/strerror-l.h \ - ${GEN}/strerror-r-gnu.h \ - ${GEN}/strerror-r-posix.h \ - ${GEN}/tm-gmtoff.h \ - ${GEN}/uselocale.h + ${GEN}/has/acl-get-entry.h \ + ${GEN}/has/acl-get-file.h \ + ${GEN}/has/acl-get-tag-type.h \ + ${GEN}/has/acl-is-trivial-np.h \ + ${GEN}/has/acl-trivial.h \ + ${GEN}/has/aligned-alloc.h \ + ${GEN}/has/confstr.h \ + ${GEN}/has/extattr-get-file.h \ + ${GEN}/has/extattr-get-link.h \ + ${GEN}/has/extattr-list-file.h \ + ${GEN}/has/extattr-list-link.h \ + ${GEN}/has/fdclosedir.h \ + ${GEN}/has/getdents.h \ + ${GEN}/has/getdents64.h \ + ${GEN}/has/getdents64-syscall.h \ + ${GEN}/has/getprogname.h \ + ${GEN}/has/getprogname-gnu.h \ + ${GEN}/has/max-align-t.h \ + ${GEN}/has/pipe2.h \ + ${GEN}/has/posix-spawn-addfchdir.h \ + ${GEN}/has/posix-spawn-addfchdir-np.h \ + ${GEN}/has/st-acmtim.h \ + ${GEN}/has/st-acmtimespec.h \ + ${GEN}/has/st-birthtim.h \ + ${GEN}/has/st-birthtimespec.h \ + ${GEN}/has/st-flags.h \ + ${GEN}/has/statx.h \ + ${GEN}/has/statx-syscall.h \ + ${GEN}/has/strerror-l.h \ + ${GEN}/has/strerror-r-gnu.h \ + ${GEN}/has/strerror-r-posix.h \ + ${GEN}/has/tm-gmtoff.h \ + ${GEN}/has/uselocale.h -${GEN}/config.h: ${HEADERS} +# Previously generated by pkgs.mk +PKG_HEADERS := ${ALL_PKGS:%=${GEN}/use/%.h} + +${GEN}/config.h: ${PKG_HEADERS} ${HEADERS} ${MSG} "[ GEN] ${TGT}" printf '// %s\n' "${TGT}" >$@ printf '#ifndef BFS_CONFIG_H\n' >>$@ printf '#define BFS_CONFIG_H\n' >>$@ - printf '#define BFS_USE_%s true\n' $$(printf '%s\n' ${PKGS} | tr 'a-z-' 'A-Z_') >>$@ cat ${.ALLSRC} >>$@ printf '#endif // BFS_CONFIG_H\n' >>$@ cat ${.ALLSRC:%=%.log} >$@.log - ${RM} ${.ALLSRC} ${.ALLSRC:%=%.log} ${VCAT} $@ .PHONY: ${GEN}/config.h -# The C source file to attempt to compile -CSRC = ${@:${GEN}/%.h=config/%.c} +# The short name of the config test +SLUG = ${@:${GEN}/%.h=%} ${HEADERS}:: - config/cc-define.sh ${CSRC} >$@ 2>$@.log - if ! [ "${IS_V}" ]; then \ - if grep -q 'true$$' $@; then \ - printf '[ CC ] %-${MSG_WIDTH}s ✔\n' ${CSRC}; \ - else \ - printf '[ CC ] %-${MSG_WIDTH}s ✘\n' ${CSRC}; \ - fi; \ + ${MKDIR} ${@D} + if config/define-if.sh ${SLUG} config/cc.sh config/${SLUG}.c >$@ 2>$@.log; then \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' ${SLUG}.c; \ + else \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' ${SLUG}.c; \ fi diff --git a/config/libacl.c b/config/libacl.c deleted file mode 100644 index de1fe50..0000000 --- a/config/libacl.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - acl_free(0); - return 0; -} diff --git a/config/libcap.c b/config/libcap.c deleted file mode 100644 index 58e832c..0000000 --- a/config/libcap.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - cap_free(0); - return 0; -} diff --git a/config/libselinux.c b/config/libselinux.c deleted file mode 100644 index bca409d..0000000 --- a/config/libselinux.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - freecon(0); - return 0; -} diff --git a/config/liburing.c b/config/liburing.c deleted file mode 100644 index bea499a..0000000 --- a/config/liburing.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - io_uring_free_probe(0); - return 0; -} diff --git a/config/max-align-t.c b/config/max-align-t.c deleted file mode 100644 index 96165ce..0000000 --- a/config/max-align-t.c +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - return _Alignof(max_align_t); -} diff --git a/config/oniguruma.c b/config/oniguruma.c deleted file mode 100644 index cb17596..0000000 --- a/config/oniguruma.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - onig_free(0); - return 0; -} diff --git a/config/pipe2.c b/config/pipe2.c deleted file mode 100644 index 4cb43b5..0000000 --- a/config/pipe2.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - int fds[2]; - return pipe2(fds, O_CLOEXEC); -} diff --git a/config/pkgconf.sh b/config/pkgconf.sh index 2fb2f1e..365ed61 100755 --- a/config/pkgconf.sh +++ b/config/pkgconf.sh @@ -41,7 +41,7 @@ if [ -z "$MODE" ]; then CFLAGS=$("$0" --cflags "$LIB") || exit 1 LDFLAGS=$("$0" --ldflags "$LIB") || exit 1 LDLIBS=$("$0" --ldlibs "$LIB") || exit 1 - config/cc.sh $CFLAGS $LDFLAGS config/$LIB.c $LDLIBS || exit 1 + config/cc.sh $CFLAGS $LDFLAGS config/use/$LIB.c $LDLIBS || exit 1 done fi diff --git a/config/pkgs.mk b/config/pkgs.mk index 2c100ab..898f262 100644 --- a/config/pkgs.mk +++ b/config/pkgs.mk @@ -8,15 +8,9 @@ include ${GEN}/vars.mk include ${GEN}/flags.mk include config/exports.mk -# External dependencies -USE_PKGS := \ - ${GEN}/libacl.use \ - ${GEN}/libcap.use \ - ${GEN}/libselinux.use \ - ${GEN}/liburing.use \ - ${GEN}/oniguruma.use +HEADERS := ${ALL_PKGS:%=${GEN}/use/%.h} -${GEN}/pkgs.mk: ${USE_PKGS} +${GEN}/pkgs.mk: ${HEADERS} ${MSG} "[ GEN] ${TGT}" printf '# %s\n' "${TGT}" >$@ gen() { \ @@ -25,18 +19,18 @@ ${GEN}/pkgs.mk: ${USE_PKGS} printf 'LDFLAGS += %s\n' "$$(config/pkgconf.sh --ldflags "$$@")"; \ printf 'LDLIBS := %s $${LDLIBS}\n' "$$(config/pkgconf.sh --ldlibs "$$@")"; \ }; \ - gen $$(cat ${.ALLSRC}) >>$@ + gen $$(grep -l ' true$$' ${.ALLSRC} | sed 's|.*/\(.*\)\.h|\1|') >>$@ ${VCAT} $@ + .PHONY: ${GEN}/pkgs.mk -# Convert ${GEN}/foo.use to foo -PKG = ${@:${GEN}/%.use=%} +# Convert ${GEN}/use/foo.h to foo +PKG = ${@:${GEN}/use/%.h=%} -${USE_PKGS}:: - if config/pkgconf.sh ${PKG} 2>$@.log; then \ - printf '%s\n' ${PKG} >$@; \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' config/${PKG}.c; \ +${HEADERS}:: + ${MKDIR} ${@D} + if config/define-if.sh use/${PKG} config/pkgconf.sh ${PKG} >$@ 2>$@.log; then \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' use/${PKG}.c; \ else \ - : >$@; \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' config/${PKG}.c; \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' use/${PKG}.c; \ fi diff --git a/config/posix-spawn-addfchdir-np.c b/config/posix-spawn-addfchdir-np.c deleted file mode 100644 index b870a53..0000000 --- a/config/posix-spawn-addfchdir-np.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - posix_spawn_file_actions_addfchdir_np(&actions, 3); - return 0; -} diff --git a/config/posix-spawn-addfchdir.c b/config/posix-spawn-addfchdir.c deleted file mode 100644 index c52ff81..0000000 --- a/config/posix-spawn-addfchdir.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - posix_spawn_file_actions_addfchdir(&actions, 3); - return 0; -} diff --git a/config/prelude.mk b/config/prelude.mk index e1e7a4d..6b0b649 100644 --- a/config/prelude.mk +++ b/config/prelude.mk @@ -110,13 +110,21 @@ MSG = @msg() { \ msg # Maximum width of a short message, to align the ✔/✘ -MSG_WIDTH := 33 +MSG_WIDTH := 30 # cat a file if V=1 VCAT,y := @cat VCAT, := @: VCAT := ${VCAT,${IS_V}} +# All external dependencies +ALL_PKGS := \ + libacl \ + libcap \ + libselinux \ + liburing \ + oniguruma + # List all object files here, as they're needed by both `make config` and `make` # All object files except the entry point diff --git a/config/st-acmtim.c b/config/st-acmtim.c deleted file mode 100644 index d687ab0..0000000 --- a/config/st-acmtim.c +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - unsigned int a = sb.st_atim.tv_sec; - unsigned int c = sb.st_ctim.tv_sec; - unsigned int m = sb.st_mtim.tv_sec; - return a + c + m; -} diff --git a/config/st-acmtimespec.c b/config/st-acmtimespec.c deleted file mode 100644 index f747bc0..0000000 --- a/config/st-acmtimespec.c +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - unsigned int a = sb.st_atimespec.tv_sec; - unsigned int c = sb.st_ctimespec.tv_sec; - unsigned int m = sb.st_mtimespec.tv_sec; - return a + c + m; -} diff --git a/config/st-birthtim.c b/config/st-birthtim.c deleted file mode 100644 index 4964571..0000000 --- a/config/st-birthtim.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - return sb.st_birthtim.tv_sec; -} diff --git a/config/st-birthtimespec.c b/config/st-birthtimespec.c deleted file mode 100644 index 91a613f..0000000 --- a/config/st-birthtimespec.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - return sb.st_birthtimespec.tv_sec; -} diff --git a/config/st-flags.c b/config/st-flags.c deleted file mode 100644 index b1d0c32..0000000 --- a/config/st-flags.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - return sb.st_flags; -} diff --git a/config/statx-syscall.c b/config/statx-syscall.c deleted file mode 100644 index 87ec869..0000000 --- a/config/statx-syscall.c +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include -#include - -int main(void) { - struct statx sb; - syscall(SYS_statx, AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); - return 0; -} diff --git a/config/statx.c b/config/statx.c deleted file mode 100644 index 65f1674..0000000 --- a/config/statx.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - struct statx sb; - statx(AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); - return 0; -} diff --git a/config/strerror-l.c b/config/strerror-l.c deleted file mode 100644 index 3dcc4d7..0000000 --- a/config/strerror-l.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - locale_t locale = duplocale(LC_GLOBAL_LOCALE); - return !strerror_l(ENOMEM, locale); -} diff --git a/config/strerror-r-gnu.c b/config/strerror-r-gnu.c deleted file mode 100644 index 26ca0ee..0000000 --- a/config/strerror-r-gnu.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - char buf[256]; - // Check that strerror_r() returns a pointer - return *strerror_r(ENOMEM, buf, sizeof(buf)); -} diff --git a/config/strerror-r-posix.c b/config/strerror-r-posix.c deleted file mode 100644 index 41b2d30..0000000 --- a/config/strerror-r-posix.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - char buf[256]; - // Check that strerror_r() returns an integer - return 2 * strerror_r(ENOMEM, buf, sizeof(buf)); -} diff --git a/config/tm-gmtoff.c b/config/tm-gmtoff.c deleted file mode 100644 index 543df48..0000000 --- a/config/tm-gmtoff.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct tm tm = {0}; - return tm.tm_gmtoff; -} diff --git a/config/use/libacl.c b/config/use/libacl.c new file mode 100644 index 0000000..de1fe50 --- /dev/null +++ b/config/use/libacl.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + acl_free(0); + return 0; +} diff --git a/config/use/libcap.c b/config/use/libcap.c new file mode 100644 index 0000000..58e832c --- /dev/null +++ b/config/use/libcap.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + cap_free(0); + return 0; +} diff --git a/config/use/libselinux.c b/config/use/libselinux.c new file mode 100644 index 0000000..bca409d --- /dev/null +++ b/config/use/libselinux.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + freecon(0); + return 0; +} diff --git a/config/use/liburing.c b/config/use/liburing.c new file mode 100644 index 0000000..bea499a --- /dev/null +++ b/config/use/liburing.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + io_uring_free_probe(0); + return 0; +} diff --git a/config/use/oniguruma.c b/config/use/oniguruma.c new file mode 100644 index 0000000..cb17596 --- /dev/null +++ b/config/use/oniguruma.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + onig_free(0); + return 0; +} diff --git a/config/uselocale.c b/config/uselocale.c deleted file mode 100644 index a712ff8..0000000 --- a/config/uselocale.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - locale_t locale = uselocale((locale_t)0); - return locale == LC_GLOBAL_LOCALE; -} -- cgit v1.2.3 From 4ded7c6b1508f22189049539c94be2930f20364d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 26 Apr 2024 17:54:15 -0400 Subject: config: Just use NOT to implement NOR --- config/flags.mk | 7 ++++--- config/prelude.mk | 16 ---------------- 2 files changed, 4 insertions(+), 19 deletions(-) diff --git a/config/flags.mk b/config/flags.mk index ee13f06..837c30f 100644 --- a/config/flags.mk +++ b/config/flags.mk @@ -75,12 +75,13 @@ export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}} export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}} SAN_CFLAGS,y := -fno-sanitize-recover=all -NO_SAN := ${NOR,${_ASAN},${_LSAN},${_MSAN},${_TSAN},${_UBSAN}} -SAN := ${NOT,${NO_SAN}} +INSANE := ${NOT,${_ASAN}${_LSAN}${_MSAN}${_TSAN}${_UBSAN}} +SAN := ${NOT,${INSANE}} export SAN_CFLAGS=${SAN_CFLAGS,${SAN}} # MSAN and TSAN both need all code to be instrumented -NOLIBS ?= ${NOT,${NOR,${_MSAN},${_TSAN}}} +YESLIBS := ${NOT,${_MSAN}${_TSAN}} +NOLIBS ?= ${NOT,${YESLIBS}} export XNOLIBS=${NOLIBS} # gcov only intercepts fork()/exec() with -std=gnu* diff --git a/config/prelude.mk b/config/prelude.mk index 6b0b649..109e60c 100644 --- a/config/prelude.mk +++ b/config/prelude.mk @@ -70,24 +70,8 @@ TRUTHY,y := y TRUTHY,1 := y # Boolean operators are also implemented with nested expansion -NOT,y := NOT, := y -# Support up to 5 arguments -AND,y := y -AND,y,y := y -AND,y,y,y := y -AND,y,y,y,y := y -AND,y,y,y,y,y := y - -# NOR can be defined without combinatorial explosion. -# OR is just ${NOT,${NOR,...}} -NOR, := y -NOR,, := y -NOR,,, := y -NOR,,,, := y -NOR,,,,, := y - # Normalize ${V} to either "y" or "" IS_V := ${TRUTHY,${V}} -- cgit v1.2.3 From b8ed989642b9f0f6c1301bcff6f1498935cbd81c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 27 Apr 2024 14:59:10 -0400 Subject: bench: Get rid of stray ls --- bench/bench.sh | 1 - 1 file changed, 1 deletion(-) diff --git a/bench/bench.sh b/bench/bench.sh index 1d379ca..1526fe5 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -254,7 +254,6 @@ setup() { echo "Building bfs $commit ..." cd "$worktree" as-user git checkout -qd "$commit" -- - ls if [ -e config ]; then as-user make -s -j"$nproc" config RELEASE=1 as-user make -s -j"$nproc" -- cgit v1.2.3 From 37caa3d71fd8bb4d0d9204e4a2f5cac234fa25fd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 29 Apr 2024 15:30:39 -0400 Subject: build: Replace `make config` with a `./configure` script This lets us do more traditional out-of-tree builds like $ ../path/to/bfs/configure $ make The .mk files are moved from ./config to ./build, mostly so that ./configure will auto-complete easily. --- .github/workflows/ci.yml | 8 +- .github/workflows/codecov.yml | 2 +- .github/workflows/codeql.yml | 2 +- Makefile | 143 +++++++++++++++--------------- README.md | 4 +- bench/bench.sh | 6 +- build/cc.sh | 16 ++++ build/config.mk | 62 +++++++++++++ build/define-if.sh | 20 +++++ build/deps.mk | 18 ++++ build/empty.c | 6 ++ build/exports.mk | 20 +++++ build/flags.mk | 136 ++++++++++++++++++++++++++++ build/has/acl-get-entry.c | 8 ++ build/has/acl-get-file.c | 8 ++ build/has/acl-get-tag-type.c | 10 +++ build/has/acl-is-trivial-np.c | 12 +++ build/has/acl-trivial.c | 8 ++ build/has/aligned-alloc.c | 8 ++ build/has/confstr.c | 9 ++ build/has/extattr-get-file.c | 10 +++ build/has/extattr-get-link.c | 10 +++ build/has/extattr-list-file.c | 10 +++ build/has/extattr-list-link.c | 10 +++ build/has/fdclosedir.c | 8 ++ build/has/getdents.c | 10 +++ build/has/getdents64-syscall.c | 12 +++ build/has/getdents64.c | 10 +++ build/has/getprogname-gnu.c | 9 ++ build/has/getprogname.c | 9 ++ build/has/max-align-t.c | 8 ++ build/has/pipe2.c | 10 +++ build/has/posix-spawn-addfchdir-np.c | 11 +++ build/has/posix-spawn-addfchdir.c | 11 +++ build/has/st-acmtim.c | 12 +++ build/has/st-acmtimespec.c | 12 +++ build/has/st-birthtim.c | 9 ++ build/has/st-birthtimespec.c | 9 ++ build/has/st-flags.c | 9 ++ build/has/statx-syscall.c | 13 +++ build/has/statx.c | 11 +++ build/has/strerror-l.c | 11 +++ build/has/strerror-r-gnu.c | 11 +++ build/has/strerror-r-posix.c | 11 +++ build/has/tm-gmtoff.c | 9 ++ build/has/uselocale.c | 9 ++ build/header.mk | 69 +++++++++++++++ build/pkgconf.sh | 100 +++++++++++++++++++++ build/pkgs.mk | 36 ++++++++ build/prelude.mk | 133 ++++++++++++++++++++++++++++ build/use/libacl.c | 9 ++ build/use/libcap.c | 9 ++ build/use/libselinux.c | 9 ++ build/use/liburing.c | 9 ++ build/use/oniguruma.c | 9 ++ config/cc.sh | 16 ---- config/config.mk | 63 ------------- config/define-if.sh | 20 ----- config/deps.mk | 18 ---- config/empty.c | 6 -- config/exports.mk | 20 ----- config/flags.mk | 136 ---------------------------- config/has/acl-get-entry.c | 8 -- config/has/acl-get-file.c | 8 -- config/has/acl-get-tag-type.c | 10 --- config/has/acl-is-trivial-np.c | 12 --- config/has/acl-trivial.c | 8 -- config/has/aligned-alloc.c | 8 -- config/has/confstr.c | 9 -- config/has/extattr-get-file.c | 10 --- config/has/extattr-get-link.c | 10 --- config/has/extattr-list-file.c | 10 --- config/has/extattr-list-link.c | 10 --- config/has/fdclosedir.c | 8 -- config/has/getdents.c | 10 --- config/has/getdents64-syscall.c | 12 --- config/has/getdents64.c | 10 --- config/has/getprogname-gnu.c | 9 -- config/has/getprogname.c | 9 -- config/has/max-align-t.c | 8 -- config/has/pipe2.c | 10 --- config/has/posix-spawn-addfchdir-np.c | 11 --- config/has/posix-spawn-addfchdir.c | 11 --- config/has/st-acmtim.c | 12 --- config/has/st-acmtimespec.c | 12 --- config/has/st-birthtim.c | 9 -- config/has/st-birthtimespec.c | 9 -- config/has/st-flags.c | 9 -- config/has/statx-syscall.c | 13 --- config/has/statx.c | 11 --- config/has/strerror-l.c | 11 --- config/has/strerror-r-gnu.c | 11 --- config/has/strerror-r-posix.c | 11 --- config/has/tm-gmtoff.c | 9 -- config/has/uselocale.c | 9 -- config/header.mk | 69 --------------- config/pkgconf.sh | 100 --------------------- config/pkgs.mk | 36 -------- config/prelude.mk | 162 ---------------------------------- config/use/libacl.c | 9 -- config/use/libcap.c | 9 -- config/use/libselinux.c | 9 -- config/use/liburing.c | 9 -- config/use/oniguruma.c | 9 -- configure | 98 ++++++++++++++++++++ docs/BUILDING.md | 47 +++++----- tests/util.sh | 9 +- tests/xspawn.c | 15 +--- 108 files changed, 1199 insertions(+), 1141 deletions(-) create mode 100755 build/cc.sh create mode 100644 build/config.mk create mode 100755 build/define-if.sh create mode 100644 build/deps.mk create mode 100644 build/empty.c create mode 100644 build/exports.mk create mode 100644 build/flags.mk create mode 100644 build/has/acl-get-entry.c create mode 100644 build/has/acl-get-file.c create mode 100644 build/has/acl-get-tag-type.c create mode 100644 build/has/acl-is-trivial-np.c create mode 100644 build/has/acl-trivial.c create mode 100644 build/has/aligned-alloc.c create mode 100644 build/has/confstr.c create mode 100644 build/has/extattr-get-file.c create mode 100644 build/has/extattr-get-link.c create mode 100644 build/has/extattr-list-file.c create mode 100644 build/has/extattr-list-link.c create mode 100644 build/has/fdclosedir.c create mode 100644 build/has/getdents.c create mode 100644 build/has/getdents64-syscall.c create mode 100644 build/has/getdents64.c create mode 100644 build/has/getprogname-gnu.c create mode 100644 build/has/getprogname.c create mode 100644 build/has/max-align-t.c create mode 100644 build/has/pipe2.c create mode 100644 build/has/posix-spawn-addfchdir-np.c create mode 100644 build/has/posix-spawn-addfchdir.c create mode 100644 build/has/st-acmtim.c create mode 100644 build/has/st-acmtimespec.c create mode 100644 build/has/st-birthtim.c create mode 100644 build/has/st-birthtimespec.c create mode 100644 build/has/st-flags.c create mode 100644 build/has/statx-syscall.c create mode 100644 build/has/statx.c create mode 100644 build/has/strerror-l.c create mode 100644 build/has/strerror-r-gnu.c create mode 100644 build/has/strerror-r-posix.c create mode 100644 build/has/tm-gmtoff.c create mode 100644 build/has/uselocale.c create mode 100644 build/header.mk create mode 100755 build/pkgconf.sh create mode 100644 build/pkgs.mk create mode 100644 build/prelude.mk create mode 100644 build/use/libacl.c create mode 100644 build/use/libcap.c create mode 100644 build/use/libselinux.c create mode 100644 build/use/liburing.c create mode 100644 build/use/oniguruma.c delete mode 100755 config/cc.sh delete mode 100644 config/config.mk delete mode 100755 config/define-if.sh delete mode 100644 config/deps.mk delete mode 100644 config/empty.c delete mode 100644 config/exports.mk delete mode 100644 config/flags.mk delete mode 100644 config/has/acl-get-entry.c delete mode 100644 config/has/acl-get-file.c delete mode 100644 config/has/acl-get-tag-type.c delete mode 100644 config/has/acl-is-trivial-np.c delete mode 100644 config/has/acl-trivial.c delete mode 100644 config/has/aligned-alloc.c delete mode 100644 config/has/confstr.c delete mode 100644 config/has/extattr-get-file.c delete mode 100644 config/has/extattr-get-link.c delete mode 100644 config/has/extattr-list-file.c delete mode 100644 config/has/extattr-list-link.c delete mode 100644 config/has/fdclosedir.c delete mode 100644 config/has/getdents.c delete mode 100644 config/has/getdents64-syscall.c delete mode 100644 config/has/getdents64.c delete mode 100644 config/has/getprogname-gnu.c delete mode 100644 config/has/getprogname.c delete mode 100644 config/has/max-align-t.c delete mode 100644 config/has/pipe2.c delete mode 100644 config/has/posix-spawn-addfchdir-np.c delete mode 100644 config/has/posix-spawn-addfchdir.c delete mode 100644 config/has/st-acmtim.c delete mode 100644 config/has/st-acmtimespec.c delete mode 100644 config/has/st-birthtim.c delete mode 100644 config/has/st-birthtimespec.c delete mode 100644 config/has/st-flags.c delete mode 100644 config/has/statx-syscall.c delete mode 100644 config/has/statx.c delete mode 100644 config/has/strerror-l.c delete mode 100644 config/has/strerror-r-gnu.c delete mode 100644 config/has/strerror-r-posix.c delete mode 100644 config/has/tm-gmtoff.c delete mode 100644 config/has/uselocale.c delete mode 100644 config/header.mk delete mode 100755 config/pkgconf.sh delete mode 100644 config/pkgs.mk delete mode 100644 config/prelude.mk delete mode 100644 config/use/libacl.c delete mode 100644 config/use/libcap.c delete mode 100644 config/use/libselinux.c delete mode 100644 config/use/liburing.c delete mode 100644 config/use/oniguruma.c create mode 100755 configure diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3890bb0..57f59f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,7 @@ jobs: gmake \ oniguruma jobs=$(sysctl -n hw.ncpu) - gmake -j$jobs config + MAKE=gmake ./configure gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" netbsd: @@ -133,7 +133,7 @@ jobs: pkgconf \ tcl-expect jobs=$(sysctl -n hw.ncpu) - make -j$jobs config + ./configure make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" dragonflybsd: @@ -165,7 +165,7 @@ jobs: run: | chown -R action:action . jobs=$(sysctl -n hw.ncpu) - sudo -u action make config + sudo -u action ./configure sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" omnios: @@ -198,5 +198,5 @@ jobs: PATH="/usr/xpg4/bin:$PATH" chown -R action:staff . jobs=$(getconf NPROCESSORS_ONLN) - sudo -u action gmake config + sudo -u action MAKE=gmake ./configure sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 2245f40..bb68927 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -25,7 +25,7 @@ jobs: - name: Generate coverage run: | - make config GCOV=y + ./configure GCOV=y make -j$(nproc) check TEST_FLAGS="--sudo" gcov -abcfpu obj/*/*.o diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index c21fda5..a0b8fe3 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -41,7 +41,7 @@ jobs: - name: Configure run: | - make -j$(nproc) config + ./configure - name: Initialize CodeQL uses: github/codeql-action/init@v3 diff --git a/Makefile b/Makefile index 981c322..2fb35fd 100644 --- a/Makefile +++ b/Makefile @@ -1,56 +1,50 @@ # Copyright © Tavian Barnes # SPDX-License-Identifier: 0BSD -# This Makefile implements the configuration and build steps for bfs. It is -# portable to both GNU make and most BSD make implementations. To build bfs, -# run +# To build bfs, run # -# $ make config +# $ ./configure # $ make # Utilities and GNU/BSD portability -include config/prelude.mk +include build/prelude.mk # The default build target default: bfs .PHONY: default # Include the generated build config, if it exists --include ${CONFIG} +-include gen/config.mk -## Configuration phase (`make config`) - -# The configuration goal itself -config:: - @+${MAKE} -sf config/config.mk +## Configuration phase (`./configure`) # bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.). # Direct users to the new configuration system. asan lsan msan tsan ubsan gcov lint release:: - @printf 'error: `%s %s` is no longer supported. ' "${MAKE}" $@ >&2 - @printf 'Use `%s config %s=y` instead.\n' "${MAKE}" $$(echo $@ | tr 'a-z' 'A-Z') >&2 + @printf 'error: `%s %s` is no longer supported. Use `./configure %s=y` instead.\n' \ + "${MAKE}" $@ $$(echo $@ | tr 'a-z' 'A-Z') >&2 @false -# Print an error if `make` is run before `make config` -${CONFIG}:: +# Print an error if `make` is run before `./configure` +gen/config.mk:: @if ! [ -e $@ ]; then \ - printf 'error: You must run `%s config` before `%s`.\n' "${MAKE}" "${MAKE}" >&2; \ + printf 'error: You must run `./configure` before `%s`.\n' "${MAKE}" >&2; \ false; \ fi ## Build phase (`make`) # The main binary -bfs: ${BIN}/bfs +bfs: bin/bfs .PHONY: bfs # All binaries BINS := \ - ${BIN}/bfs \ - ${BIN}/tests/mksock \ - ${BIN}/tests/units \ - ${BIN}/tests/xspawnee \ - ${BIN}/tests/xtouch + bin/bfs \ + bin/tests/mksock \ + bin/tests/units \ + bin/tests/xspawnee \ + bin/tests/xtouch all: ${BINS} .PHONY: all @@ -60,52 +54,50 @@ ALL_CFLAGS = ${CPPFLAGS} ${CFLAGS} ${DEPFLAGS} ALL_LDFLAGS = ${CFLAGS} ${LDFLAGS} # The main binary -${BIN}/bfs: ${LIBBFS} ${OBJ}/src/main.o +bin/bfs: ${LIBBFS} obj/src/main.o ${BINS}: @${MKDIR} ${@D} - +${MSG} "[ LD ] ${TGT}" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ + +${MSG} "[ LD ] $@" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ ${POSTLINK} # Get the .c file for a .o file -_CSRC = ${@:${OBJ}/%.o=%.c} -CSRC = ${_CSRC:gen/%=${GEN}/%} +CSRC = ${@:obj/%.o=%.c} -# Depend on ${CONFIG} to make sure `make config` runs first, and to rebuild when -# the configuration changes -${OBJS}: ${CONFIG} +# Rebuild when the configuration changes +${OBJS}: gen/config.mk @${MKDIR} ${@D} - ${MSG} "[ CC ] ${_CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ + ${MSG} "[ CC ] ${CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ # Save the version number to this file, but only update version.c if it changes -${GEN}/version.c.new:: +gen/version.c.new:: @${MKDIR} ${@D} @printf 'const char bfs_version[] = "' >$@ @if [ "$$VERSION" ]; then \ printf '%s' "$$VERSION"; \ - elif test -d .git && command -v git >/dev/null 2>&1; then \ - git describe --always --dirty; \ + elif test -e src/../.git && command -v git >/dev/null 2>&1; then \ + git -C src/.. describe --always --dirty; \ else \ echo "3.1.3"; \ fi | tr -d '\n' >>$@ @printf '";\n' >>$@ -${GEN}/version.c: ${GEN}/version.c.new +gen/version.c: gen/version.c.new @test -e $@ && cmp -s $@ ${.ALLSRC} && rm ${.ALLSRC} || mv ${.ALLSRC} $@ -${OBJ}/gen/version.o: ${GEN}/version.c +obj/gen/version.o: gen/version.c ## Test phase (`make check`) # Unit test binaries UTEST_BINS := \ - ${BIN}/tests/units \ - ${BIN}/tests/xspawnee + bin/tests/units \ + bin/tests/xspawnee # Integration test binaries ITEST_BINS := \ - ${BIN}/tests/mksock \ - ${BIN}/tests/xtouch + bin/tests/mksock \ + bin/tests/xtouch # Build (but don't run) test binaries tests: ${UTEST_BINS} ${ITEST_BINS} @@ -117,45 +109,45 @@ check: unit-tests integration-tests # Run the unit tests unit-tests: ${UTEST_BINS} - ${MSG} "[TEST] tests/units" ${BIN}/tests/units + ${MSG} "[TEST] tests/units" bin/tests/units .PHONY: unit-tests -${BIN}/tests/units: \ +bin/tests/units: \ ${UNIT_OBJS} \ ${LIBBFS} -${BIN}/tests/xspawnee: \ - ${OBJ}/tests/xspawnee.o +bin/tests/xspawnee: \ + obj/tests/xspawnee.o # The different flag combinations we check INTEGRATIONS := default dfs ids eds j1 j2 j3 s INTEGRATION_TESTS := ${INTEGRATIONS:%=check-%} # Check just `bfs` -check-default: ${BIN}/bfs ${ITEST_BINS} +check-default: bin/bfs ${ITEST_BINS} +${MSG} "[TEST] bfs" \ - ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs" ${TEST_FLAGS} # Check the different search strategies -check-dfs check-ids check-eds: ${BIN}/bfs ${ITEST_BINS} +check-dfs check-ids check-eds: bin/bfs ${ITEST_BINS} +${MSG} "[TEST] bfs -S ${@:check-%=%}" \ - ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -S ${@:check-%=%}" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs -S ${@:check-%=%}" ${TEST_FLAGS} # Check various flags -check-j1 check-j2 check-j3 check-s: ${BIN}/bfs ${ITEST_BINS} +check-j1 check-j2 check-j3 check-s: bin/bfs ${ITEST_BINS} +${MSG} "[TEST] bfs -${@:check-%=%}" \ - ./tests/tests.sh --make="${MAKE}" --bfs="${BIN}/bfs -${@:check-%=%}" ${TEST_FLAGS} + ./tests/tests.sh --make="${MAKE}" --bfs="bin/bfs -${@:check-%=%}" ${TEST_FLAGS} # Run the integration tests integration-tests: ${INTEGRATION_TESTS} .PHONY: integration-tests -${BIN}/tests/mksock: \ - ${OBJ}/tests/mksock.o \ +bin/tests/mksock: \ + obj/tests/mksock.o \ ${LIBBFS} -${BIN}/tests/xtouch: \ - ${OBJ}/tests/xtouch.o \ +bin/tests/xtouch: \ + obj/tests/xtouch.o \ ${LIBBFS} # `make distcheck` configurations @@ -183,8 +175,10 @@ DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_LIBDIR=/usr/lib32/pkgconf DISTCHECK_CONFIG_release := RELEASE=y ${DISTCHECKS}:: - +${MAKE} -rs BUILDDIR=${BUILDDIR}/$@ config ${DISTCHECK_CONFIG_${@:distcheck-%=%}} - +${MAKE} -s BUILDDIR=${BUILDDIR}/$@ check TEST_FLAGS="--sudo --verbose=skipped" + @${MKDIR} $@ + @+cd $@ \ + && ../configure ${DISTCHECK_CONFIG_${@:distcheck-%=%}} \ + && ${MAKE} -s check TEST_FLAGS="--sudo --verbose=skipped" ## Packaging (`make install`) @@ -193,44 +187,49 @@ DEST_MANDIR := ${DESTDIR}${MANDIR} install:: ${Q}${MKDIR} ${DEST_PREFIX}/bin - ${MSG} "[INSTALL] bin/bfs" \ - ${INSTALL} -m755 ${BIN}/bfs ${DEST_PREFIX}/bin/bfs + ${MSG} "[INST] bin/bfs" \ + ${INSTALL} -m755 bin/bfs ${DEST_PREFIX}/bin/bfs ${Q}${MKDIR} ${DEST_MANDIR}/man1 - ${MSG} "[INSTALL] man/man1/bfs.1" \ + ${MSG} "[INST] man/man1/bfs.1" \ ${INSTALL} -m644 docs/bfs.1 ${DEST_MANDIR}/man1/bfs.1 ${Q}${MKDIR} ${DEST_PREFIX}/share/bash-completion/completions - ${MSG} "[INSTALL] completions/bfs.bash" \ + ${MSG} "[INST] completions/bfs.bash" \ ${INSTALL} -m644 completions/bfs.bash ${DEST_PREFIX}/share/bash-completion/completions/bfs ${Q}${MKDIR} ${DEST_PREFIX}/share/zsh/site-functions - ${MSG} "[INSTALL] completions/bfs.zsh" \ + ${MSG} "[INST] completions/bfs.zsh" \ ${INSTALL} -m644 completions/bfs.zsh ${DEST_PREFIX}/share/zsh/site-functions/_bfs ${Q}${MKDIR} ${DEST_PREFIX}/share/fish/vendor_completions.d - ${MSG} "[INSTALL] completions/bfs.fish" \ + ${MSG} "[INST] completions/bfs.fish" \ ${INSTALL} -m644 completions/bfs.fish ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish uninstall:: - ${RM} ${DEST_PREFIX}/share/bash-completion/completions/bfs - ${RM} ${DEST_PREFIX}/share/zsh/site-functions/_bfs - ${RM} ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish - ${RM} ${DEST_MANDIR}/man1/bfs.1 - ${RM} ${DEST_PREFIX}/bin/bfs + ${MSG} "[ RM ] completions/bfs.bash" \ + ${RM} ${DEST_PREFIX}/share/bash-completion/completions/bfs + ${MSG} "[ RM ] completions/bfs.zsh" \ + ${RM} ${DEST_PREFIX}/share/zsh/site-functions/_bfs + ${MSG} "[ RM ] completions/bfs.fish" \ + ${RM} ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish + ${MSG} "[ RM ] man/man1/bfs.1" \ + ${RM} ${DEST_MANDIR}/man1/bfs.1 + ${MSG} "[ RM ] bin/bfs" \ + ${RM} ${DEST_PREFIX}/bin/bfs # Check that `make install` works and `make uninstall` removes everything check-install:: - +${MAKE} install DESTDIR=${BUILDDIR}/pkg - +${MAKE} uninstall DESTDIR=${BUILDDIR}/pkg - ${BIN}/bfs ${BUILDDIR}/pkg -not -type d -print -exit 1 - ${RM} -r ${BUILDDIR}/pkg + +${MAKE} install DESTDIR=pkg + +${MAKE} uninstall DESTDIR=pkg + bin/bfs pkg -not -type d -print -exit 1 + ${RM} -r pkg ## Cleanup (`make clean`) # Clean all build products clean:: ${MSG} "[ RM ] bin obj" \ - ${RM} -r ${BIN} ${OBJ} + ${RM} -r bin obj # Clean everything, including generated files distclean: clean ${MSG} "[ RM ] gen" \ - ${RM} -r ${GEN} ${DISTCHECKS} + ${RM} -r gen ${DISTCHECKS} .PHONY: distclean diff --git a/README.md b/README.md index b95c16b..922ebdc 100644 --- a/README.md +++ b/README.md @@ -333,7 +333,7 @@ Once you have the dependencies, you can build bfs. Download one of the [releases](https://github.com/tavianator/bfs/releases) or clone the [git repo](https://github.com/tavianator/bfs). Then run - $ make config + $ ./configure $ make This will build the `./bin/bfs` binary. @@ -343,7 +343,7 @@ Run the test suite to make sure it works correctly: If you're interested in speed, you may want to build the release version instead: - $ make config RELEASE=y + $ ./configure RELEASE=y $ make Finally, if you want to install it globally, run diff --git a/bench/bench.sh b/bench/bench.sh index 1526fe5..ba46599 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -221,7 +221,7 @@ setup() { fi echo "Building bfs ..." - as-user make -s -j"$nproc" RELEASE=y + as-user ./configure RELEASE=y as-user make -s -j"$nproc" all as-user mkdir -p bench/corpus @@ -254,8 +254,8 @@ setup() { echo "Building bfs $commit ..." cd "$worktree" as-user git checkout -qd "$commit" -- - if [ -e config ]; then - as-user make -s -j"$nproc" config RELEASE=1 + if [ -e configure ]; then + as-user ./configure RELEASE=1 as-user make -s -j"$nproc" else as-user make -s -j"$nproc" release diff --git a/build/cc.sh b/build/cc.sh new file mode 100755 index 0000000..45d51ca --- /dev/null +++ b/build/cc.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Run the compiler and check if it succeeded + +set -eu + +TMP=$(mktemp) +trap 'rm -f "$TMP"' EXIT + +( + set -x + $XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o "$TMP" +) diff --git a/build/config.mk b/build/config.mk new file mode 100644 index 0000000..153838d --- /dev/null +++ b/build/config.mk @@ -0,0 +1,62 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that implements `./configure` + +include build/prelude.mk +include build/exports.mk + +# All configuration steps +config: gen/config.mk gen/config.h +.PHONY: config + +# Makefile fragments generated by `./configure` +MKS := \ + gen/vars.mk \ + gen/flags.mk \ + gen/deps.mk \ + gen/pkgs.mk + +# The main configuration file, which includes the others +gen/config.mk: ${MKS} + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + @printf 'include %s\n' ${.ALLSRC} >>$@ + ${VCAT} gen/config.mk +.PHONY: gen/config.mk + +# Saves the configurable variables +gen/vars.mk:: + @${MKDIR} ${@D} + ${MSG} "[ GEN] $@" + @printf '# %s\n' "$@" >$@ + @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@ + @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ + @printf 'OS := %s\n' "$${OS:-$$(uname)}" >>$@ + @printf 'CC := %s\n' "$$XCC" >>$@ + @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ + @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ + @printf 'PKG_CONFIG := %s\n' "$$XPKG_CONFIG" >>$@ + @printf 'RM := %s\n' "$$XRM" >>$@ + ${VCAT} $@ + +# Sets the build flags. This depends on vars.mk and uses a recursive make so +# that the default flags can depend on variables like ${OS}. +gen/flags.mk: gen/vars.mk + @+${MAKE} -sf build/flags.mk $@ +.PHONY: gen/flags.mk + +# Check for dependency generation support +gen/deps.mk: gen/flags.mk + @+${MAKE} -sf build/deps.mk $@ +.PHONY: gen/deps.mk + +# Auto-detect dependencies and their build flags +gen/pkgs.mk: gen/flags.mk + @+${MAKE} -sf build/pkgs.mk $@ +.PHONY: gen/pkgs.mk + +# Compile-time feature detection +gen/config.h: gen/config.mk + @+${MAKE} -sf build/header.mk $@ +.PHONY: gen/config.h diff --git a/build/define-if.sh b/build/define-if.sh new file mode 100755 index 0000000..295ead8 --- /dev/null +++ b/build/define-if.sh @@ -0,0 +1,20 @@ +#!/bin/sh + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Output a C preprocessor definition based on whether a command succeeds + +set -eu + +SLUG="${1#build/}" +SLUG="${SLUG%.c}" +MACRO="BFS_$(printf '%s' "$SLUG" | tr '/a-z-' '_A-Z_')" +shift + +if "$@"; then + printf '#define %s true\n' "$MACRO" +else + printf '#define %s false\n' "$MACRO" + exit 1 +fi diff --git a/build/deps.mk b/build/deps.mk new file mode 100644 index 0000000..e8dd9fb --- /dev/null +++ b/build/deps.mk @@ -0,0 +1,18 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/deps.mk + +include build/prelude.mk +include gen/vars.mk +include gen/flags.mk +include build/exports.mk + +gen/deps.mk:: + ${MSG} "[ GEN] $@" + printf '# %s\n' "$@" >$@ + if build/cc.sh -MD -MP -MF /dev/null build/empty.c; then \ + printf 'DEPFLAGS := -MD -MP\n'; \ + fi >>$@ 2>$@.log + ${VCAT} $@ + printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ diff --git a/build/empty.c b/build/empty.c new file mode 100644 index 0000000..4fa9a5b --- /dev/null +++ b/build/empty.c @@ -0,0 +1,6 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +int main(void) { + return 0; +} diff --git a/build/exports.mk b/build/exports.mk new file mode 100644 index 0000000..ed19134 --- /dev/null +++ b/build/exports.mk @@ -0,0 +1,20 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile fragment that exports variables used by configuration scripts + +export XPREFIX=${PREFIX} +export XMANDIR=${MANDIR} + +export XCC=${CC} +export XINSTALL=${INSTALL} +export XMKDIR=${MKDIR} +export XPKG_CONFIG=${PKG_CONFIG} +export XRM=${RM} + +export XCPPFLAGS=${CPPFLAGS} +export XCFLAGS=${CFLAGS} +export XLDFLAGS=${LDFLAGS} +export XLDLIBS=${LDLIBS} + +export XNOLIBS=${NOLIBS} diff --git a/build/flags.mk b/build/flags.mk new file mode 100644 index 0000000..0a44e94 --- /dev/null +++ b/build/flags.mk @@ -0,0 +1,136 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/flags.mk + +include build/prelude.mk +include gen/vars.mk + +# Configurable flags +CPPFLAGS ?= +CFLAGS ?= \ + -g \ + -Wall \ + -Wformat=2 \ + -Werror=implicit \ + -Wimplicit-fallthrough \ + -Wmissing-declarations \ + -Wshadow \ + -Wsign-compare \ + -Wstrict-prototypes +LDFLAGS ?= +LDLIBS ?= + +export XCPPFLAGS=${CPPFLAGS} +export XCFLAGS=${CFLAGS} +export XLDFLAGS=${LDFLAGS} +export XLDLIBS=${LDLIBS} + +# Immutable flags +export BFS_CPPFLAGS= \ + -Isrc \ + -Igen \ + -D__EXTENSIONS__ \ + -D_ATFILE_SOURCE \ + -D_BSD_SOURCE \ + -D_DARWIN_C_SOURCE \ + -D_DEFAULT_SOURCE \ + -D_GNU_SOURCE \ + -D_POSIX_PTHREAD_SEMANTICS \ + -D_FILE_OFFSET_BITS=64 \ + -D_TIME_BITS=64 +export BFS_CFLAGS= -std=c17 -pthread + +# Platform-specific system libraries +LDLIBS,DragonFly := -lposix1e +LDLIBS,Linux := -lrt +LDLIBS,NetBSD := -lutil +LDLIBS,SunOS := -lsec -lsocket -lnsl +export BFS_LDLIBS=${LDLIBS,${OS}} + +# Build profiles +_ASAN := ${TRUTHY,${ASAN}} +_LSAN := ${TRUTHY,${LSAN}} +_MSAN := ${TRUTHY,${MSAN}} +_TSAN := ${TRUTHY,${TSAN}} +_UBSAN := ${TRUTHY,${UBSAN}} +_GCOV := ${TRUTHY,${GCOV}} +_LINT := ${TRUTHY,${LINT}} +_RELEASE := ${TRUTHY,${RELEASE}} + +# https://github.com/google/sanitizers/issues/342 +TSAN_CPPFLAGS,y := -DBFS_USE_TARGET_CLONES=0 +export TSAN_CPPFLAGS=${TSAN_CPPFLAGS,${_TSAN}} + +ASAN_CFLAGS,y := -fsanitize=address +LSAN_CFLAGS,y := -fsanitize=leak +MSAN_CFLAGS,y := -fsanitize=memory -fsanitize-memory-track-origins +TSAN_CFLAGS,y := -fsanitize=thread +UBSAN_CFLAGS.y := -fsanitize=undefined + +export ASAN_CFLAGS=${ASAN_CFLAGS,${_ASAN}} +export LSAN_CFLAGS=${LSAN_CFLAGS,${_LSAN}} +export MSAN_CFLAGS=${MSAN_CFLAGS,${_MSAN}} +export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}} +export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}} + +SAN_CFLAGS,y := -fno-sanitize-recover=all +INSANE := ${NOT,${_ASAN}${_LSAN}${_MSAN}${_TSAN}${_UBSAN}} +SAN := ${NOT,${INSANE}} +export SAN_CFLAGS=${SAN_CFLAGS,${SAN}} + +# MSAN and TSAN both need all code to be instrumented +YESLIBS := ${NOT,${_MSAN}${_TSAN}} +NOLIBS ?= ${NOT,${YESLIBS}} +export XNOLIBS=${NOLIBS} + +# gcov only intercepts fork()/exec() with -std=gnu* +GCOV_CFLAGS,y := -std=gnu17 --coverage +export GCOV_CFLAGS=${GCOV_CFLAGS,${_GCOV}} + +LINT_CPPFLAGS,y := -D_FORTIFY_SOURCE=3 -DBFS_LINT +LINT_CFLAGS,y := -Werror -O2 + +export LINT_CPPFLAGS=${LINT_CPPFLAGS,${_LINT}} +export LINT_CFLAGS=${LINT_CFLAGS,${_LINT}} + +RELEASE_CPPFLAGS,y := -DNDEBUG +RELEASE_CFLAGS,y := -O3 -flto=auto + +export RELEASE_CPPFLAGS=${RELEASE_CPPFLAGS,${_RELEASE}} +export RELEASE_CFLAGS=${RELEASE_CFLAGS,${_RELEASE}} + +# Set a variable +SETVAR = printf '%s := %s\n' >>$@ + +# Append to a variable, if non-empty +APPEND = append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append + +gen/flags.mk:: + ${MSG} "[ GEN] $@" + printf '# %s\n' "$@" >$@ + ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS" + ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS" + ${APPEND} CPPFLAGS "$$LINT_CPPFLAGS" + ${APPEND} CPPFLAGS "$$RELEASE_CPPFLAGS" + ${APPEND} CPPFLAGS "$$XCPPFLAGS" + ${APPEND} CPPFLAGS "$$EXTRA_CPPFLAGS" + ${SETVAR} CFLAGS "$$BFS_CFLAGS" + ${APPEND} CFLAGS "$$ASAN_CFLAGS" + ${APPEND} CFLAGS "$$LSAN_CFLAGS" + ${APPEND} CFLAGS "$$MSAN_CFLAGS" + ${APPEND} CFLAGS "$$TSAN_CFLAGS" + ${APPEND} CFLAGS "$$UBSAN_CFLAGS" + ${APPEND} CFLAGS "$$SAN_CFLAGS" + ${APPEND} CFLAGS "$$GCOV_CFLAGS" + ${APPEND} CFLAGS "$$LINT_CFLAGS" + ${APPEND} CFLAGS "$$RELEASE_CFLAGS" + ${APPEND} CFLAGS "$$XCFLAGS" + ${APPEND} CFLAGS "$$EXTRA_CFLAGS" + ${SETVAR} LDFLAGS "$$XLDFLAGS" + ${SETVAR} LDLIBS "$$XLDLIBS" + ${APPEND} LDLIBS "$$EXTRA_LDLIBS" + ${APPEND} LDLIBS "$$BFS_LDLIBS" + ${SETVAR} NOLIBS "$$XNOLIBS" + test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ + ${VCAT} $@ diff --git a/build/has/acl-get-entry.c b/build/has/acl-get-entry.c new file mode 100644 index 0000000..3cce771 --- /dev/null +++ b/build/has/acl-get-entry.c @@ -0,0 +1,8 @@ +#include +#include + +int main(void) { + acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); + acl_entry_t entry; + return acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); +} diff --git a/build/has/acl-get-file.c b/build/has/acl-get-file.c new file mode 100644 index 0000000..89fbf23 --- /dev/null +++ b/build/has/acl-get-file.c @@ -0,0 +1,8 @@ +#include +#include +#include + +int main(void) { + acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); + return acl == (acl_t)NULL; +} diff --git a/build/has/acl-get-tag-type.c b/build/has/acl-get-tag-type.c new file mode 100644 index 0000000..2901956 --- /dev/null +++ b/build/has/acl-get-tag-type.c @@ -0,0 +1,10 @@ +#include +#include +#include + +int main(void) { + acl_entry_t entry; + memset(&entry, 0, sizeof(entry)); + acl_tag_t tag; + return acl_get_tag_type(entry, &tag); +} diff --git a/build/has/acl-is-trivial-np.c b/build/has/acl-is-trivial-np.c new file mode 100644 index 0000000..9ca9fc7 --- /dev/null +++ b/build/has/acl-is-trivial-np.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + acl_t acl = acl_get_fd(3); + int trivial; + acl_is_trivial_np(acl, &trivial); + return 0; +} diff --git a/build/has/acl-trivial.c b/build/has/acl-trivial.c new file mode 100644 index 0000000..7efc838 --- /dev/null +++ b/build/has/acl-trivial.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return acl_trivial("."); +} diff --git a/build/has/aligned-alloc.c b/build/has/aligned-alloc.c new file mode 100644 index 0000000..4460038 --- /dev/null +++ b/build/has/aligned-alloc.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return !aligned_alloc(_Alignof(void *), sizeof(void *)); +} diff --git a/build/has/confstr.c b/build/has/confstr.c new file mode 100644 index 0000000..58280b4 --- /dev/null +++ b/build/has/confstr.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + confstr(_CS_PATH, NULL, 0); + return 0; +} diff --git a/build/has/extattr-get-file.c b/build/has/extattr-get-file.c new file mode 100644 index 0000000..ac9cf96 --- /dev/null +++ b/build/has/extattr-get-file.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_get_file("file", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); +} diff --git a/build/has/extattr-get-link.c b/build/has/extattr-get-link.c new file mode 100644 index 0000000..c35be5b --- /dev/null +++ b/build/has/extattr-get-link.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_get_link("link", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); +} diff --git a/build/has/extattr-list-file.c b/build/has/extattr-list-file.c new file mode 100644 index 0000000..e68a8bb --- /dev/null +++ b/build/has/extattr-list-file.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_list_file("file", EXTATTR_NAMESPACE_USER, NULL, 0); +} diff --git a/build/has/extattr-list-link.c b/build/has/extattr-list-link.c new file mode 100644 index 0000000..49f0ec2 --- /dev/null +++ b/build/has/extattr-list-link.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return extattr_list_link("link", EXTATTR_NAMESPACE_USER, NULL, 0); +} diff --git a/build/has/fdclosedir.c b/build/has/fdclosedir.c new file mode 100644 index 0000000..f4ad1f5 --- /dev/null +++ b/build/has/fdclosedir.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return fdclosedir(opendir(".")); +} diff --git a/build/has/getdents.c b/build/has/getdents.c new file mode 100644 index 0000000..d0d4228 --- /dev/null +++ b/build/has/getdents.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct dirent de; + getdents(3, &de, 1024); + return 0; +} diff --git a/build/has/getdents64-syscall.c b/build/has/getdents64-syscall.c new file mode 100644 index 0000000..4838c14 --- /dev/null +++ b/build/has/getdents64-syscall.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + struct dirent64 de; + syscall(SYS_getdents64, 3, &de, 1024); + return 0; +} diff --git a/build/has/getdents64.c b/build/has/getdents64.c new file mode 100644 index 0000000..1abf36d --- /dev/null +++ b/build/has/getdents64.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct dirent64 de; + getdents64(3, &de, 1024); + return 0; +} diff --git a/build/has/getprogname-gnu.c b/build/has/getprogname-gnu.c new file mode 100644 index 0000000..6b97c5e --- /dev/null +++ b/build/has/getprogname-gnu.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + const char *str = program_invocation_short_name; + return str[0]; +} diff --git a/build/has/getprogname.c b/build/has/getprogname.c new file mode 100644 index 0000000..83dc8e8 --- /dev/null +++ b/build/has/getprogname.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + const char *str = getprogname(); + return str[0]; +} diff --git a/build/has/max-align-t.c b/build/has/max-align-t.c new file mode 100644 index 0000000..96165ce --- /dev/null +++ b/build/has/max-align-t.c @@ -0,0 +1,8 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + return _Alignof(max_align_t); +} diff --git a/build/has/pipe2.c b/build/has/pipe2.c new file mode 100644 index 0000000..4cb43b5 --- /dev/null +++ b/build/has/pipe2.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + int fds[2]; + return pipe2(fds, O_CLOEXEC); +} diff --git a/build/has/posix-spawn-addfchdir-np.c b/build/has/posix-spawn-addfchdir-np.c new file mode 100644 index 0000000..b870a53 --- /dev/null +++ b/build/has/posix-spawn-addfchdir-np.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addfchdir_np(&actions, 3); + return 0; +} diff --git a/build/has/posix-spawn-addfchdir.c b/build/has/posix-spawn-addfchdir.c new file mode 100644 index 0000000..c52ff81 --- /dev/null +++ b/build/has/posix-spawn-addfchdir.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + posix_spawn_file_actions_t actions; + posix_spawn_file_actions_init(&actions); + posix_spawn_file_actions_addfchdir(&actions, 3); + return 0; +} diff --git a/build/has/st-acmtim.c b/build/has/st-acmtim.c new file mode 100644 index 0000000..d687ab0 --- /dev/null +++ b/build/has/st-acmtim.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + unsigned int a = sb.st_atim.tv_sec; + unsigned int c = sb.st_ctim.tv_sec; + unsigned int m = sb.st_mtim.tv_sec; + return a + c + m; +} diff --git a/build/has/st-acmtimespec.c b/build/has/st-acmtimespec.c new file mode 100644 index 0000000..f747bc0 --- /dev/null +++ b/build/has/st-acmtimespec.c @@ -0,0 +1,12 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + unsigned int a = sb.st_atimespec.tv_sec; + unsigned int c = sb.st_ctimespec.tv_sec; + unsigned int m = sb.st_mtimespec.tv_sec; + return a + c + m; +} diff --git a/build/has/st-birthtim.c b/build/has/st-birthtim.c new file mode 100644 index 0000000..4964571 --- /dev/null +++ b/build/has/st-birthtim.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_birthtim.tv_sec; +} diff --git a/build/has/st-birthtimespec.c b/build/has/st-birthtimespec.c new file mode 100644 index 0000000..91a613f --- /dev/null +++ b/build/has/st-birthtimespec.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_birthtimespec.tv_sec; +} diff --git a/build/has/st-flags.c b/build/has/st-flags.c new file mode 100644 index 0000000..b1d0c32 --- /dev/null +++ b/build/has/st-flags.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct stat sb = {0}; + return sb.st_flags; +} diff --git a/build/has/statx-syscall.c b/build/has/statx-syscall.c new file mode 100644 index 0000000..87ec869 --- /dev/null +++ b/build/has/statx-syscall.c @@ -0,0 +1,13 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include +#include + +int main(void) { + struct statx sb; + syscall(SYS_statx, AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); + return 0; +} diff --git a/build/has/statx.c b/build/has/statx.c new file mode 100644 index 0000000..65f1674 --- /dev/null +++ b/build/has/statx.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + struct statx sb; + statx(AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); + return 0; +} diff --git a/build/has/strerror-l.c b/build/has/strerror-l.c new file mode 100644 index 0000000..3dcc4d7 --- /dev/null +++ b/build/has/strerror-l.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + locale_t locale = duplocale(LC_GLOBAL_LOCALE); + return !strerror_l(ENOMEM, locale); +} diff --git a/build/has/strerror-r-gnu.c b/build/has/strerror-r-gnu.c new file mode 100644 index 0000000..26ca0ee --- /dev/null +++ b/build/has/strerror-r-gnu.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + char buf[256]; + // Check that strerror_r() returns a pointer + return *strerror_r(ENOMEM, buf, sizeof(buf)); +} diff --git a/build/has/strerror-r-posix.c b/build/has/strerror-r-posix.c new file mode 100644 index 0000000..41b2d30 --- /dev/null +++ b/build/has/strerror-r-posix.c @@ -0,0 +1,11 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + char buf[256]; + // Check that strerror_r() returns an integer + return 2 * strerror_r(ENOMEM, buf, sizeof(buf)); +} diff --git a/build/has/tm-gmtoff.c b/build/has/tm-gmtoff.c new file mode 100644 index 0000000..543df48 --- /dev/null +++ b/build/has/tm-gmtoff.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct tm tm = {0}; + return tm.tm_gmtoff; +} diff --git a/build/has/uselocale.c b/build/has/uselocale.c new file mode 100644 index 0000000..a712ff8 --- /dev/null +++ b/build/has/uselocale.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + locale_t locale = uselocale((locale_t)0); + return locale == LC_GLOBAL_LOCALE; +} diff --git a/build/header.mk b/build/header.mk new file mode 100644 index 0000000..3f77ca5 --- /dev/null +++ b/build/header.mk @@ -0,0 +1,69 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/config.h + +include build/prelude.mk +include gen/config.mk +include build/exports.mk + +# All header fragments we generate +HEADERS := \ + gen/has/acl-get-entry.h \ + gen/has/acl-get-file.h \ + gen/has/acl-get-tag-type.h \ + gen/has/acl-is-trivial-np.h \ + gen/has/acl-trivial.h \ + gen/has/aligned-alloc.h \ + gen/has/confstr.h \ + gen/has/extattr-get-file.h \ + gen/has/extattr-get-link.h \ + gen/has/extattr-list-file.h \ + gen/has/extattr-list-link.h \ + gen/has/fdclosedir.h \ + gen/has/getdents.h \ + gen/has/getdents64.h \ + gen/has/getdents64-syscall.h \ + gen/has/getprogname.h \ + gen/has/getprogname-gnu.h \ + gen/has/max-align-t.h \ + gen/has/pipe2.h \ + gen/has/posix-spawn-addfchdir.h \ + gen/has/posix-spawn-addfchdir-np.h \ + gen/has/st-acmtim.h \ + gen/has/st-acmtimespec.h \ + gen/has/st-birthtim.h \ + gen/has/st-birthtimespec.h \ + gen/has/st-flags.h \ + gen/has/statx.h \ + gen/has/statx-syscall.h \ + gen/has/strerror-l.h \ + gen/has/strerror-r-gnu.h \ + gen/has/strerror-r-posix.h \ + gen/has/tm-gmtoff.h \ + gen/has/uselocale.h + +# Previously generated by pkgs.mk +PKG_HEADERS := ${ALL_PKGS:%=gen/use/%.h} + +gen/config.h: ${PKG_HEADERS} ${HEADERS} + ${MSG} "[ GEN] $@" + printf '// %s\n' "$@" >$@ + printf '#ifndef BFS_CONFIG_H\n' >>$@ + printf '#define BFS_CONFIG_H\n' >>$@ + cat ${.ALLSRC} >>$@ + printf '#endif // BFS_CONFIG_H\n' >>$@ + cat ${.ALLSRC:%=%.log} >$@.log + ${VCAT} $@ +.PHONY: gen/config.h + +# The short name of the config test +SLUG = ${@:gen/%.h=%} + +${HEADERS}:: + ${MKDIR} ${@D} + if build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c >$@ 2>$@.log; then \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' ${SLUG}.c; \ + else \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' ${SLUG}.c; \ + fi diff --git a/build/pkgconf.sh b/build/pkgconf.sh new file mode 100755 index 0000000..96e4bf1 --- /dev/null +++ b/build/pkgconf.sh @@ -0,0 +1,100 @@ +#!/bin/sh + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# pkg-config wrapper with hardcoded fallbacks + +set -eu + +MODE= +case "${1:-}" in + --*) + MODE="$1" + shift +esac + +if [ $# -lt 1 ]; then + exit +fi + +case "$XNOLIBS" in + y|1) + exit 1 +esac + +if [ -z "$MODE" ]; then + # Check whether the libraries exist at all + for LIB; do + # Check ${USE_$LIB} + USE_LIB="USE_$(printf '%s' "$LIB" | tr 'a-z-' 'A-Z_')" + eval "USE=\"\${$USE_LIB:-}\"" + case "$USE" in + y|1) + continue + ;; + n|0) + exit 1 + ;; + esac + + CFLAGS=$("$0" --cflags "$LIB") || exit 1 + LDFLAGS=$("$0" --ldflags "$LIB") || exit 1 + LDLIBS=$("$0" --ldlibs "$LIB") || exit 1 + build/cc.sh $CFLAGS $LDFLAGS build/use/$LIB.c $LDLIBS || exit 1 + done +fi + +# Defer to pkg-config if possible +if command -v "${XPKG_CONFIG:-}" >/dev/null 2>&1; then + case "$MODE" in + --cflags) + "$XPKG_CONFIG" --cflags "$@" + ;; + --ldflags) + "$XPKG_CONFIG" --libs-only-L --libs-only-other "$@" + ;; + --ldlibs) + "$XPKG_CONFIG" --libs-only-l "$@" + ;; + esac + + exit +fi + +# pkg-config unavailable, emulate it ourselves +CFLAGS="" +LDFLAGS="" +LDLIBS="" + +for LIB; do + case "$LIB" in + libacl) + LDLIB=-lacl + ;; + libcap) + LDLIB=-lcap + ;; + libselinux) + LDLIB=-lselinux + ;; + liburing) + LDLIB=-luring + ;; + oniguruma) + LDLIB=-lonig + ;; + *) + printf 'error: Unknown package %s\n' "$LIB" >&2 + exit 1 + ;; + esac + + LDLIBS="$LDLIBS$LDLIB " +done + +case "$MODE" in + --ldlibs) + printf '%s\n' "$LDLIBS" + ;; +esac diff --git a/build/pkgs.mk b/build/pkgs.mk new file mode 100644 index 0000000..51ca664 --- /dev/null +++ b/build/pkgs.mk @@ -0,0 +1,36 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Makefile that generates gen/pkgs.mk + +include build/prelude.mk +include gen/vars.mk +include gen/flags.mk +include build/exports.mk + +HEADERS := ${ALL_PKGS:%=gen/use/%.h} + +gen/pkgs.mk: ${HEADERS} + ${MSG} "[ GEN] $@" + printf '# %s\n' "$@" >$@ + gen() { \ + printf 'PKGS := %s\n' "$$*"; \ + printf 'CFLAGS += %s\n' "$$(build/pkgconf.sh --cflags "$$@")"; \ + printf 'LDFLAGS += %s\n' "$$(build/pkgconf.sh --ldflags "$$@")"; \ + printf 'LDLIBS := %s $${LDLIBS}\n' "$$(build/pkgconf.sh --ldlibs "$$@")"; \ + }; \ + gen $$(grep -l ' true$$' ${.ALLSRC} | sed 's|.*/\(.*\)\.h|\1|') >>$@ + ${VCAT} $@ + +.PHONY: gen/pkgs.mk + +# Convert gen/use/foo.h to foo +PKG = ${@:gen/use/%.h=%} + +${HEADERS}:: + ${MKDIR} ${@D} + if build/define-if.sh use/${PKG} build/pkgconf.sh ${PKG} >$@ 2>$@.log; then \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' use/${PKG}.c; \ + else \ + test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' use/${PKG}.c; \ + fi diff --git a/build/prelude.mk b/build/prelude.mk new file mode 100644 index 0000000..b235fa9 --- /dev/null +++ b/build/prelude.mk @@ -0,0 +1,133 @@ +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Common makefile utilities. Compatible with both GNU make and most BSD makes. + +# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to +.OBJDIR: . + +# We don't use any suffix rules +.SUFFIXES: + +# GNU make has $^ for the full list of targets, while BSD make has $> and the +# long-form ${.ALLSRC}. We could write $^ $> to get them both, but that would +# break if one of them implemented support for the other. So instead, bring +# BSD's ${.ALLSRC} to GNU. +.ALLSRC ?= $^ + +# Installation paths +DESTDIR ?= +PREFIX ?= /usr +MANDIR ?= ${PREFIX}/share/man + +# Configurable executables +CC ?= cc +INSTALL ?= install +MKDIR ?= mkdir -p +PKG_CONFIG ?= pkg-config +RM ?= rm -f + +# GNU and BSD make have incompatible syntax for conditionals, but we can do a +# lot with just nested variable expansion. We use "y" as the canonical +# truthy value, and "" (the empty string) as the canonical falsey value. +# +# To normalize a boolean, use ${TRUTHY,${VAR}}, which expands like this: +# +# VAR=y ${TRUTHY,${VAR}} => ${TRUTHY,y} => y +# VAR=1 ${TRUTHY,${VAR}} => ${TRUTHY,1} => y +# VAR=n ${TRUTHY,${VAR}} => ${TRUTHY,n} => [empty] +# VAR=other ${TRUTHY,${VAR}} => ${TRUTHY,other} => [empty] +# VAR= ${TRUTHY,${VAR}} => ${TRUTHY,} => [emtpy] +# +# Inspired by https://github.com/wahern/autoguess +TRUTHY,y := y +TRUTHY,1 := y + +# Boolean operators are also implemented with nested expansion +NOT, := y + +# Normalize ${V} to either "y" or "" +IS_V := ${TRUTHY,${V}} + +# Suppress output unless V=1 +Q, := @ +Q := ${Q,${IS_V}} + +# Show full commands with `make V=1`, otherwise short summaries +MSG = @msg() { \ + MSG="$$1"; \ + shift; \ + test "${IS_V}" || printf '%s\n' "$$MSG"; \ + test "$${1:-}" || return 0; \ + test "${IS_V}" && printf '%s\n' "$$*"; \ + "$$@"; \ + }; \ + msg + +# Maximum width of a short message, to align the ✔/✘ +MSG_WIDTH := 30 + +# cat a file if V=1 +VCAT,y := @cat +VCAT, := @: +VCAT := ${VCAT,${IS_V}} + +# All external dependencies +ALL_PKGS := \ + libacl \ + libcap \ + libselinux \ + liburing \ + oniguruma + +# List all object files here, as they're needed by both `./configure` and `make` + +# All object files except the entry point +LIBBFS := \ + obj/src/alloc.o \ + obj/src/bar.o \ + obj/src/bfstd.o \ + obj/src/bftw.o \ + obj/src/color.o \ + obj/src/ctx.o \ + obj/src/diag.o \ + obj/src/dir.o \ + obj/src/dstring.o \ + obj/src/eval.o \ + obj/src/exec.o \ + obj/src/expr.o \ + obj/src/fsade.o \ + obj/src/ioq.o \ + obj/src/mtab.o \ + obj/src/opt.o \ + obj/src/parse.o \ + obj/src/printf.o \ + obj/src/pwcache.o \ + obj/src/stat.o \ + obj/src/thread.o \ + obj/src/trie.o \ + obj/src/typo.o \ + obj/src/xregex.o \ + obj/src/xspawn.o \ + obj/src/xtime.o \ + obj/gen/version.o + +# Unit test objects +UNIT_OBJS := \ + obj/tests/alloc.o \ + obj/tests/bfstd.o \ + obj/tests/bit.o \ + obj/tests/ioq.o \ + obj/tests/main.o \ + obj/tests/trie.o \ + obj/tests/xspawn.o \ + obj/tests/xtime.o + +# All object files +OBJS := \ + obj/src/main.o \ + obj/tests/mksock.o \ + obj/tests/xspawnee.o \ + obj/tests/xtouch.o \ + ${LIBBFS} \ + ${UNIT_OBJS} diff --git a/build/use/libacl.c b/build/use/libacl.c new file mode 100644 index 0000000..de1fe50 --- /dev/null +++ b/build/use/libacl.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + acl_free(0); + return 0; +} diff --git a/build/use/libcap.c b/build/use/libcap.c new file mode 100644 index 0000000..58e832c --- /dev/null +++ b/build/use/libcap.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + cap_free(0); + return 0; +} diff --git a/build/use/libselinux.c b/build/use/libselinux.c new file mode 100644 index 0000000..bca409d --- /dev/null +++ b/build/use/libselinux.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + freecon(0); + return 0; +} diff --git a/build/use/liburing.c b/build/use/liburing.c new file mode 100644 index 0000000..bea499a --- /dev/null +++ b/build/use/liburing.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + io_uring_free_probe(0); + return 0; +} diff --git a/build/use/oniguruma.c b/build/use/oniguruma.c new file mode 100644 index 0000000..cb17596 --- /dev/null +++ b/build/use/oniguruma.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + onig_free(0); + return 0; +} diff --git a/config/cc.sh b/config/cc.sh deleted file mode 100755 index 45d51ca..0000000 --- a/config/cc.sh +++ /dev/null @@ -1,16 +0,0 @@ -#!/bin/sh - -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Run the compiler and check if it succeeded - -set -eu - -TMP=$(mktemp) -trap 'rm -f "$TMP"' EXIT - -( - set -x - $XCC $XCPPFLAGS $XCFLAGS $XLDFLAGS "$@" $XLDLIBS -o "$TMP" -) diff --git a/config/config.mk b/config/config.mk deleted file mode 100644 index 280c6ac..0000000 --- a/config/config.mk +++ /dev/null @@ -1,63 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Makefile fragment that implements `make config` - -include config/prelude.mk -include config/exports.mk - -# All configuration steps -config: ${CONFIG} ${GEN}/config.h -.PHONY: config - -# Makefile fragments generated by `make config` -MKS := \ - ${GEN}/vars.mk \ - ${GEN}/flags.mk \ - ${GEN}/deps.mk \ - ${GEN}/pkgs.mk - -# The main configuration file, which includes the others -${CONFIG}: ${MKS} - ${MSG} "[ GEN] ${TGT}" - @printf '# %s\n' "${TGT}" >$@ - @printf 'include $${GEN}/%s\n' ${MKS:${GEN}/%=%} >>$@ - ${VCAT} ${CONFIG} -.PHONY: ${CONFIG} - -# Saves the configurable variables -${GEN}/vars.mk:: - @${MKDIR} ${@D} - ${MSG} "[ GEN] ${TGT}" - @printf '# %s\n' "${TGT}" >$@ - @printf 'PREFIX := %s\n' "$$XPREFIX" >>$@ - @printf 'MANDIR := %s\n' "$$XMANDIR" >>$@ - @printf 'OS := %s\n' "$${OS:-$$(uname)}" >>$@ - @printf 'CC := %s\n' "$$XCC" >>$@ - @printf 'INSTALL := %s\n' "$$XINSTALL" >>$@ - @printf 'MKDIR := %s\n' "$$XMKDIR" >>$@ - @printf 'PKG_CONFIG := %s\n' "$$XPKG_CONFIG" >>$@ - @printf 'RM := %s\n' "$$XRM" >>$@ - @printf 'PKGS :=\n' >>$@ - ${VCAT} $@ - -# Sets the build flags. This depends on vars.mk and uses a recursive make so -# that the default flags can depend on variables like ${OS}. -${GEN}/flags.mk: ${GEN}/vars.mk - @+${MAKE} -sf config/flags.mk $@ -.PHONY: ${GEN}/flags.mk - -# Check for dependency generation support -${GEN}/deps.mk: ${GEN}/flags.mk - @+${MAKE} -sf config/deps.mk $@ -.PHONY: ${GEN}/deps.mk - -# Auto-detect dependencies and their build flags -${GEN}/pkgs.mk: ${GEN}/flags.mk - @+${MAKE} -sf config/pkgs.mk $@ -.PHONY: ${GEN}/pkgs.mk - -# Compile-time feature detection -${GEN}/config.h: ${CONFIG} - @+${MAKE} -sf config/header.mk $@ -.PHONY: ${GEN}/config.h diff --git a/config/define-if.sh b/config/define-if.sh deleted file mode 100755 index 059c1ac..0000000 --- a/config/define-if.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/bin/sh - -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Output a C preprocessor definition based on whether a command succeeds - -set -eu - -SLUG="${1#config/}" -SLUG="${SLUG%.c}" -MACRO="BFS_$(printf '%s' "$SLUG" | tr '/a-z-' '_A-Z_')" -shift - -if "$@"; then - printf '#define %s true\n' "$MACRO" -else - printf '#define %s false\n' "$MACRO" - exit 1 -fi diff --git a/config/deps.mk b/config/deps.mk deleted file mode 100644 index ac394a5..0000000 --- a/config/deps.mk +++ /dev/null @@ -1,18 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/deps.mk - -include config/prelude.mk -include ${GEN}/vars.mk -include ${GEN}/flags.mk -include config/exports.mk - -${GEN}/deps.mk:: - ${MSG} "[ GEN] ${TGT}" - printf '# %s\n' "${TGT}" >$@ - if config/cc.sh -MD -MP -MF /dev/null config/empty.c; then \ - printf 'DEPFLAGS = -MD -MP\n'; \ - fi >>$@ 2>$@.log - ${VCAT} $@ - printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ diff --git a/config/empty.c b/config/empty.c deleted file mode 100644 index 4fa9a5b..0000000 --- a/config/empty.c +++ /dev/null @@ -1,6 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -int main(void) { - return 0; -} diff --git a/config/exports.mk b/config/exports.mk deleted file mode 100644 index ed19134..0000000 --- a/config/exports.mk +++ /dev/null @@ -1,20 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Makefile fragment that exports variables used by configuration scripts - -export XPREFIX=${PREFIX} -export XMANDIR=${MANDIR} - -export XCC=${CC} -export XINSTALL=${INSTALL} -export XMKDIR=${MKDIR} -export XPKG_CONFIG=${PKG_CONFIG} -export XRM=${RM} - -export XCPPFLAGS=${CPPFLAGS} -export XCFLAGS=${CFLAGS} -export XLDFLAGS=${LDFLAGS} -export XLDLIBS=${LDLIBS} - -export XNOLIBS=${NOLIBS} diff --git a/config/flags.mk b/config/flags.mk deleted file mode 100644 index 837c30f..0000000 --- a/config/flags.mk +++ /dev/null @@ -1,136 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/flags.mk - -include config/prelude.mk -include ${GEN}/vars.mk - -# Configurable flags -CPPFLAGS ?= -CFLAGS ?= \ - -g \ - -Wall \ - -Wformat=2 \ - -Werror=implicit \ - -Wimplicit-fallthrough \ - -Wmissing-declarations \ - -Wshadow \ - -Wsign-compare \ - -Wstrict-prototypes -LDFLAGS ?= -LDLIBS ?= - -export XCPPFLAGS=${CPPFLAGS} -export XCFLAGS=${CFLAGS} -export XLDFLAGS=${LDFLAGS} -export XLDLIBS=${LDLIBS} - -# Immutable flags -export BFS_CPPFLAGS= \ - -Isrc \ - -I${GEN} \ - -D__EXTENSIONS__ \ - -D_ATFILE_SOURCE \ - -D_BSD_SOURCE \ - -D_DARWIN_C_SOURCE \ - -D_DEFAULT_SOURCE \ - -D_GNU_SOURCE \ - -D_POSIX_PTHREAD_SEMANTICS \ - -D_FILE_OFFSET_BITS=64 \ - -D_TIME_BITS=64 -export BFS_CFLAGS= -std=c17 -pthread - -# Platform-specific system libraries -LDLIBS,DragonFly := -lposix1e -LDLIBS,Linux := -lrt -LDLIBS,NetBSD := -lutil -LDLIBS,SunOS := -lsec -lsocket -lnsl -export BFS_LDLIBS=${LDLIBS,${OS}} - -# Build profiles -_ASAN := ${TRUTHY,${ASAN}} -_LSAN := ${TRUTHY,${LSAN}} -_MSAN := ${TRUTHY,${MSAN}} -_TSAN := ${TRUTHY,${TSAN}} -_UBSAN := ${TRUTHY,${UBSAN}} -_GCOV := ${TRUTHY,${GCOV}} -_LINT := ${TRUTHY,${LINT}} -_RELEASE := ${TRUTHY,${RELEASE}} - -# https://github.com/google/sanitizers/issues/342 -TSAN_CPPFLAGS,y := -DBFS_USE_TARGET_CLONES=0 -export TSAN_CPPFLAGS=${TSAN_CPPFLAGS,${_TSAN}} - -ASAN_CFLAGS,y := -fsanitize=address -LSAN_CFLAGS,y := -fsanitize=leak -MSAN_CFLAGS,y := -fsanitize=memory -fsanitize-memory-track-origins -TSAN_CFLAGS,y := -fsanitize=thread -UBSAN_CFLAGS.y := -fsanitize=undefined - -export ASAN_CFLAGS=${ASAN_CFLAGS,${_ASAN}} -export LSAN_CFLAGS=${LSAN_CFLAGS,${_LSAN}} -export MSAN_CFLAGS=${MSAN_CFLAGS,${_MSAN}} -export TSAN_CFLAGS=${TSAN_CFLAGS,${_TSAN}} -export UBSAN_CFLAGS=${UBSAN_CFLAGS,${_UBSAN}} - -SAN_CFLAGS,y := -fno-sanitize-recover=all -INSANE := ${NOT,${_ASAN}${_LSAN}${_MSAN}${_TSAN}${_UBSAN}} -SAN := ${NOT,${INSANE}} -export SAN_CFLAGS=${SAN_CFLAGS,${SAN}} - -# MSAN and TSAN both need all code to be instrumented -YESLIBS := ${NOT,${_MSAN}${_TSAN}} -NOLIBS ?= ${NOT,${YESLIBS}} -export XNOLIBS=${NOLIBS} - -# gcov only intercepts fork()/exec() with -std=gnu* -GCOV_CFLAGS,y := -std=gnu17 --coverage -export GCOV_CFLAGS=${GCOV_CFLAGS,${_GCOV}} - -LINT_CPPFLAGS,y := -D_FORTIFY_SOURCE=3 -DBFS_LINT -LINT_CFLAGS,y := -Werror -O2 - -export LINT_CPPFLAGS=${LINT_CPPFLAGS,${_LINT}} -export LINT_CFLAGS=${LINT_CFLAGS,${_LINT}} - -RELEASE_CPPFLAGS,y := -DNDEBUG -RELEASE_CFLAGS,y := -O3 -flto=auto - -export RELEASE_CPPFLAGS=${RELEASE_CPPFLAGS,${_RELEASE}} -export RELEASE_CFLAGS=${RELEASE_CFLAGS,${_RELEASE}} - -# Set a variable -SETVAR = printf '%s := %s\n' >>$@ - -# Append to a variable, if non-empty -APPEND = append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append - -${GEN}/flags.mk:: - ${MSG} "[ GEN] ${TGT}" - printf '# %s\n' "${TGT}" >$@ - ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS" - ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS" - ${APPEND} CPPFLAGS "$$LINT_CPPFLAGS" - ${APPEND} CPPFLAGS "$$RELEASE_CPPFLAGS" - ${APPEND} CPPFLAGS "$$XCPPFLAGS" - ${APPEND} CPPFLAGS "$$EXTRA_CPPFLAGS" - ${SETVAR} CFLAGS "$$BFS_CFLAGS" - ${APPEND} CFLAGS "$$ASAN_CFLAGS" - ${APPEND} CFLAGS "$$LSAN_CFLAGS" - ${APPEND} CFLAGS "$$MSAN_CFLAGS" - ${APPEND} CFLAGS "$$TSAN_CFLAGS" - ${APPEND} CFLAGS "$$UBSAN_CFLAGS" - ${APPEND} CFLAGS "$$SAN_CFLAGS" - ${APPEND} CFLAGS "$$GCOV_CFLAGS" - ${APPEND} CFLAGS "$$LINT_CFLAGS" - ${APPEND} CFLAGS "$$RELEASE_CFLAGS" - ${APPEND} CFLAGS "$$XCFLAGS" - ${APPEND} CFLAGS "$$EXTRA_CFLAGS" - ${SETVAR} LDFLAGS "$$XLDFLAGS" - ${SETVAR} LDLIBS "$$XLDLIBS" - ${APPEND} LDLIBS "$$EXTRA_LDLIBS" - ${APPEND} LDLIBS "$$BFS_LDLIBS" - ${SETVAR} NOLIBS "$$XNOLIBS" - test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ - ${VCAT} $@ diff --git a/config/has/acl-get-entry.c b/config/has/acl-get-entry.c deleted file mode 100644 index 3cce771..0000000 --- a/config/has/acl-get-entry.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include - -int main(void) { - acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); - acl_entry_t entry; - return acl_get_entry(acl, ACL_FIRST_ENTRY, &entry); -} diff --git a/config/has/acl-get-file.c b/config/has/acl-get-file.c deleted file mode 100644 index 89fbf23..0000000 --- a/config/has/acl-get-file.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include -#include - -int main(void) { - acl_t acl = acl_get_file(".", ACL_TYPE_DEFAULT); - return acl == (acl_t)NULL; -} diff --git a/config/has/acl-get-tag-type.c b/config/has/acl-get-tag-type.c deleted file mode 100644 index 2901956..0000000 --- a/config/has/acl-get-tag-type.c +++ /dev/null @@ -1,10 +0,0 @@ -#include -#include -#include - -int main(void) { - acl_entry_t entry; - memset(&entry, 0, sizeof(entry)); - acl_tag_t tag; - return acl_get_tag_type(entry, &tag); -} diff --git a/config/has/acl-is-trivial-np.c b/config/has/acl-is-trivial-np.c deleted file mode 100644 index 9ca9fc7..0000000 --- a/config/has/acl-is-trivial-np.c +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - acl_t acl = acl_get_fd(3); - int trivial; - acl_is_trivial_np(acl, &trivial); - return 0; -} diff --git a/config/has/acl-trivial.c b/config/has/acl-trivial.c deleted file mode 100644 index 7efc838..0000000 --- a/config/has/acl-trivial.c +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - return acl_trivial("."); -} diff --git a/config/has/aligned-alloc.c b/config/has/aligned-alloc.c deleted file mode 100644 index 4460038..0000000 --- a/config/has/aligned-alloc.c +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - return !aligned_alloc(_Alignof(void *), sizeof(void *)); -} diff --git a/config/has/confstr.c b/config/has/confstr.c deleted file mode 100644 index 58280b4..0000000 --- a/config/has/confstr.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - confstr(_CS_PATH, NULL, 0); - return 0; -} diff --git a/config/has/extattr-get-file.c b/config/has/extattr-get-file.c deleted file mode 100644 index ac9cf96..0000000 --- a/config/has/extattr-get-file.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - return extattr_get_file("file", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); -} diff --git a/config/has/extattr-get-link.c b/config/has/extattr-get-link.c deleted file mode 100644 index c35be5b..0000000 --- a/config/has/extattr-get-link.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - return extattr_get_link("link", EXTATTR_NAMESPACE_USER, "xattr", NULL, 0); -} diff --git a/config/has/extattr-list-file.c b/config/has/extattr-list-file.c deleted file mode 100644 index e68a8bb..0000000 --- a/config/has/extattr-list-file.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - return extattr_list_file("file", EXTATTR_NAMESPACE_USER, NULL, 0); -} diff --git a/config/has/extattr-list-link.c b/config/has/extattr-list-link.c deleted file mode 100644 index 49f0ec2..0000000 --- a/config/has/extattr-list-link.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - return extattr_list_link("link", EXTATTR_NAMESPACE_USER, NULL, 0); -} diff --git a/config/has/fdclosedir.c b/config/has/fdclosedir.c deleted file mode 100644 index f4ad1f5..0000000 --- a/config/has/fdclosedir.c +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - return fdclosedir(opendir(".")); -} diff --git a/config/has/getdents.c b/config/has/getdents.c deleted file mode 100644 index d0d4228..0000000 --- a/config/has/getdents.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct dirent de; - getdents(3, &de, 1024); - return 0; -} diff --git a/config/has/getdents64-syscall.c b/config/has/getdents64-syscall.c deleted file mode 100644 index 4838c14..0000000 --- a/config/has/getdents64-syscall.c +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - struct dirent64 de; - syscall(SYS_getdents64, 3, &de, 1024); - return 0; -} diff --git a/config/has/getdents64.c b/config/has/getdents64.c deleted file mode 100644 index 1abf36d..0000000 --- a/config/has/getdents64.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct dirent64 de; - getdents64(3, &de, 1024); - return 0; -} diff --git a/config/has/getprogname-gnu.c b/config/has/getprogname-gnu.c deleted file mode 100644 index 6b97c5e..0000000 --- a/config/has/getprogname-gnu.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - const char *str = program_invocation_short_name; - return str[0]; -} diff --git a/config/has/getprogname.c b/config/has/getprogname.c deleted file mode 100644 index 83dc8e8..0000000 --- a/config/has/getprogname.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - const char *str = getprogname(); - return str[0]; -} diff --git a/config/has/max-align-t.c b/config/has/max-align-t.c deleted file mode 100644 index 96165ce..0000000 --- a/config/has/max-align-t.c +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - return _Alignof(max_align_t); -} diff --git a/config/has/pipe2.c b/config/has/pipe2.c deleted file mode 100644 index 4cb43b5..0000000 --- a/config/has/pipe2.c +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - int fds[2]; - return pipe2(fds, O_CLOEXEC); -} diff --git a/config/has/posix-spawn-addfchdir-np.c b/config/has/posix-spawn-addfchdir-np.c deleted file mode 100644 index b870a53..0000000 --- a/config/has/posix-spawn-addfchdir-np.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - posix_spawn_file_actions_addfchdir_np(&actions, 3); - return 0; -} diff --git a/config/has/posix-spawn-addfchdir.c b/config/has/posix-spawn-addfchdir.c deleted file mode 100644 index c52ff81..0000000 --- a/config/has/posix-spawn-addfchdir.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - posix_spawn_file_actions_t actions; - posix_spawn_file_actions_init(&actions); - posix_spawn_file_actions_addfchdir(&actions, 3); - return 0; -} diff --git a/config/has/st-acmtim.c b/config/has/st-acmtim.c deleted file mode 100644 index d687ab0..0000000 --- a/config/has/st-acmtim.c +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - unsigned int a = sb.st_atim.tv_sec; - unsigned int c = sb.st_ctim.tv_sec; - unsigned int m = sb.st_mtim.tv_sec; - return a + c + m; -} diff --git a/config/has/st-acmtimespec.c b/config/has/st-acmtimespec.c deleted file mode 100644 index f747bc0..0000000 --- a/config/has/st-acmtimespec.c +++ /dev/null @@ -1,12 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - unsigned int a = sb.st_atimespec.tv_sec; - unsigned int c = sb.st_ctimespec.tv_sec; - unsigned int m = sb.st_mtimespec.tv_sec; - return a + c + m; -} diff --git a/config/has/st-birthtim.c b/config/has/st-birthtim.c deleted file mode 100644 index 4964571..0000000 --- a/config/has/st-birthtim.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - return sb.st_birthtim.tv_sec; -} diff --git a/config/has/st-birthtimespec.c b/config/has/st-birthtimespec.c deleted file mode 100644 index 91a613f..0000000 --- a/config/has/st-birthtimespec.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - return sb.st_birthtimespec.tv_sec; -} diff --git a/config/has/st-flags.c b/config/has/st-flags.c deleted file mode 100644 index b1d0c32..0000000 --- a/config/has/st-flags.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct stat sb = {0}; - return sb.st_flags; -} diff --git a/config/has/statx-syscall.c b/config/has/statx-syscall.c deleted file mode 100644 index 87ec869..0000000 --- a/config/has/statx-syscall.c +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include -#include - -int main(void) { - struct statx sb; - syscall(SYS_statx, AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); - return 0; -} diff --git a/config/has/statx.c b/config/has/statx.c deleted file mode 100644 index 65f1674..0000000 --- a/config/has/statx.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - struct statx sb; - statx(AT_FDCWD, ".", 0, STATX_BASIC_STATS, &sb); - return 0; -} diff --git a/config/has/strerror-l.c b/config/has/strerror-l.c deleted file mode 100644 index 3dcc4d7..0000000 --- a/config/has/strerror-l.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include -#include - -int main(void) { - locale_t locale = duplocale(LC_GLOBAL_LOCALE); - return !strerror_l(ENOMEM, locale); -} diff --git a/config/has/strerror-r-gnu.c b/config/has/strerror-r-gnu.c deleted file mode 100644 index 26ca0ee..0000000 --- a/config/has/strerror-r-gnu.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - char buf[256]; - // Check that strerror_r() returns a pointer - return *strerror_r(ENOMEM, buf, sizeof(buf)); -} diff --git a/config/has/strerror-r-posix.c b/config/has/strerror-r-posix.c deleted file mode 100644 index 41b2d30..0000000 --- a/config/has/strerror-r-posix.c +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include -#include - -int main(void) { - char buf[256]; - // Check that strerror_r() returns an integer - return 2 * strerror_r(ENOMEM, buf, sizeof(buf)); -} diff --git a/config/has/tm-gmtoff.c b/config/has/tm-gmtoff.c deleted file mode 100644 index 543df48..0000000 --- a/config/has/tm-gmtoff.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - struct tm tm = {0}; - return tm.tm_gmtoff; -} diff --git a/config/has/uselocale.c b/config/has/uselocale.c deleted file mode 100644 index a712ff8..0000000 --- a/config/has/uselocale.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - locale_t locale = uselocale((locale_t)0); - return locale == LC_GLOBAL_LOCALE; -} diff --git a/config/header.mk b/config/header.mk deleted file mode 100644 index 50ad5e8..0000000 --- a/config/header.mk +++ /dev/null @@ -1,69 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/config.h - -include config/prelude.mk -include ${GEN}/config.mk -include config/exports.mk - -# All header fragments we generate -HEADERS := \ - ${GEN}/has/acl-get-entry.h \ - ${GEN}/has/acl-get-file.h \ - ${GEN}/has/acl-get-tag-type.h \ - ${GEN}/has/acl-is-trivial-np.h \ - ${GEN}/has/acl-trivial.h \ - ${GEN}/has/aligned-alloc.h \ - ${GEN}/has/confstr.h \ - ${GEN}/has/extattr-get-file.h \ - ${GEN}/has/extattr-get-link.h \ - ${GEN}/has/extattr-list-file.h \ - ${GEN}/has/extattr-list-link.h \ - ${GEN}/has/fdclosedir.h \ - ${GEN}/has/getdents.h \ - ${GEN}/has/getdents64.h \ - ${GEN}/has/getdents64-syscall.h \ - ${GEN}/has/getprogname.h \ - ${GEN}/has/getprogname-gnu.h \ - ${GEN}/has/max-align-t.h \ - ${GEN}/has/pipe2.h \ - ${GEN}/has/posix-spawn-addfchdir.h \ - ${GEN}/has/posix-spawn-addfchdir-np.h \ - ${GEN}/has/st-acmtim.h \ - ${GEN}/has/st-acmtimespec.h \ - ${GEN}/has/st-birthtim.h \ - ${GEN}/has/st-birthtimespec.h \ - ${GEN}/has/st-flags.h \ - ${GEN}/has/statx.h \ - ${GEN}/has/statx-syscall.h \ - ${GEN}/has/strerror-l.h \ - ${GEN}/has/strerror-r-gnu.h \ - ${GEN}/has/strerror-r-posix.h \ - ${GEN}/has/tm-gmtoff.h \ - ${GEN}/has/uselocale.h - -# Previously generated by pkgs.mk -PKG_HEADERS := ${ALL_PKGS:%=${GEN}/use/%.h} - -${GEN}/config.h: ${PKG_HEADERS} ${HEADERS} - ${MSG} "[ GEN] ${TGT}" - printf '// %s\n' "${TGT}" >$@ - printf '#ifndef BFS_CONFIG_H\n' >>$@ - printf '#define BFS_CONFIG_H\n' >>$@ - cat ${.ALLSRC} >>$@ - printf '#endif // BFS_CONFIG_H\n' >>$@ - cat ${.ALLSRC:%=%.log} >$@.log - ${VCAT} $@ -.PHONY: ${GEN}/config.h - -# The short name of the config test -SLUG = ${@:${GEN}/%.h=%} - -${HEADERS}:: - ${MKDIR} ${@D} - if config/define-if.sh ${SLUG} config/cc.sh config/${SLUG}.c >$@ 2>$@.log; then \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' ${SLUG}.c; \ - else \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' ${SLUG}.c; \ - fi diff --git a/config/pkgconf.sh b/config/pkgconf.sh deleted file mode 100755 index 365ed61..0000000 --- a/config/pkgconf.sh +++ /dev/null @@ -1,100 +0,0 @@ -#!/bin/sh - -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# pkg-config wrapper with hardcoded fallbacks - -set -eu - -MODE= -case "${1:-}" in - --*) - MODE="$1" - shift -esac - -if [ $# -lt 1 ]; then - exit -fi - -case "$XNOLIBS" in - y|1) - exit 1 -esac - -if [ -z "$MODE" ]; then - # Check whether the libraries exist at all - for LIB; do - # Check ${USE_$LIB} - USE_LIB="USE_$(printf '%s' "$LIB" | tr 'a-z-' 'A-Z_')" - eval "USE=\"\${$USE_LIB:-}\"" - case "$USE" in - y|1) - continue - ;; - n|0) - exit 1 - ;; - esac - - CFLAGS=$("$0" --cflags "$LIB") || exit 1 - LDFLAGS=$("$0" --ldflags "$LIB") || exit 1 - LDLIBS=$("$0" --ldlibs "$LIB") || exit 1 - config/cc.sh $CFLAGS $LDFLAGS config/use/$LIB.c $LDLIBS || exit 1 - done -fi - -# Defer to pkg-config if possible -if command -v "${XPKG_CONFIG:-}" >/dev/null 2>&1; then - case "$MODE" in - --cflags) - "$XPKG_CONFIG" --cflags "$@" - ;; - --ldflags) - "$XPKG_CONFIG" --libs-only-L --libs-only-other "$@" - ;; - --ldlibs) - "$XPKG_CONFIG" --libs-only-l "$@" - ;; - esac - - exit -fi - -# pkg-config unavailable, emulate it ourselves -CFLAGS="" -LDFLAGS="" -LDLIBS="" - -for LIB; do - case "$LIB" in - libacl) - LDLIB=-lacl - ;; - libcap) - LDLIB=-lcap - ;; - libselinux) - LDLIB=-lselinux - ;; - liburing) - LDLIB=-luring - ;; - oniguruma) - LDLIB=-lonig - ;; - *) - printf 'error: Unknown package %s\n' "$LIB" >&2 - exit 1 - ;; - esac - - LDLIBS="$LDLIBS$LDLIB " -done - -case "$MODE" in - --ldlibs) - printf '%s\n' "$LDLIBS" - ;; -esac diff --git a/config/pkgs.mk b/config/pkgs.mk deleted file mode 100644 index 898f262..0000000 --- a/config/pkgs.mk +++ /dev/null @@ -1,36 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Makefile that generates gen/pkgs.mk - -include config/prelude.mk -include ${GEN}/vars.mk -include ${GEN}/flags.mk -include config/exports.mk - -HEADERS := ${ALL_PKGS:%=${GEN}/use/%.h} - -${GEN}/pkgs.mk: ${HEADERS} - ${MSG} "[ GEN] ${TGT}" - printf '# %s\n' "${TGT}" >$@ - gen() { \ - printf 'PKGS := %s\n' "$$*"; \ - printf 'CFLAGS += %s\n' "$$(config/pkgconf.sh --cflags "$$@")"; \ - printf 'LDFLAGS += %s\n' "$$(config/pkgconf.sh --ldflags "$$@")"; \ - printf 'LDLIBS := %s $${LDLIBS}\n' "$$(config/pkgconf.sh --ldlibs "$$@")"; \ - }; \ - gen $$(grep -l ' true$$' ${.ALLSRC} | sed 's|.*/\(.*\)\.h|\1|') >>$@ - ${VCAT} $@ - -.PHONY: ${GEN}/pkgs.mk - -# Convert ${GEN}/use/foo.h to foo -PKG = ${@:${GEN}/use/%.h=%} - -${HEADERS}:: - ${MKDIR} ${@D} - if config/define-if.sh use/${PKG} config/pkgconf.sh ${PKG} >$@ 2>$@.log; then \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' use/${PKG}.c; \ - else \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' use/${PKG}.c; \ - fi diff --git a/config/prelude.mk b/config/prelude.mk deleted file mode 100644 index 109e60c..0000000 --- a/config/prelude.mk +++ /dev/null @@ -1,162 +0,0 @@ -# Copyright © Tavian Barnes -# SPDX-License-Identifier: 0BSD - -# Common makefile utilities. Compatible with both GNU make and most BSD makes. - -# BSD make will chdir into ${.OBJDIR} by default, unless we tell it not to -.OBJDIR: . - -# We don't use any suffix rules -.SUFFIXES: - -# GNU make has $^ for the full list of targets, while BSD make has $> and the -# long-form ${.ALLSRC}. We could write $^ $> to get them both, but that would -# break if one of them implemented support for the other. So instead, bring -# BSD's ${.ALLSRC} to GNU. -.ALLSRC ?= $^ - -# For out-of-tree builds, e.g. -# -# $ make config BUILDDIR=/path/to/build/dir -# $ make BUILDDIR=/path/to/build/dir -BUILDDIR ?= . - -# Shorthand for build subdirectories -BIN := ${BUILDDIR}/bin -GEN := ${BUILDDIR}/gen -OBJ := ${BUILDDIR}/obj - -# GNU make strips a leading ./ from target names, so do the same for BSD make -BIN := ${BIN:./%=%} -GEN := ${GEN:./%=%} -OBJ := ${OBJ:./%=%} - -# The configuration file generated by `make config` -CONFIG := ${GEN}/config.mk - -# Installation paths -DESTDIR ?= -PREFIX ?= /usr -MANDIR ?= ${PREFIX}/share/man - -# GNU make supports `export VAR`, but BSD make requires `export VAR=value`. -# Sadly, GNU make gives a recursion error on `export VAR=${VAR}`. -_BUILDDIR := ${BUILDDIR} -export BUILDDIR=${_BUILDDIR} - -# Configurable executables; can be overridden with -# -# $ make config CC=clang -CC ?= cc -INSTALL ?= install -MKDIR ?= mkdir -p -PKG_CONFIG ?= pkg-config -RM ?= rm -f - -# GNU and BSD make have incompatible syntax for conditionals, but we can do a -# lot with just nested variable expansion. We use "y" as the canonical -# truthy value, and "" (the empty string) as the canonical falsey value. -# -# To normalize a boolean, use ${TRUTHY,${VAR}}, which expands like this: -# -# VAR=y ${TRUTHY,${VAR}} => ${TRUTHY,y} => y -# VAR=1 ${TRUTHY,${VAR}} => ${TRUTHY,1} => y -# VAR=n ${TRUTHY,${VAR}} => ${TRUTHY,n} => [empty] -# VAR=other ${TRUTHY,${VAR}} => ${TRUTHY,other} => [empty] -# VAR= ${TRUTHY,${VAR}} => ${TRUTHY,} => [emtpy] -# -# Inspired by https://github.com/wahern/autoguess -TRUTHY,y := y -TRUTHY,1 := y - -# Boolean operators are also implemented with nested expansion -NOT, := y - -# Normalize ${V} to either "y" or "" -IS_V := ${TRUTHY,${V}} - -# Suppress output unless V=1 -Q, := @ -Q := ${Q,${IS_V}} - -# The current target, with ${BUILDDIR} stripped for shorter messages -TGT = ${@:${BUILDDIR}/%=%} - -# Show full commands with `make V=1`, otherwise short summaries -MSG = @msg() { \ - MSG="$$1"; \ - shift; \ - test "${IS_V}" || printf '%s\n' "$$MSG"; \ - test "$${1:-}" || return 0; \ - test "${IS_V}" && printf '%s\n' "$$*"; \ - "$$@"; \ - }; \ - msg - -# Maximum width of a short message, to align the ✔/✘ -MSG_WIDTH := 30 - -# cat a file if V=1 -VCAT,y := @cat -VCAT, := @: -VCAT := ${VCAT,${IS_V}} - -# All external dependencies -ALL_PKGS := \ - libacl \ - libcap \ - libselinux \ - liburing \ - oniguruma - -# List all object files here, as they're needed by both `make config` and `make` - -# All object files except the entry point -LIBBFS := \ - ${OBJ}/src/alloc.o \ - ${OBJ}/src/bar.o \ - ${OBJ}/src/bfstd.o \ - ${OBJ}/src/bftw.o \ - ${OBJ}/src/color.o \ - ${OBJ}/src/ctx.o \ - ${OBJ}/src/diag.o \ - ${OBJ}/src/dir.o \ - ${OBJ}/src/dstring.o \ - ${OBJ}/src/eval.o \ - ${OBJ}/src/exec.o \ - ${OBJ}/src/expr.o \ - ${OBJ}/src/fsade.o \ - ${OBJ}/src/ioq.o \ - ${OBJ}/src/mtab.o \ - ${OBJ}/src/opt.o \ - ${OBJ}/src/parse.o \ - ${OBJ}/src/printf.o \ - ${OBJ}/src/pwcache.o \ - ${OBJ}/src/stat.o \ - ${OBJ}/src/thread.o \ - ${OBJ}/src/trie.o \ - ${OBJ}/src/typo.o \ - ${OBJ}/src/xregex.o \ - ${OBJ}/src/xspawn.o \ - ${OBJ}/src/xtime.o \ - ${OBJ}/gen/version.o - -# Unit test objects -UNIT_OBJS := \ - ${OBJ}/tests/alloc.o \ - ${OBJ}/tests/bfstd.o \ - ${OBJ}/tests/bit.o \ - ${OBJ}/tests/ioq.o \ - ${OBJ}/tests/main.o \ - ${OBJ}/tests/trie.o \ - ${OBJ}/tests/xspawn.o \ - ${OBJ}/tests/xtime.o - -# All object files -OBJS := \ - ${OBJ}/src/main.o \ - ${OBJ}/tests/mksock.o \ - ${OBJ}/tests/xspawnee.o \ - ${OBJ}/tests/xtouch.o \ - ${LIBBFS} \ - ${UNIT_OBJS} diff --git a/config/use/libacl.c b/config/use/libacl.c deleted file mode 100644 index de1fe50..0000000 --- a/config/use/libacl.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - acl_free(0); - return 0; -} diff --git a/config/use/libcap.c b/config/use/libcap.c deleted file mode 100644 index 58e832c..0000000 --- a/config/use/libcap.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - cap_free(0); - return 0; -} diff --git a/config/use/libselinux.c b/config/use/libselinux.c deleted file mode 100644 index bca409d..0000000 --- a/config/use/libselinux.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - freecon(0); - return 0; -} diff --git a/config/use/liburing.c b/config/use/liburing.c deleted file mode 100644 index bea499a..0000000 --- a/config/use/liburing.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - io_uring_free_probe(0); - return 0; -} diff --git a/config/use/oniguruma.c b/config/use/oniguruma.c deleted file mode 100644 index cb17596..0000000 --- a/config/use/oniguruma.c +++ /dev/null @@ -1,9 +0,0 @@ -// Copyright © Tavian Barnes -// SPDX-License-Identifier: 0BSD - -#include - -int main(void) { - onig_free(0); - return 0; -} diff --git a/configure b/configure new file mode 100755 index 0000000..6920212 --- /dev/null +++ b/configure @@ -0,0 +1,98 @@ +#!/bin/sh + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# bfs build configuration script + +set -eu + +help() { + cat <] [CC=...] [CFLAGS=...] [...] + +Compiler configuration: + + CC + The C compiler to use + CPPFLAGS + C preprocessor flags + CFLAGS + C compiler flags + LDFLAGS + Linker flags + LDLIBS + Dynamic libraries to link + + EXTRA_CPPFLAGS + EXTRA_CFLAGS + EXTRA_LDFLAGS + EXTRA_LDLIBS + Adds to the default flags, instead of replacing them + +Build profiles: + + RELEASE=y + Enable optimizations, disable assertions + ASAN=y + LSAN=y + MSAN=y + TSAN=y + UBSAN=y + Enable sanitizers + GCOV=y + Enable code coverage instrumentation + +External dependencies: + + PKG_CONFIG + The pkg-config binary to use + + USE_LIBACL=[y|n] + USE_LIBCAP=[y|n] + USE_LIBSELINUX=[y|n] + USE_LIBURIG=[y|n] + USE_ONIGURUMA=[y|n] + Enable or disable external dependencies + +Packaging configuration: + + PREFIX + Set the installation prefix (default: /usr) + MANDIR + Set the man page directory (default: \$PREFIX/share/man) +EOF +} + +for arg; do + case "$arg" in + --help) + help + exit 0 + ;; + -*|*=*) + continue # make flag (-j2) or variable (CC=clang) + ;; + *) + printf 'error: Unrecognized option "%s"\n\n' "$arg" >&2 + help >&2 + exit 1 + ;; + esac +done + +# Get the relative path to the source tree based on how the script was run +DIR=$(dirname -- "$0") + +# Set up symbolic links for out-of-tree builds +for f in Makefile build completions docs src tests; do + test -e "$f" || ln -s "$DIR/$f" "$f" +done + +# Infer -jN unless MAKEFLAGS is set +j= +if ! [ "${MAKEFLAGS+y}" ]; then + j="-j$({ nproc || sysctl -n hw.ncpu || getconf _NPROCESSORS_ONLN || echo 1; } 2>/dev/null)" +fi + +${MAKE:-make} $j -rsf build/config.mk "$@" diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 4ed139c..db5d721 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -4,10 +4,9 @@ Building `bfs` Compiling --------- -`bfs` uses [GNU Make](https://www.gnu.org/software/make/) as its build system. A simple invocation of - $ make config + $ ./configure $ make should build `bfs` successfully. @@ -18,7 +17,6 @@ For example, to use all your cores, run `make -j$(nproc)`. | Command | Description | |------------------|---------------------------------------------------------------| -| `make config` | Configures the build system | | `make` | Builds just the `bfs` binary | | `make all` | Builds everything, including the tests (but doesn't run them) | | `make check` | Builds everything, and runs the tests | @@ -33,13 +31,13 @@ The configuration system provides a few shorthand flags for handy configurations | Command | Description | |-------------------------|-------------------------------------------------------------| -| `make config RELEASE=y` | Build `bfs` with optimizations, LTO, and without assertions | -| `make config ASAN=y` | Enable [AddressSanitizer] | -| `make config LSAN=y` | Enable [LeakSanitizer] | -| `make config MSAN=y` | Enable [MemorySanitizer] | -| `make config TSAN=y` | Enable [ThreadSanitizer] | -| `make config UBSAN=y` | Enable [UndefinedBehaviorSanitizer] | -| `make config GCOV=y` | Enable [code coverage] | +| `./configure RELEASE=y` | Build `bfs` with optimizations, LTO, and without assertions | +| `./configure ASAN=y` | Enable [AddressSanitizer] | +| `./configure LSAN=y` | Enable [LeakSanitizer] | +| `./configure MSAN=y` | Enable [MemorySanitizer] | +| `./configure TSAN=y` | Enable [ThreadSanitizer] | +| `./configure UBSAN=y` | Enable [UndefinedBehaviorSanitizer] | +| `./configure GCOV=y` | Enable [code coverage] | [AddressSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizer [LeakSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#stand-alone-mode @@ -48,21 +46,20 @@ The configuration system provides a few shorthand flags for handy configurations [UndefinedBehaviorSanitizer]: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html [code coverage]: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html -You can combine multiple profiles (e.g. `make config ASAN=y UBSAN=y`), but not all of them will work together. +You can combine multiple profiles (e.g. `./configure ASAN=y UBSAN=y`), but not all of them will work together. ### Flags -Other flags can be specified on the `make config` command line or in the environment. +Other flags can be specified on the `./configure` command line or in the environment. Here are some of the common ones; check the [`Makefile`](/Makefile) for more. | Flag | Description | |-------------------------------------|----------------------------------------------------| -| `CC` | The C compiler to use, e.g. `make config CC=clang` | +| `CC` | The C compiler to use, e.g. `./configure CC=clang` | | `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | | `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | | `USE_LIBACL`
`USE_LIBCAP`
... | Enable/disable [optional dependencies] | | `TEST_FLAGS` | `tests.sh` flags for `make check` | -| `BUILDDIR` | The build output directory (default: `.`) | | `DESTDIR` | The root directory for `make install` | | `PREFIX` | The installation prefix (default: `/usr`) | | `MANDIR` | The man page installation directory | @@ -72,9 +69,9 @@ Here are some of the common ones; check the [`Makefile`](/Makefile) for more. ### Dependencies `bfs` depends on some system libraries for some of its features. -These dependencies are optional, and can be turned off in `make config` if necessary by setting the appropriate variable to `n` (e.g. `make config USE_ONIGURUMA=n`). +These dependencies are optional, and can be turned off in `./configure` if necessary by setting the appropriate variable to `n` (e.g. `./configure USE_ONIGURUMA=n`). -| Dependency | Platforms | `make config` flag | +| Dependency | Platforms | `./configure` flag | |--------------|------------|--------------------| | [libacl] | Linux only | `USE_LIBACL` | | [libcap] | Linux only | `USE_LIBCAP` | @@ -90,22 +87,28 @@ These dependencies are optional, and can be turned off in `make config` if neces ### Dependency tracking -The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in the [`Makefile`](/Makefile)). +The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in [`build/deps.mk`](/build/deps.mk)). So if you edit a header file, `make` will rebuild the necessary object files ensuring they don't go out of sync. We also add a dependency on the current configuration, so you can change configurations and rebuild without having to `make clean`. - -We go one step further than most build systems by tracking the flags that were used for the previous compilation. -That means you can change configurations without having to `make clean`. For example, - $ make config + $ ./configure $ make - $ make config RELEASE=y + $ ./configure RELEASE=y $ make will build the project in debug mode and then rebuild it in release mode. +### Out-of-tree builds + +You can set up an out-of-tree build by running the `configure` script from another directory, for example: + + $ mkdir out + $ cd out + $ ../configure + $ make + Testing ------- diff --git a/tests/util.sh b/tests/util.sh index 7dba9fb..3969db5 100644 --- a/tests/util.sh +++ b/tests/util.sh @@ -12,12 +12,9 @@ _realpath() ( ) # Globals -TESTS=$(_realpath "$TESTS") -if [ "${BUILDDIR-}" ]; then - BIN=$(_realpath "$BUILDDIR/bin") -else - BIN=$(_realpath "$TESTS/../bin") -fi +ROOT=$(_realpath "$(dirname -- "$TESTS")") +TESTS="$ROOT/tests" +BIN="$ROOT/bin" MKSOCK="$BIN/tests/mksock" XTOUCH="$BIN/tests/xtouch" UNAME=$(uname) diff --git a/tests/xspawn.c b/tests/xspawn.c index 7362aa5..785ea48 100644 --- a/tests/xspawn.c +++ b/tests/xspawn.c @@ -64,27 +64,20 @@ static bool check_use_path(bool use_posix) { spawn.flags &= ~BFS_SPAWN_USE_POSIX; } - const char *builddir = getenv("BUILDDIR"); - dchar *bin = dstrprintf("%s/bin", builddir ? builddir : "."); - ret &= bfs_pcheck(bin, "dstrprintf()"); - if (!ret) { - goto destroy; - } - - ret &= bfs_pcheck(bfs_spawn_addopen(&spawn, 10, bin, O_RDONLY | O_DIRECTORY, 0) == 0); + ret &= bfs_pcheck(bfs_spawn_addopen(&spawn, 10, "bin", O_RDONLY | O_DIRECTORY, 0) == 0); ret &= bfs_pcheck(bfs_spawn_adddup2(&spawn, 10, 11) == 0); ret &= bfs_pcheck(bfs_spawn_addclose(&spawn, 10) == 0); ret &= bfs_pcheck(bfs_spawn_addfchdir(&spawn, 11) == 0); ret &= bfs_pcheck(bfs_spawn_addclose(&spawn, 11) == 0); if (!ret) { - goto bin; + goto destroy; } // Check that $PATH is resolved in the parent's environment char **envp; ret &= bfs_pcheck(envp = envdup()); if (!ret) { - goto bin; + goto destroy; } // Check that $PATH is resolved after the file actions @@ -138,8 +131,6 @@ env: free(*var); } free(envp); -bin: - dstrfree(bin); destroy: ret &= bfs_pcheck(bfs_spawn_destroy(&spawn) == 0); out: -- cgit v1.2.3 From 1f06941a7cc586c78152ca67dec0551106977b08 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 30 Apr 2024 15:07:06 -0400 Subject: build: Listen to make -s --- Makefile | 8 ++------ build/config.mk | 8 ++++---- build/deps.mk | 8 ++++---- build/flags.mk | 8 ++++---- build/header.mk | 21 +++++++++----------- build/msg-if.sh | 21 ++++++++++++++++++++ build/msg.sh | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ build/pkgs.mk | 13 +++++------- build/prelude.mk | 21 +++++--------------- configure | 2 +- 10 files changed, 115 insertions(+), 55 deletions(-) create mode 100755 build/msg-if.sh create mode 100755 build/msg.sh diff --git a/Makefile b/Makefile index 2fb35fd..cd8bb55 100644 --- a/Makefile +++ b/Makefile @@ -49,16 +49,12 @@ BINS := \ all: ${BINS} .PHONY: all -# Group relevant flags together -ALL_CFLAGS = ${CPPFLAGS} ${CFLAGS} ${DEPFLAGS} -ALL_LDFLAGS = ${CFLAGS} ${LDFLAGS} - # The main binary bin/bfs: ${LIBBFS} obj/src/main.o ${BINS}: @${MKDIR} ${@D} - +${MSG} "[ LD ] $@" ${CC} ${ALL_LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ + +${MSG} "[ LD ] $@" ${CC} ${CFLAGS} ${LDFLAGS} ${.ALLSRC} ${LDLIBS} -o $@ ${POSTLINK} # Get the .c file for a .o file @@ -67,7 +63,7 @@ CSRC = ${@:obj/%.o=%.c} # Rebuild when the configuration changes ${OBJS}: gen/config.mk @${MKDIR} ${@D} - ${MSG} "[ CC ] ${CSRC}" ${CC} ${ALL_CFLAGS} -c ${CSRC} -o $@ + ${MSG} "[ CC ] ${CSRC}" ${CC} ${CPPFLAGS} ${CFLAGS} -c ${CSRC} -o $@ # Save the version number to this file, but only update version.c if it changes gen/version.c.new:: diff --git a/build/config.mk b/build/config.mk index 153838d..24873ec 100644 --- a/build/config.mk +++ b/build/config.mk @@ -43,20 +43,20 @@ gen/vars.mk:: # Sets the build flags. This depends on vars.mk and uses a recursive make so # that the default flags can depend on variables like ${OS}. gen/flags.mk: gen/vars.mk - @+${MAKE} -sf build/flags.mk $@ + @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/flags.mk $@ .PHONY: gen/flags.mk # Check for dependency generation support gen/deps.mk: gen/flags.mk - @+${MAKE} -sf build/deps.mk $@ + @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/deps.mk $@ .PHONY: gen/deps.mk # Auto-detect dependencies and their build flags gen/pkgs.mk: gen/flags.mk - @+${MAKE} -sf build/pkgs.mk $@ + @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/pkgs.mk $@ .PHONY: gen/pkgs.mk # Compile-time feature detection gen/config.h: gen/config.mk - @+${MAKE} -sf build/header.mk $@ + @+XMAKEFLAGS="$$MAKEFLAGS" ${MAKE} -sf build/header.mk $@ .PHONY: gen/config.h diff --git a/build/deps.mk b/build/deps.mk index e8dd9fb..3db62b6 100644 --- a/build/deps.mk +++ b/build/deps.mk @@ -10,9 +10,9 @@ include build/exports.mk gen/deps.mk:: ${MSG} "[ GEN] $@" - printf '# %s\n' "$@" >$@ - if build/cc.sh -MD -MP -MF /dev/null build/empty.c; then \ - printf 'DEPFLAGS := -MD -MP\n'; \ + @printf '# %s\n' "$@" >$@ + @if build/cc.sh -MD -MP -MF /dev/null build/empty.c; then \ + printf 'CPPFLAGS += -MD -MP\n'; \ fi >>$@ 2>$@.log ${VCAT} $@ - printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ + @printf -- '-include %s\n' ${OBJS:.o=.d} >>$@ diff --git a/build/flags.mk b/build/flags.mk index 0a44e94..c911b22 100644 --- a/build/flags.mk +++ b/build/flags.mk @@ -101,14 +101,14 @@ export RELEASE_CPPFLAGS=${RELEASE_CPPFLAGS,${_RELEASE}} export RELEASE_CFLAGS=${RELEASE_CFLAGS,${_RELEASE}} # Set a variable -SETVAR = printf '%s := %s\n' >>$@ +SETVAR = @printf '%s := %s\n' >>$@ # Append to a variable, if non-empty -APPEND = append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append +APPEND = @append() { test -z "$$2" || printf '%s += %s\n' "$$1" "$$2" >>$@; }; append gen/flags.mk:: ${MSG} "[ GEN] $@" - printf '# %s\n' "$@" >$@ + @printf '# %s\n' "$@" >$@ ${SETVAR} CPPFLAGS "$$BFS_CPPFLAGS" ${APPEND} CPPFLAGS "$$TSAN_CPPFLAGS" ${APPEND} CPPFLAGS "$$LINT_CPPFLAGS" @@ -132,5 +132,5 @@ gen/flags.mk:: ${APPEND} LDLIBS "$$EXTRA_LDLIBS" ${APPEND} LDLIBS "$$BFS_LDLIBS" ${SETVAR} NOLIBS "$$XNOLIBS" - test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ + @test "${OS}-${SAN}" != FreeBSD-y || printf 'POSTLINK = elfctl -e +noaslr $$@\n' >>$@ ${VCAT} $@ diff --git a/build/header.mk b/build/header.mk index 3f77ca5..951f09b 100644 --- a/build/header.mk +++ b/build/header.mk @@ -48,12 +48,12 @@ PKG_HEADERS := ${ALL_PKGS:%=gen/use/%.h} gen/config.h: ${PKG_HEADERS} ${HEADERS} ${MSG} "[ GEN] $@" - printf '// %s\n' "$@" >$@ - printf '#ifndef BFS_CONFIG_H\n' >>$@ - printf '#define BFS_CONFIG_H\n' >>$@ - cat ${.ALLSRC} >>$@ - printf '#endif // BFS_CONFIG_H\n' >>$@ - cat ${.ALLSRC:%=%.log} >$@.log + @printf '// %s\n' "$@" >$@ + @printf '#ifndef BFS_CONFIG_H\n' >>$@ + @printf '#define BFS_CONFIG_H\n' >>$@ + @cat ${.ALLSRC} >>$@ + @printf '#endif // BFS_CONFIG_H\n' >>$@ + @cat ${.ALLSRC:%=%.log} >$@.log ${VCAT} $@ .PHONY: gen/config.h @@ -61,9 +61,6 @@ gen/config.h: ${PKG_HEADERS} ${HEADERS} SLUG = ${@:gen/%.h=%} ${HEADERS}:: - ${MKDIR} ${@D} - if build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c >$@ 2>$@.log; then \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' ${SLUG}.c; \ - else \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' ${SLUG}.c; \ - fi + @${MKDIR} ${@D} + @build/define-if.sh ${SLUG} build/cc.sh build/${SLUG}.c >$@ 2>$@.log; \ + build/msg-if.sh "[ CC ] ${SLUG}.c" test $$? -eq 0 diff --git a/build/msg-if.sh b/build/msg-if.sh new file mode 100755 index 0000000..8112aea --- /dev/null +++ b/build/msg-if.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Print a success/failure indicator from a makefile: +# +# $ ./configure +# [ CC ] use/liburing.c ✘ +# [ CC ] use/oniguruma.c ✔ + +set -eu + +MSG="$1" +shift + +if "$@"; then + build/msg.sh "$(printf '%-37s ✔' "$MSG")" +else + build/msg.sh "$(printf '%-37s ✘' "$MSG")" +fi diff --git a/build/msg.sh b/build/msg.sh new file mode 100755 index 0000000..8b4714e --- /dev/null +++ b/build/msg.sh @@ -0,0 +1,60 @@ +#!/bin/sh + +# Copyright © Tavian Barnes +# SPDX-License-Identifier: 0BSD + +# Print a message from a makefile: +# +# $ make -s +# $ make +# [ CC ] src/main.c +# $ make V=1 +# cc -Isrc -Igen -D... + +set -eu + +# Get the $MAKEFLAGS from the top-level make invocation +MFLAGS="${XMAKEFLAGS-${MAKEFLAGS-}}" + +# Check if make should be quiet (make -s) +is_quiet() { + # GNU make puts single-letter flags in the first word of $MAKEFLAGS, + # without a leading dash + case "${MFLAGS%% *}" in + -*) : ;; + *s*) return 0 ;; + esac + + # BSD make puts each flag separately like -r -s -j 48 + for flag in $MFLAGS; do + case "$flag" in + # Ignore things like --jobserver-auth + --*) continue ;; + -*s*) return 0 ;; + esac + done + + return 1 +} + +# Check if make should be loud (make V=1) +is_loud() { + test "$XV" +} + +MSG="$1" +shift + +if ! is_quiet && ! is_loud; then + printf '%s\n' "$MSG" +fi + +if [ $# -eq 0 ]; then + exit +fi + +if is_loud; then + printf '%s\n' "$*" +fi + +"$@" diff --git a/build/pkgs.mk b/build/pkgs.mk index 51ca664..39b550d 100644 --- a/build/pkgs.mk +++ b/build/pkgs.mk @@ -12,8 +12,8 @@ HEADERS := ${ALL_PKGS:%=gen/use/%.h} gen/pkgs.mk: ${HEADERS} ${MSG} "[ GEN] $@" - printf '# %s\n' "$@" >$@ - gen() { \ + @printf '# %s\n' "$@" >$@ + @gen() { \ printf 'PKGS := %s\n' "$$*"; \ printf 'CFLAGS += %s\n' "$$(build/pkgconf.sh --cflags "$$@")"; \ printf 'LDFLAGS += %s\n' "$$(build/pkgconf.sh --ldflags "$$@")"; \ @@ -28,9 +28,6 @@ gen/pkgs.mk: ${HEADERS} PKG = ${@:gen/use/%.h=%} ${HEADERS}:: - ${MKDIR} ${@D} - if build/define-if.sh use/${PKG} build/pkgconf.sh ${PKG} >$@ 2>$@.log; then \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✔\n' use/${PKG}.c; \ - else \ - test "${IS_V}" || printf '[ CC ] %-${MSG_WIDTH}s ✘\n' use/${PKG}.c; \ - fi + @${MKDIR} ${@D} + @build/define-if.sh use/${PKG} build/pkgconf.sh ${PKG} >$@ 2>$@.log; \ + build/msg-if.sh "[ CC ] use/${PKG}.c" test $$? -eq 0; diff --git a/build/prelude.mk b/build/prelude.mk index b235fa9..5be26cb 100644 --- a/build/prelude.mk +++ b/build/prelude.mk @@ -44,33 +44,22 @@ TRUTHY,y := y TRUTHY,1 := y # Boolean operators are also implemented with nested expansion -NOT, := y +NOT, := y # Normalize ${V} to either "y" or "" -IS_V := ${TRUTHY,${V}} +export XV=${TRUTHY,${V}} # Suppress output unless V=1 Q, := @ -Q := ${Q,${IS_V}} +Q := ${Q,${XV}} # Show full commands with `make V=1`, otherwise short summaries -MSG = @msg() { \ - MSG="$$1"; \ - shift; \ - test "${IS_V}" || printf '%s\n' "$$MSG"; \ - test "$${1:-}" || return 0; \ - test "${IS_V}" && printf '%s\n' "$$*"; \ - "$$@"; \ - }; \ - msg - -# Maximum width of a short message, to align the ✔/✘ -MSG_WIDTH := 30 +MSG = @build/msg.sh # cat a file if V=1 VCAT,y := @cat VCAT, := @: -VCAT := ${VCAT,${IS_V}} +VCAT := ${VCAT,${XV}} # All external dependencies ALL_PKGS := \ diff --git a/configure b/configure index 6920212..830628a 100755 --- a/configure +++ b/configure @@ -95,4 +95,4 @@ if ! [ "${MAKEFLAGS+y}" ]; then j="-j$({ nproc || sysctl -n hw.ncpu || getconf _NPROCESSORS_ONLN || echo 1; } 2>/dev/null)" fi -${MAKE:-make} $j -rsf build/config.mk "$@" +${MAKE:-make} $j -rf build/config.mk "$@" -- cgit v1.2.3 From 99260d347b91f9f7ede335b7f582cee34ead2b0c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 1 May 2024 14:23:20 -0400 Subject: build: Add some nice aliases to ./configure --- .github/workflows/ci.yml | 4 +- .github/workflows/codecov.yml | 2 +- Makefile | 10 +-- build/header.mk | 2 +- configure | 141 ++++++++++++++++++----------- docs/BUILDING.md | 200 ++++++++++++++++++++++++++---------------- 6 files changed, 221 insertions(+), 138 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 57f59f4..e47600b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -107,7 +107,7 @@ jobs: gmake \ oniguruma jobs=$(sysctl -n hw.ncpu) - MAKE=gmake ./configure + ./configure MAKE=gmake gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" netbsd: @@ -198,5 +198,5 @@ jobs: PATH="/usr/xpg4/bin:$PATH" chown -R action:staff . jobs=$(getconf NPROCESSORS_ONLN) - sudo -u action MAKE=gmake ./configure + sudo -u action ./configure MAKE=gmake sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index bb68927..6aaace6 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -25,7 +25,7 @@ jobs: - name: Generate coverage run: | - ./configure GCOV=y + ./configure --enable-gcov make -j$(nproc) check TEST_FLAGS="--sudo" gcov -abcfpu obj/*/*.o diff --git a/Makefile b/Makefile index cd8bb55..8a8ec6b 100644 --- a/Makefile +++ b/Makefile @@ -164,11 +164,11 @@ distcheck: .PHONY: distcheck # Per-distcheck configuration -DISTCHECK_CONFIG_asan := ASAN=y UBSAN=y -DISTCHECK_CONFIG_msan := MSAN=y UBSAN=y CC=clang -DISTCHECK_CONFIG_tsan := TSAN=y UBSAN=y CC=clang +DISTCHECK_CONFIG_asan := --enable-asan --enable-ubsan +DISTCHECK_CONFIG_msan := --enable-msan --enable-ubsan CC=clang +DISTCHECK_CONFIG_tsan := --enable-tsan --enable-ubsan CC=clang DISTCHECK_CONFIG_m32 := EXTRA_CFLAGS="-m32" PKG_CONFIG_LIBDIR=/usr/lib32/pkgconfig -DISTCHECK_CONFIG_release := RELEASE=y +DISTCHECK_CONFIG_release := --enable-release ${DISTCHECKS}:: @${MKDIR} $@ @@ -195,7 +195,7 @@ install:: ${MSG} "[INST] completions/bfs.zsh" \ ${INSTALL} -m644 completions/bfs.zsh ${DEST_PREFIX}/share/zsh/site-functions/_bfs ${Q}${MKDIR} ${DEST_PREFIX}/share/fish/vendor_completions.d - ${MSG} "[INST] completions/bfs.fish" \ + ${MSG} "[INST] completions/bfs.fish" \ ${INSTALL} -m644 completions/bfs.fish ${DEST_PREFIX}/share/fish/vendor_completions.d/bfs.fish uninstall:: diff --git a/build/header.mk b/build/header.mk index 951f09b..a9157ad 100644 --- a/build/header.mk +++ b/build/header.mk @@ -53,7 +53,7 @@ gen/config.h: ${PKG_HEADERS} ${HEADERS} @printf '#define BFS_CONFIG_H\n' >>$@ @cat ${.ALLSRC} >>$@ @printf '#endif // BFS_CONFIG_H\n' >>$@ - @cat ${.ALLSRC:%=%.log} >$@.log + @cat ${.ALLSRC:%=%.log} >gen/config.log ${VCAT} $@ .PHONY: gen/config.h diff --git a/configure b/configure index 830628a..d42dec3 100755 --- a/configure +++ b/configure @@ -7,75 +7,117 @@ set -eu -help() { - cat <] [CC=...] [CFLAGS=...] [...] +# Default to `make` +MAKE="${MAKE:-make}" -Compiler configuration: +# Pass -j$(nproc) unless MAKEFLAGS is set +if [ "${MAKEFLAGS+y}" ]; then + j="" +else + j="-j$({ nproc || sysctl -n hw.ncpu || getconf _NPROCESSORS_ONLN || echo 1; } 2>/dev/null)" +fi + +for arg; do + case "$arg" in + -h|--help) + cat <&2 + printf 'Run %s --help for more information.\n' "$0" >&2 + exit 1 + ;; + esac + ;; + + --prefix=*) + shift + set -- "$@" "PREFIX=${arg#*=}" + ;; + + MAKE=*) + MAKE="${arg#*=}" + shift + ;; + + # make flag (-j2) or variable (CC=clang) -*|*=*) - continue # make flag (-j2) or variable (CC=clang) + continue ;; + *) printf 'error: Unrecognized option "%s"\n\n' "$arg" >&2 - help >&2 + printf 'Run %s --help for more information.\n' "$0" >&2 exit 1 ;; esac @@ -89,10 +131,7 @@ for f in Makefile build completions docs src tests; do test -e "$f" || ln -s "$DIR/$f" "$f" done -# Infer -jN unless MAKEFLAGS is set -j= -if ! [ "${MAKEFLAGS+y}" ]; then - j="-j$({ nproc || sysctl -n hw.ncpu || getconf _NPROCESSORS_ONLN || echo 1; } 2>/dev/null)" -fi +# Set MAKEFLAGS to -j$(nproc) if it's unset +export MAKEFLAGS="${MAKEFLAGS-$j}" -${MAKE:-make} $j -rf build/config.mk "$@" +$MAKE -rf build/config.mk "$@" diff --git a/docs/BUILDING.md b/docs/BUILDING.md index db5d721..cb33c51 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -1,104 +1,101 @@ Building `bfs` ============== -Compiling ---------- - A simple invocation of $ ./configure $ make should build `bfs` successfully. -As usual with `make`, you can run a [parallel build](https://www.gnu.org/software/make/manual/html_node/Parallel.html) with `-j`. -For example, to use all your cores, run `make -j$(nproc)`. -### Targets -| Command | Description | -|------------------|---------------------------------------------------------------| -| `make` | Builds just the `bfs` binary | -| `make all` | Builds everything, including the tests (but doesn't run them) | -| `make check` | Builds everything, and runs the tests | -| `make install` | Installs `bfs` (with man page, shell completions, etc.) | -| `make uninstall` | Uninstalls `bfs` | -| `make clean` | Delete the build products | -| `make distclean` | Delete all generated files, including the build configuration | +Configuration +------------- + +```console +$ ./configure --help +Usage: + + $ ./configure [--enable-*|--disable-*] [CC=...] [CFLAGS=...] [...] + $ make + +... +``` + +### Variables + +Variables set in the environment or on the command line will be picked up: +These variables specify binaries to run during the configuration and build process: + +
+MAKE=make
+    make implementation
+CC=cc
+    C compiler
+INSTALL=install
+    Copy files during make install
+MKDIR="mkdir -p"
+    Create directories
+PKG_CONFIG=pkg-config
+    Detect external libraries and required build flags
+RM="rm -f"
+    Delete files
+
+ +These flags will be used by the build process: + +
+CPPFLAGS="-I... -D..."
+CFLAGS="-W... -f..."
+LDFLAGS="-L... -Wl,..."
+    Preprocessor/compiler/linker flags
+
+LDLIBS="-l... -l..."
+    Dynamic libraries to link
+
+EXTRA_{CPPFLAGS,CFLAGS,LDFLAGS,LDLIBS}="..."
+    Adds to the default flags, instead of replacing them
+
### Build profiles -The configuration system provides a few shorthand flags for handy configurations: - -| Command | Description | -|-------------------------|-------------------------------------------------------------| -| `./configure RELEASE=y` | Build `bfs` with optimizations, LTO, and without assertions | -| `./configure ASAN=y` | Enable [AddressSanitizer] | -| `./configure LSAN=y` | Enable [LeakSanitizer] | -| `./configure MSAN=y` | Enable [MemorySanitizer] | -| `./configure TSAN=y` | Enable [ThreadSanitizer] | -| `./configure UBSAN=y` | Enable [UndefinedBehaviorSanitizer] | -| `./configure GCOV=y` | Enable [code coverage] | - -[AddressSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizer -[LeakSanitizer]: https://github.com/google/sanitizers/wiki/AddressSanitizerLeakSanitizer#stand-alone-mode -[MemorySanitizer]: https://github.com/google/sanitizers/wiki/MemorySanitizer -[ThreadSanitizer]: https://github.com/google/sanitizers/wiki/ThreadSanitizerCppManual -[UndefinedBehaviorSanitizer]: https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html -[code coverage]: https://gcc.gnu.org/onlinedocs/gcc/Gcov.html - -You can combine multiple profiles (e.g. `./configure ASAN=y UBSAN=y`), but not all of them will work together. - -### Flags - -Other flags can be specified on the `./configure` command line or in the environment. -Here are some of the common ones; check the [`Makefile`](/Makefile) for more. - -| Flag | Description | -|-------------------------------------|----------------------------------------------------| -| `CC` | The C compiler to use, e.g. `./configure CC=clang` | -| `CFLAGS`
`EXTRA_CFLAGS` | Override/add to the default compiler flags | -| `LDFLAGS`
`EXTRA_LDFLAGS` | Override/add to the linker flags | -| `USE_LIBACL`
`USE_LIBCAP`
... | Enable/disable [optional dependencies] | -| `TEST_FLAGS` | `tests.sh` flags for `make check` | -| `DESTDIR` | The root directory for `make install` | -| `PREFIX` | The installation prefix (default: `/usr`) | -| `MANDIR` | The man page installation directory | - -[optional dependencies]: #dependencies +The default flags result in a plain debug build. +Other build profiles can be enabled: -### Dependencies +
+--enable-release
+    Enable optimizations, disable assertions
 
-`bfs` depends on some system libraries for some of its features.
-These dependencies are optional, and can be turned off in `./configure` if necessary by setting the appropriate variable to `n` (e.g. `./configure USE_ONIGURUMA=n`).
+--enable-asan
+--enable-lsan
+--enable-msan
+--enable-tsan
+--enable-ubsan
+    Enable sanitizers
 
-| Dependency   | Platforms  | `./configure` flag |
-|--------------|------------|--------------------|
-| [libacl]     | Linux only | `USE_LIBACL`       |
-| [libcap]     | Linux only | `USE_LIBCAP`       |
-| [liburing]   | Linux only | `USE_LIBURING`     |
-| [libselinux] | Linux only | `USE_LIBSELINUX`   |
-| [Oniguruma]  | All        | `USE_ONIGURUMA`    |
+--enable-gcov
+    Enable code coverage instrumentation
+
-[libacl]: https://savannah.nongnu.org/projects/acl -[libcap]: https://sites.google.com/site/fullycapable/ -[libselinux]: https://github.com/SELinuxProject/selinux -[liburing]: https://github.com/axboe/liburing -[Oniguruma]: https://github.com/kkos/oniguruma +You can combine multiple profiles (e.g. `./configure --enable-asan --enable-ubsan`), but not all of them will work together. -### Dependency tracking +### Dependencies -The build system automatically tracks header dependencies with the `-M` family of compiler options (see `DEPFLAGS` in [`build/deps.mk`](/build/deps.mk)). -So if you edit a header file, `make` will rebuild the necessary object files ensuring they don't go out of sync. +`bfs` depends on some system libraries for some of its features. +External dependencies are auto-detected by default, but you can `--enable` or `--disable` them manually: -We also add a dependency on the current configuration, so you can change configurations and rebuild without having to `make clean`. -For example, +
+--enable-libacl      --disable-libacl
+--enable-libcap      --disable-libcap
+--enable-libselinux  --disable-libselinux
+--enable-liburing    --disable-liburing
+--enable-oniguruma   --disable-oniguruma
+
- $ ./configure - $ make - $ ./configure RELEASE=y - $ make +[`pkg-config`] is used, if available, to detect these libraries and any additional build flags they may require. +If this is undesireable, disable it by setting `PKG_CONFIG` to the empty string (`./configure PKG_CONFIG=""`). -will build the project in debug mode and then rebuild it in release mode. +[`pkg-config`]: https://www.freedesktop.org/wiki/Software/pkg-config/ ### Out-of-tree builds @@ -110,6 +107,53 @@ You can set up an out-of-tree build by running the `configure` script from anoth $ make +Building +-------- + +### Targets + +The [`Makefile`](/Makefile) supports several different build targets: + +
+make
+    The default target; builds just the bfs binary
+make all
+    Builds everything, including the tests (but doesn't run them)
+
+make check
+    Builds everything, and runs all tests
+make unit-tests
+    Builds and runs the unit tests
+make integration-tests
+    Builds and runs the integration tests
+make distcheck
+    Builds and runs the tests in multiple different configurations
+
+make install
+    Installs bfs globally
+make uninstall
+    Uninstalls bfs
+
+make clean
+    Deletes all built files
+make distclean
+    Also deletes files generated by ./configure
+
+ + +Troubleshooting +--------------- + +If the build fails or behaves unexpectedly, start by enabling verbose mode: + + $ ./configure V=1 + $ make V=1 + +This will print the generated configuration and the exact commands that are executed. + +You can also check the file `gen/config.log`, which contains any errors from commands run during the configuration phase. + + Testing ------- -- cgit v1.2.3 From cf080ec377461ca6fe2e0e9bb8b25ad63bda6bc6 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 2 May 2024 11:25:42 -0400 Subject: Prefer ./configure --enable-release to RELEASE=y --- README.md | 2 +- bench/bench.sh | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 922ebdc..b9aad82 100644 --- a/README.md +++ b/README.md @@ -343,7 +343,7 @@ Run the test suite to make sure it works correctly: If you're interested in speed, you may want to build the release version instead: - $ ./configure RELEASE=y + $ ./configure --enable-release $ make Finally, if you want to install it globally, run diff --git a/bench/bench.sh b/bench/bench.sh index ba46599..cf1ae49 100644 --- a/bench/bench.sh +++ b/bench/bench.sh @@ -221,7 +221,7 @@ setup() { fi echo "Building bfs ..." - as-user ./configure RELEASE=y + as-user ./configure --enable-release as-user make -s -j"$nproc" all as-user mkdir -p bench/corpus @@ -255,7 +255,7 @@ setup() { cd "$worktree" as-user git checkout -qd "$commit" -- if [ -e configure ]; then - as-user ./configure RELEASE=1 + as-user ./configure --enable-release as-user make -s -j"$nproc" else as-user make -s -j"$nproc" release -- cgit v1.2.3 From aab0cdfef0df2e0a478ad062899c044fae0aec76 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 2 May 2024 11:17:29 -0400 Subject: Release 3.2 --- Makefile | 2 +- docs/CHANGELOG.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 8a8ec6b..2d68f20 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,7 @@ gen/version.c.new:: elif test -e src/../.git && command -v git >/dev/null 2>&1; then \ git -C src/.. describe --always --dirty; \ else \ - echo "3.1.3"; \ + echo "3.2"; \ fi | tr -d '\n' >>$@ @printf '";\n' >>$@ diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 672c2b4..62b6480 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,6 +1,36 @@ 3.* === +3.2 +--- + +**May 2, 2024** + +### New features + +- New `-limit N` action that quits immediately after `N` results + +- Implemented `-context` (from GNU find) for matching SELinux contexts ([#27](https://github.com/tavianator/bfs/issues/27)) + +- Implemented `-printf %Z` for printing SELinux contexts + +### Changes + +- The build system has been rewritten, and there is now a configure step: + + $ ./configure + $ make + + See `./configure --help` or [docs/BUILDING.md](/docs/BUILDING.md) for more details. + +- Improved platform support + - Implemented `-acl` on Solaris/Illumos + - Implemented `-xattr` on DragonFly BSD + +### Bug fixes + +- Fixed some rarely-used code paths that clean up after allocation failures + 3.1.3 ----- -- cgit v1.2.3 From 86b084841e0990b9911fdb81c01974a0bbd297f5 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Thu, 2 May 2024 19:03:51 -0400 Subject: Makefile: Recommend --enable-release over RELEASE=y --- Makefile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 2d68f20..d89f3b6 100644 --- a/Makefile +++ b/Makefile @@ -21,8 +21,8 @@ default: bfs # bfs used to have flag-like targets (`make release`, `make asan ubsan`, etc.). # Direct users to the new configuration system. asan lsan msan tsan ubsan gcov lint release:: - @printf 'error: `%s %s` is no longer supported. Use `./configure %s=y` instead.\n' \ - "${MAKE}" $@ $$(echo $@ | tr 'a-z' 'A-Z') >&2 + @printf 'error: `%s %s` is no longer supported. Use `./configure --enable-%s` instead.\n' \ + "${MAKE}" $@ $@ >&2 @false # Print an error if `make` is run before `./configure` -- cgit v1.2.3 From c74947f39063218f3cbd884e4ebaafe8dfc9302c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Fri, 3 May 2024 15:54:35 -0400 Subject: build: Don't look for -s inside VAR=val in $MAKEFLAGS --- build/msg.sh | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build/msg.sh b/build/msg.sh index 8b4714e..a7da31b 100755 --- a/build/msg.sh +++ b/build/msg.sh @@ -30,6 +30,8 @@ is_quiet() { case "$flag" in # Ignore things like --jobserver-auth --*) continue ;; + # Skip variable assignments + *=*) break ;; -*s*) return 0 ;; esac done -- cgit v1.2.3 From f976c98d334dce9ba30aa7da4427bb530aeea536 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 6 May 2024 16:04:05 -0400 Subject: xtime: Use the libc's timegm() if present --- build/has/timegm.c | 9 +++++++++ build/header.mk | 1 + src/xtime.c | 36 +++++++++++++++++++++++++++++++++--- tests/xtime.c | 12 ++++++++---- 4 files changed, 51 insertions(+), 7 deletions(-) create mode 100644 build/has/timegm.c diff --git a/build/has/timegm.c b/build/has/timegm.c new file mode 100644 index 0000000..6e2d155 --- /dev/null +++ b/build/has/timegm.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include + +int main(void) { + struct tm tm = {0}; + return (int)timegm(&tm); +} diff --git a/build/header.mk b/build/header.mk index a9157ad..d235fd0 100644 --- a/build/header.mk +++ b/build/header.mk @@ -40,6 +40,7 @@ HEADERS := \ gen/has/strerror-l.h \ gen/has/strerror-r-gnu.h \ gen/has/strerror-r-posix.h \ + gen/has/timegm.h \ gen/has/tm-gmtoff.h \ gen/has/uselocale.h diff --git a/src/xtime.c b/src/xtime.c index 91ed915..c3537e7 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -12,13 +12,13 @@ #include int xmktime(struct tm *tm, time_t *timep) { - *timep = mktime(tm); + time_t time = mktime(tm); - if (*timep == -1) { + if (time == -1) { int error = errno; struct tm tmp; - if (!localtime_r(timep, &tmp)) { + if (!localtime_r(&time, &tmp)) { bfs_bug("localtime_r(-1): %s", xstrerror(errno)); return -1; } @@ -30,9 +30,37 @@ int xmktime(struct tm *tm, time_t *timep) { } } + *timep = time; + return 0; +} + +#if BFS_HAS_TIMEGM + +int xtimegm(struct tm *tm, time_t *timep) { + time_t time = timegm(tm); + + if (time == -1) { + int error = errno; + + struct tm tmp; + if (!gmtime_r(&time, &tmp)) { + bfs_bug("gmtime_r(-1): %s", xstrerror(errno)); + return -1; + } + + if (tm->tm_year != tmp.tm_year || tm->tm_yday != tmp.tm_yday + || tm->tm_hour != tmp.tm_hour || tm->tm_min != tmp.tm_min || tm->tm_sec != tmp.tm_sec) { + errno = error; + return -1; + } + } + + *timep = time; return 0; } +#else + static int safe_add(int *value, int delta) { if (*value >= 0) { if (delta > INT_MAX - *value) { @@ -147,6 +175,8 @@ overflow: return -1; } +#endif // !BFS_HAS_TIMEGM + /** Parse a decimal digit. */ static int xgetdigit(char c) { int ret = c - '0'; diff --git a/tests/xtime.c b/tests/xtime.c index a7c63d2..d9d6c5c 100644 --- a/tests/xtime.c +++ b/tests/xtime.c @@ -137,6 +137,7 @@ static bool check_one_xtimegm(const struct tm *tm) { return ret; } +#if !BFS_HAS_TIMEGM /** Check an overflowing xtimegm() call. */ static bool check_xtimegm_overflow(const struct tm *tm) { struct tm copy = *tm; @@ -154,6 +155,7 @@ static bool check_xtimegm_overflow(const struct tm *tm) { return ret; } +#endif /** xtimegm() tests. */ static bool check_xtimegm(void) { @@ -173,11 +175,13 @@ static bool check_xtimegm(void) { ret &= check_one_xtimegm(&tm); } +#if !BFS_HAS_TIMEGM // Check integer overflow cases - check_xtimegm_overflow(&(struct tm) { .tm_sec = INT_MAX, .tm_min = INT_MAX }); - check_xtimegm_overflow(&(struct tm) { .tm_min = INT_MAX, .tm_hour = INT_MAX }); - check_xtimegm_overflow(&(struct tm) { .tm_hour = INT_MAX, .tm_mday = INT_MAX }); - check_xtimegm_overflow(&(struct tm) { .tm_mon = INT_MAX, .tm_year = INT_MAX }); + ret &= check_xtimegm_overflow(&(struct tm) { .tm_sec = INT_MAX, .tm_min = INT_MAX }); + ret &= check_xtimegm_overflow(&(struct tm) { .tm_min = INT_MAX, .tm_hour = INT_MAX }); + ret &= check_xtimegm_overflow(&(struct tm) { .tm_hour = INT_MAX, .tm_mday = INT_MAX }); + ret &= check_xtimegm_overflow(&(struct tm) { .tm_mon = INT_MAX, .tm_year = INT_MAX }); +#endif return ret; } -- cgit v1.2.3 From ef1e2caf907ce57a3d0968a952295182d139f1dd Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Mon, 6 May 2024 19:33:29 -0400 Subject: xtime: Don't use timegm() on FreeBSD+msan --- src/xtime.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/xtime.c b/src/xtime.c index c3537e7..eb11afa 100644 --- a/src/xtime.c +++ b/src/xtime.c @@ -5,6 +5,7 @@ #include "xtime.h" #include "bfstd.h" #include "diag.h" +#include "sanity.h" #include #include #include @@ -34,7 +35,8 @@ int xmktime(struct tm *tm, time_t *timep) { return 0; } -#if BFS_HAS_TIMEGM +// FreeBSD is missing an interceptor +#if BFS_HAS_TIMEGM && !(__FreeBSD__ && SANITIZE_MEMORY) int xtimegm(struct tm *tm, time_t *timep) { time_t time = timegm(tm); -- cgit v1.2.3 From 781b9eb78070872734c4e8303b3d02ae9e9c0f37 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 12:33:16 -0400 Subject: prelude: Kill unused __NetBSD_Prereq__() polyfill --- src/prelude.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/prelude.h b/src/prelude.h index ddeacbd..22c72be 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -138,10 +138,6 @@ extern const char bfs_version[]; # define __GLIBC_PREREQ(maj, min) false #endif -#ifndef __NetBSD_Prereq__ -# define __NetBSD_Prereq__(maj, min, patch) false -#endif - // Fundamental utilities /** -- cgit v1.2.3 From 7129ed0ff069fde418773be313442beb0dacfaaf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 12:48:30 -0400 Subject: stat: Prefer fstat(fd) to fstatat(fd, "", AT_EMPTY_PATH) This lets us get rid of the runtime probe for AT_EMPTY_PATH support, and should be more efficient anyway. We still use statx(fd, "", AT_EMPTY_PATH) if available. Link: https://lore.kernel.org/linux-fsdevel/CAHk-=wiYnnv7Kw7v+Cp2xU6_Fd-qxQMZuuxZ61LgA2=Gtftw-A@mail.gmail.com/ --- src/stat.c | 32 +++++++++++++------------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/src/stat.c b/src/stat.c index f5cf3fe..db1f99a 100644 --- a/src/stat.c +++ b/src/stat.c @@ -297,27 +297,21 @@ int bfs_stat(int at_fd, const char *at_path, enum bfs_stat_flags flags, struct b return bfs_stat_tryfollow(at_fd, at_path, at_flags, flags, buf); } - // Check __GNU__ to work around https://lists.gnu.org/archive/html/bug-hurd/2021-12/msg00001.html -#if defined(AT_EMPTY_PATH) && !__GNU__ - static atomic bool has_at_ep = true; - if (load(&has_at_ep, relaxed)) { - at_flags |= AT_EMPTY_PATH; - int ret = bfs_stat_explicit(at_fd, "", at_flags, buf); - if (ret != 0 && errno == EINVAL) { - store(&has_at_ep, false, relaxed); - } else { - return ret; - } - } -#endif - - struct stat statbuf; - if (fstat(at_fd, &statbuf) == 0) { - bfs_stat_convert(buf, &statbuf); - return 0; - } else { +#if BFS_USE_STATX + // If we have statx(), use it with AT_EMPTY_PATH for its extra features + at_flags |= AT_EMPTY_PATH; + return bfs_stat_explicit(at_fd, "", at_flags, buf); +#else + // Otherwise, just use fstat() rather than fstatat(at_fd, ""), to save + // the kernel the trouble of copying in the empty string + struct stat sb; + if (fstat(at_fd, &sb) != 0) { return -1; } + + bfs_stat_convert(buf, &sb); + return 0; +#endif } const struct timespec *bfs_stat_time(const struct bfs_stat *buf, enum bfs_stat_field field) { -- cgit v1.2.3 From 2fef5781950c5c830031883e1b498267dbbe5d22 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 12:53:38 -0400 Subject: stat: Remove __GNU__ guard The number of people running bfs on Hurd with an outdated glibc version is vanishingly small (I hope). --- src/stat.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/stat.c b/src/stat.c index db1f99a..5a10a99 100644 --- a/src/stat.c +++ b/src/stat.c @@ -62,7 +62,7 @@ int bfs_fstatat_flags(enum bfs_stat_flags flags) { ret |= AT_SYMLINK_NOFOLLOW; } -#if defined(AT_NO_AUTOMOUNT) && (!__GNU__ || __GLIBC_PREREQ(2, 35)) +#ifdef AT_NO_AUTOMOUNT ret |= AT_NO_AUTOMOUNT; #endif -- cgit v1.2.3 From e514ebc67f0ece9b24d9cd7e5478d12297d0efcf Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 12:55:32 -0400 Subject: prelude: Remove unused __GLIBC_PREREQ() polyfill --- src/prelude.h | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/prelude.h b/src/prelude.h index 22c72be..fbdc85c 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -134,10 +134,6 @@ extern const char bfs_version[]; # include #endif -#ifndef __GLIBC_PREREQ -# define __GLIBC_PREREQ(maj, min) false -#endif - // Fundamental utilities /** -- cgit v1.2.3 From 39b51c6d6e947778bf2b63fc9586a3236665881e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 13:07:45 -0400 Subject: build: Add checks for strtofflags() and string_to_flags() --- build/has/string-to-flags.c | 9 +++++++++ build/has/strtofflags.c | 9 +++++++++ build/header.mk | 2 ++ src/bfstd.c | 16 +++++++++------- 4 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 build/has/string-to-flags.c create mode 100644 build/has/strtofflags.c diff --git a/build/has/string-to-flags.c b/build/has/string-to-flags.c new file mode 100644 index 0000000..027d72c --- /dev/null +++ b/build/has/string-to-flags.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + return string_to_flags(NULL, NULL, NULL); +} diff --git a/build/has/strtofflags.c b/build/has/strtofflags.c new file mode 100644 index 0000000..73ecbcb --- /dev/null +++ b/build/has/strtofflags.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + return strtofflags(NULL, NULL, NULL); +} diff --git a/build/header.mk b/build/header.mk index d235fd0..2cd13fa 100644 --- a/build/header.mk +++ b/build/header.mk @@ -40,6 +40,8 @@ HEADERS := \ gen/has/strerror-l.h \ gen/has/strerror-r-gnu.h \ gen/has/strerror-r-posix.h \ + gen/has/string-to-flags.h \ + gen/has/strtofflags.h \ gen/has/timegm.h \ gen/has/tm-gmtoff.h \ gen/has/uselocale.h diff --git a/src/bfstd.c b/src/bfstd.c index f8ce871..0ac3a72 100644 --- a/src/bfstd.c +++ b/src/bfstd.c @@ -637,8 +637,14 @@ error: return NULL; } +#if BFS_HAS_STRTOFFLAGS +# define BFS_STRTOFFLAGS strtofflags +#elif BFS_HAS_STRING_TO_FLAGS +# define BFS_STRTOFFLAGS string_to_flags +#endif + int xstrtofflags(const char **str, unsigned long long *set, unsigned long long *clear) { -#if BSD && !__GNU__ +#ifdef BFS_STRTOFFLAGS char *str_arg = (char *)*str; #if __OpenBSD__ @@ -649,11 +655,7 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * bfs_fflags_t set_arg = 0; bfs_fflags_t clear_arg = 0; -#if __NetBSD__ - int ret = string_to_flags(&str_arg, &set_arg, &clear_arg); -#else - int ret = strtofflags(&str_arg, &set_arg, &clear_arg); -#endif + int ret = BFS_STRTOFFLAGS(&str_arg, &set_arg, &clear_arg); *str = str_arg; *set = set_arg; @@ -663,7 +665,7 @@ int xstrtofflags(const char **str, unsigned long long *set, unsigned long long * errno = EINVAL; } return ret; -#else // !BSD +#else // !BFS_STRTOFFLAGS errno = ENOTSUP; return -1; #endif -- cgit v1.2.3 From cf032e8775a70729c397d731a5e2e1ecb58f4877 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 13:23:03 -0400 Subject: mtab: Fix getmntinfo() error check The man pages all say "if an error occurs, zero is returned" --- src/mtab.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mtab.c b/src/mtab.c index 7905d14..9ea8d4a 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -148,7 +148,7 @@ struct bfs_mtab *bfs_mtab_parse(void) { bfs_statfs *mntbuf; int size = getmntinfo(&mntbuf, MNT_WAIT); - if (size < 0) { + if (size <= 0) { error = errno; goto fail; } -- cgit v1.2.3 From c7320b94af8db53b0a4b0a96e37d6e81a0d4a68c Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 13:24:06 -0400 Subject: build: Add a check for getmntinfo() --- build/has/getmntinfo.c | 10 ++++++++++ build/header.mk | 1 + src/mtab.c | 3 +-- 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 build/has/getmntinfo.c diff --git a/build/has/getmntinfo.c b/build/has/getmntinfo.c new file mode 100644 index 0000000..90ef5fb --- /dev/null +++ b/build/has/getmntinfo.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include +#include + +int main(void) { + return getmntinfo(NULL, MNT_WAIT); +} diff --git a/build/header.mk b/build/header.mk index 2cd13fa..52dcb6a 100644 --- a/build/header.mk +++ b/build/header.mk @@ -24,6 +24,7 @@ HEADERS := \ gen/has/getdents.h \ gen/has/getdents64.h \ gen/has/getdents64-syscall.h \ + gen/has/getmntinfo.h \ gen/has/getprogname.h \ gen/has/getprogname-gnu.h \ gen/has/max-align-t.h \ diff --git a/src/mtab.c b/src/mtab.c index 9ea8d4a..9befde3 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -15,7 +15,7 @@ #if !defined(BFS_USE_MNTENT) && BFS_USE_MNTENT_H # define BFS_USE_MNTENT true -#elif !defined(BFS_USE_MNTINFO) && BSD +#elif !defined(BFS_USE_MNTINFO) && BFS_HAS_GETMNTINFO # define BFS_USE_MNTINFO true #elif !defined(BFS_USE_MNTTAB) && __SVR4 # define BFS_USE_MNTTAB true @@ -27,7 +27,6 @@ # include #elif BFS_USE_MNTINFO # include -# include #elif BFS_USE_MNTTAB # include # include -- cgit v1.2.3 From 3908e7ef7ecaffd7b54228d09baeb1db83b0e45d Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 13:34:19 -0400 Subject: build: Check for 1- and 2-argument getmntent() variants --- build/has/getmntent-1.c | 9 +++++++++ build/has/getmntent-2.c | 10 ++++++++++ build/header.mk | 2 ++ src/mtab.c | 4 ++-- 4 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 build/has/getmntent-1.c create mode 100644 build/has/getmntent-2.c diff --git a/build/has/getmntent-1.c b/build/has/getmntent-1.c new file mode 100644 index 0000000..9854dcd --- /dev/null +++ b/build/has/getmntent-1.c @@ -0,0 +1,9 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + return !getmntent(stdin); +} diff --git a/build/has/getmntent-2.c b/build/has/getmntent-2.c new file mode 100644 index 0000000..71f0220 --- /dev/null +++ b/build/has/getmntent-2.c @@ -0,0 +1,10 @@ +// Copyright © Tavian Barnes +// SPDX-License-Identifier: 0BSD + +#include +#include + +int main(void) { + struct mnttab mnt; + return getmntent(stdin, &mnt); +} diff --git a/build/header.mk b/build/header.mk index 52dcb6a..da6de68 100644 --- a/build/header.mk +++ b/build/header.mk @@ -24,6 +24,8 @@ HEADERS := \ gen/has/getdents.h \ gen/has/getdents64.h \ gen/has/getdents64-syscall.h \ + gen/has/getmntent-1.h \ + gen/has/getmntent-2.h \ gen/has/getmntinfo.h \ gen/has/getprogname.h \ gen/has/getprogname-gnu.h \ diff --git a/src/mtab.c b/src/mtab.c index 9befde3..0377fea 100644 --- a/src/mtab.c +++ b/src/mtab.c @@ -13,11 +13,11 @@ #include #include -#if !defined(BFS_USE_MNTENT) && BFS_USE_MNTENT_H +#if !defined(BFS_USE_MNTENT) && BFS_HAS_GETMNTENT_1 # define BFS_USE_MNTENT true #elif !defined(BFS_USE_MNTINFO) && BFS_HAS_GETMNTINFO # define BFS_USE_MNTINFO true -#elif !defined(BFS_USE_MNTTAB) && __SVR4 +#elif !defined(BFS_USE_MNTTAB) && BFS_HAS_GETMNTENT_2 # define BFS_USE_MNTTAB true #endif -- cgit v1.2.3 From 9413c9b1bc2ab2e47bcb75257a2eece090cc3a3b Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 13:24:43 -0400 Subject: prelude: Kill #include We don't check for `BSD` anymore. --- src/prelude.h | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/prelude.h b/src/prelude.h index fbdc85c..72f88b0 100644 --- a/src/prelude.h +++ b/src/prelude.h @@ -127,13 +127,6 @@ extern const char bfs_version[]; # define __has_attribute(attr) false #endif -// Platform detection - -// Get the definition of BSD if available -#if BFS_USE_SYS_PARAM_H -# include -#endif - // Fundamental utilities /** -- cgit v1.2.3 From 8ea16c14863d7d6363e251810ca89b7c214cfadc Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 13:42:43 -0400 Subject: distcheck: Don't build with make -s That way I can see the ./configure output on CI. --- Makefile | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index d89f3b6..69048cf 100644 --- a/Makefile +++ b/Makefile @@ -156,11 +156,11 @@ DISTCHECKS := \ # Test multiple configurations distcheck: - @+${MAKE} -s distcheck-asan - @+test "$$(uname)" = Darwin || ${MAKE} -s distcheck-msan - @+${MAKE} -s distcheck-tsan - @+test "$$(uname)-$$(uname -m)" != Linux-x86_64 || ${MAKE} -s distcheck-m32 - @+${MAKE} -s distcheck-release + @+${MAKE} distcheck-asan + @+test "$$(uname)" = Darwin || ${MAKE} distcheck-msan + @+${MAKE} distcheck-tsan + @+test "$$(uname)-$$(uname -m)" != Linux-x86_64 || ${MAKE} distcheck-m32 + @+${MAKE} distcheck-release .PHONY: distcheck # Per-distcheck configuration @@ -174,7 +174,7 @@ ${DISTCHECKS}:: @${MKDIR} $@ @+cd $@ \ && ../configure ${DISTCHECK_CONFIG_${@:distcheck-%=%}} \ - && ${MAKE} -s check TEST_FLAGS="--sudo --verbose=skipped" + && ${MAKE} check TEST_FLAGS="--sudo --verbose=skipped" ## Packaging (`make install`) -- cgit v1.2.3 From 83a8e57e24c403eb1110bddd59e9f80d57fe04ab Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 14:17:25 -0400 Subject: ci: Save config.log files --- .github/workflows/ci.yml | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e47600b..1d4196e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,6 +41,11 @@ jobs: run: | make -j$(nproc) distcheck + - uses: actions/upload-artifact@v4 + with: + name: linux-config.log + path: distcheck-*/gen/config.log + macos: name: macOS @@ -73,7 +78,6 @@ jobs: with: operating_system: freebsd version: "14.0" - sync_files: runner-to-vm run: | sudo pkg install -y \ @@ -85,6 +89,11 @@ jobs: sudo mount -t fdescfs none /dev/fd make -j$(nproc) distcheck + - uses: actions/upload-artifact@v4 + with: + name: freebsd-config.log + path: distcheck-*/gen/config.log + openbsd: name: OpenBSD @@ -98,7 +107,6 @@ jobs: with: operating_system: openbsd version: "7.5" - sync_files: runner-to-vm run: | sudo pkg_add \ @@ -110,6 +118,11 @@ jobs: ./configure MAKE=gmake gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + - uses: actions/upload-artifact@v4 + with: + name: openbsd-config.log + path: gen/config.log + netbsd: name: NetBSD @@ -123,7 +136,6 @@ jobs: with: operating_system: netbsd version: "10.0" - sync_files: runner-to-vm run: | PATH="/sbin:/usr/sbin:$PATH" @@ -136,6 +148,11 @@ jobs: ./configure make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + - uses: actions/upload-artifact@v4 + with: + name: netbsd-config.log + path: gen/config.log + dragonflybsd: name: DragonFly BSD @@ -149,7 +166,6 @@ jobs: with: release: "6.4.0" usesh: true - copyback: false prepare: | pkg install -y \ @@ -168,6 +184,11 @@ jobs: sudo -u action ./configure sudo -u action make -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + - uses: actions/upload-artifact@v4 + with: + name: dragonfly-config.log + path: gen/config.log + omnios: name: OmniOS @@ -181,7 +202,6 @@ jobs: with: release: "r151048" usesh: true - copyback: false prepare: | pkg install \ @@ -200,3 +220,8 @@ jobs: jobs=$(getconf NPROCESSORS_ONLN) sudo -u action ./configure MAKE=gmake sudo -u action gmake -j$jobs check TEST_FLAGS="--sudo --verbose=skipped" + + - uses: actions/upload-artifact@v4 + with: + name: omnios-config.log + path: gen/config.log -- cgit v1.2.3 From c5cf2cf90834f2f56b2940d2a499a1a614ebfd21 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Tue, 7 May 2024 14:51:41 -0400 Subject: build/has/getdents: Squelch warnings --- build/has/getdents.c | 5 ++--- build/has/getdents64-syscall.c | 5 ++--- build/has/getdents64.c | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/build/has/getdents.c b/build/has/getdents.c index d0d4228..579898f 100644 --- a/build/has/getdents.c +++ b/build/has/getdents.c @@ -4,7 +4,6 @@ #include int main(void) { - struct dirent de; - getdents(3, &de, 1024); - return 0; + char buf[1024]; + return getdents(3, (void *)buf, sizeof(buf)); } diff --git a/build/has/getdents64-syscall.c b/build/has/getdents64-syscall.c index 4838c14..7642d93 100644 --- a/build/has/getdents64-syscall.c +++ b/build/has/getdents64-syscall.c @@ -6,7 +6,6 @@ #include int main(void) { - struct dirent64 de; - syscall(SYS_getdents64, 3, &de, 1024); - return 0; + char buf[1024]; + return syscall(SYS_getdents64, 3, (void *)buf, sizeof(buf)); } diff --git a/build/has/getdents64.c b/build/has/getdents64.c index 1abf36d..d8e8062 100644 --- a/build/has/getdents64.c +++ b/build/has/getdents64.c @@ -4,7 +4,6 @@ #include int main(void) { - struct dirent64 de; - getdents64(3, &de, 1024); - return 0; + char buf[1024]; + return getdents64(3, (void *)buf, sizeof(buf)); } -- cgit v1.2.3