From 863b70d198f62f28581162473a521208dd67879e Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sat, 6 Mar 2021 13:40:24 -0500 Subject: Implement -flags, from FreeBSD find This is the last BSD-specific primary I'm aware of. Fixes #14. --- eval.c | 34 ++++++++++++++++++++++++++++++++ eval.h | 1 + expr.h | 5 +++++ parse.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++ stat.c | 14 +++++++++++++ stat.h | 12 ++++++++---- tests.sh | 15 ++++++++++++++ tests/test_flags.out | 1 + 8 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 tests/test_flags.out diff --git a/eval.c b/eval.c index a72be86..df047f0 100644 --- a/eval.c +++ b/eval.c @@ -441,6 +441,39 @@ done: return ret; } +/** + * -flags test. + */ +bool eval_flags(const struct expr *expr, struct eval_state *state) { + const struct bfs_stat *statbuf = eval_stat(state); + if (!statbuf) { + return false; + } + + if (!(statbuf->mask & BFS_STAT_ATTRS)) { + eval_error(state, "Couldn't get file %s.\n", bfs_stat_field_name(BFS_STAT_ATTRS)); + return false; + } + + unsigned long flags = statbuf->attrs; + unsigned long set = expr->set_flags; + unsigned long clear = expr->clear_flags; + + switch (expr->mode_cmp) { + case MODE_EXACT: + return flags == set && !(flags & clear); + + case MODE_ALL: + return (flags & set) == set && !(flags & clear); + + case MODE_ANY: + return (flags & set) || (flags & clear) != clear; + } + + assert(!"Invalid comparison mode"); + return false; +} + /** * -fstype test. */ @@ -590,6 +623,7 @@ bool eval_perm(const struct expr *expr, struct eval_state *state) { return !(mode & target) == !target; } + assert(!"Invalid comparison mode"); return false; } diff --git a/eval.h b/eval.h index 2a43e09..533857c 100644 --- a/eval.h +++ b/eval.h @@ -77,6 +77,7 @@ bool eval_nouser(const struct expr *expr, struct eval_state *state); bool eval_depth(const struct expr *expr, struct eval_state *state); bool eval_empty(const struct expr *expr, struct eval_state *state); +bool eval_flags(const struct expr *expr, struct eval_state *state); bool eval_fstype(const struct expr *expr, struct eval_state *state); bool eval_hidden(const struct expr *expr, struct eval_state *state); bool eval_inum(const struct expr *expr, struct eval_state *state); diff --git a/expr.h b/expr.h index 2462861..562d84e 100644 --- a/expr.h +++ b/expr.h @@ -135,6 +135,11 @@ struct expr { /** Mode to use for directories (different due to X). */ mode_t dir_mode; + /** Flags that should be set. */ + unsigned long long set_flags; + /** Flags that should be cleared. */ + unsigned long long clear_flags; + /** The optional stat field to look at. */ enum bfs_stat_field stat_field; /** The optional reference time. */ diff --git a/parse.c b/parse.c index 08899eb..fd4585c 100644 --- a/parse.c +++ b/parse.c @@ -1159,6 +1159,60 @@ static struct expr *parse_f(struct parser_state *state, int arg1, int arg2) { return &expr_true; } +/** + * Parse -flags FLAGS. + */ +static struct expr *parse_flags(struct parser_state *state, int arg1, int arg2) { +#if __APPLE__ || __FreeBSD__ + struct expr *expr = parse_unary_test(state, eval_flags); + if (!expr) { + return NULL; + } + + // strtofflags() takes a non-const char * + char *copy = strdup(expr->sdata); + if (!copy) { + parse_perror(state, "strdup()"); + goto err; + } + + char *flags = copy; + switch (flags[0]) { + case '-': + expr->mode_cmp = MODE_ALL; + ++flags; + break; + case '+': + expr->mode_cmp = MODE_ANY; + ++flags; + break; + default: + expr->mode_cmp = MODE_EXACT; + break; + } + + unsigned long set, clear; + if (strtofflags(&flags, &set, &clear) != 0) { + parse_error(state, "${blu}%s${rs}: Invalid flags ${bld}%s${rs}.\n", expr->argv[0], flags); + goto err; + } + + expr->set_flags = set; + expr->clear_flags = clear; + + free(copy); + return expr; + +err: + free(copy); + free_expr(expr); + return NULL; +#else // !(__APPLE__ || __FreeBSD) + parse_error(state, "${blu}%s${rs} is missing platform support.\n", state->argv[0]); + return NULL; +#endif +} + /** * Parse -fls FILE. */ @@ -2888,6 +2942,7 @@ static const struct table_entry parse_table[] = { {"-exit", T_ACTION, parse_exit}, {"-f", T_FLAG, parse_f}, {"-false", T_TEST, parse_const, false}, + {"-flags", T_TEST, parse_flags}, {"-fls", T_ACTION, parse_fls}, {"-follow", T_OPTION, parse_follow, BFTW_FOLLOW_ALL, true}, {"-fprint", T_ACTION, parse_fprint}, diff --git a/stat.c b/stat.c index 7fb6b50..d1132ad 100644 --- a/stat.c +++ b/stat.c @@ -24,6 +24,10 @@ #include #include +#if BFS_HAS_SYS_PARAM +# include +#endif + #ifdef STATX_BASIC_STATS # define HAVE_STATX true #elif __linux__ @@ -65,6 +69,8 @@ const char *bfs_stat_field_name(enum bfs_stat_field field) { return "block count"; case BFS_STAT_RDEV: return "underlying device"; + case BFS_STAT_ATTRS: + return "attributes"; case BFS_STAT_ATIME: return "access time"; case BFS_STAT_BTIME: @@ -121,6 +127,11 @@ static void bfs_stat_convert(const struct stat *statbuf, struct bfs_stat *buf) { buf->rdev = statbuf->st_rdev; buf->mask |= BFS_STAT_RDEV; +#if BSD + buf->attrs = statbuf->st_flags; + buf->mask |= BFS_STAT_ATTRS; +#endif + buf->atime = statbuf->st_atim; buf->mask |= BFS_STAT_ATIME; @@ -244,6 +255,9 @@ static int bfs_statx_impl(int at_fd, const char *at_path, int at_flags, enum bfs buf->rdev = bfs_makedev(xbuf.stx_rdev_major, xbuf.stx_rdev_minor); buf->mask |= BFS_STAT_RDEV; + buf->attrs = xbuf.stx_attributes; + buf->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; diff --git a/stat.h b/stat.h index cb68190..55c75e9 100644 --- a/stat.h +++ b/stat.h @@ -47,10 +47,11 @@ enum bfs_stat_field { BFS_STAT_SIZE = 1 << 7, BFS_STAT_BLOCKS = 1 << 8, BFS_STAT_RDEV = 1 << 9, - BFS_STAT_ATIME = 1 << 10, - BFS_STAT_BTIME = 1 << 11, - BFS_STAT_CTIME = 1 << 12, - BFS_STAT_MTIME = 1 << 13, + 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, }; /** @@ -106,6 +107,9 @@ struct bfs_stat { /** The device ID represented by this file. */ dev_t rdev; + /** Attributes/flags set on the file. */ + unsigned long long attrs; + /** Access time. */ struct timespec atime; /** Birth/creation time. */ diff --git a/tests.sh b/tests.sh index 98a0d2f..b039eea 100755 --- a/tests.sh +++ b/tests.sh @@ -327,6 +327,8 @@ bsd_tests=( test_exit + test_flags + test_follow test_gid_name @@ -2929,6 +2931,19 @@ function test_exclude_exclude() { ! quiet invoke_bfs basic -exclude -exclude -name foo } +function test_flags() { + if ! quiet invoke_bfs scratch -quit -flags offline; then + return 0 + fi + + rm -rf scratch/* + + touch scratch/{foo,bar} + quiet chflags offline scratch/bar + + bfs_diff scratch -flags -offline,nohidden +} + BOL= EOL='\n' diff --git a/tests/test_flags.out b/tests/test_flags.out new file mode 100644 index 0000000..11998ed --- /dev/null +++ b/tests/test_flags.out @@ -0,0 +1 @@ +scratch/bar -- cgit v1.2.3