summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2024-04-10 10:10:51 -0400
committerTavian Barnes <tavianator@tavianator.com>2024-04-10 13:15:49 -0400
commit8f6b0c1b360f2fea3f7f6563808513cbdd51df80 (patch)
treec90846bd2b002904d2e2534baa7c78189c7ff476
parentc0cbaab04b3d37a1786f04018eb6226359291031 (diff)
Implement -context
Closes: https://github.com/tavianator/bfs/issues/27
-rw-r--r--completions/bfs.bash1
-rw-r--r--completions/bfs.fish1
-rw-r--r--completions/bfs.zsh1
-rw-r--r--docs/bfs.14
-rw-r--r--src/eval.c43
-rw-r--r--src/eval.h1
-rw-r--r--src/parse.c114
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.
*/
@@ -194,6 +208,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.
*/
static const struct timespec *eval_stat_time(const struct bfs_stat *statbuf, enum bfs_stat_field field, struct bfs_eval *state) {
@@ -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
@@ -1076,6 +1076,67 @@ static struct bfs_expr *parse_color(struct bfs_parser *parser, int color, int ar
}
/**
+ * 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 <backslash>, 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}.
*/
static struct bfs_expr *parse_const(struct bfs_parser *parser, int value, int arg2) {
@@ -1632,54 +1693,6 @@ static struct bfs_expr *parse_mount(struct bfs_parser *parser, int arg1, int arg
}
/**
- * 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 <backslash>, 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.
*/
static struct bfs_expr *parse_name(struct bfs_parser *parser, int casefold, int arg2) {
@@ -2768,6 +2781,10 @@ static struct bfs_expr *parse_help(struct bfs_parser *parser, int arg1, int arg2
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");
cfprintf(cout, " ${blu}-empty${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},