summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2021-03-06 13:40:24 -0500
committerTavian Barnes <tavianator@tavianator.com>2021-03-06 13:40:24 -0500
commit863b70d198f62f28581162473a521208dd67879e (patch)
tree9506283742eef1951ecaa897f4584b3941499a51
parent8f201b2380aef3a566316343e7e71c6fc995cf41 (diff)
downloadbfs-863b70d198f62f28581162473a521208dd67879e.tar.xz
Implement -flags, from FreeBSD find
This is the last BSD-specific primary I'm aware of. Fixes #14.
-rw-r--r--eval.c34
-rw-r--r--eval.h1
-rw-r--r--expr.h5
-rw-r--r--parse.c55
-rw-r--r--stat.c14
-rw-r--r--stat.h12
-rwxr-xr-xtests.sh15
-rw-r--r--tests/test_flags.out1
8 files changed, 133 insertions, 4 deletions
diff --git a/eval.c b/eval.c
index a72be86..df047f0 100644
--- a/eval.c
+++ b/eval.c
@@ -442,6 +442,39 @@ done:
}
/**
+ * -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.
*/
bool eval_fstype(const struct expr *expr, struct eval_state *state) {
@@ -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
@@ -1160,6 +1160,60 @@ static struct expr *parse_f(struct parser_state *state, int arg1, int arg2) {
}
/**
+ * 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.
*/
static struct expr *parse_fls(struct parser_state *state, int arg1, int arg2) {
@@ -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 <sys/types.h>
#include <sys/stat.h>
+#if BFS_HAS_SYS_PARAM
+# include <sys/param.h>
+#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