summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTavian Barnes <tavianator@tavianator.com>2016-12-18 12:38:19 -0500
committerTavian Barnes <tavianator@tavianator.com>2016-12-18 13:19:04 -0500
commit4b9592f93a68f88152b390898004e5e54b540cae (patch)
tree18930e6373e5e75aa2999f405971d7a29e4eebc8
parent2ebbe75a8d272458cd3229b6033b2a5ce19b105b (diff)
downloadbfs-4b9592f93a68f88152b390898004e5e54b540cae.tar.xz
Implement -regex, -iregex, and -regextype/-E
-rw-r--r--bfs.h5
-rw-r--r--bftw.c5
-rw-r--r--eval.c33
-rw-r--r--parse.c116
-rwxr-xr-xtests.sh30
-rw-r--r--tests/test_0101.out3
-rw-r--r--tests/test_0102.out3
-rw-r--r--tests/test_0103.out1
-rw-r--r--tests/test_0104.out1
-rw-r--r--tests/test_0105.out1
-rw-r--r--tests/test_0106.out1
-rw-r--r--util.c11
-rw-r--r--util.h16
13 files changed, 218 insertions, 8 deletions
diff --git a/bfs.h b/bfs.h
index c8b4255..732a6c5 100644
--- a/bfs.h
+++ b/bfs.h
@@ -13,6 +13,7 @@
#define BFS_H
#include "color.h"
+#include <regex.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
@@ -240,6 +241,9 @@ struct expr {
/** Optional -exec flags. */
enum exec_flags exec_flags;
+ /** Optional compiled regex. */
+ regex_t *regex;
+
/** Optional integer data for this expression. */
long long idata;
@@ -293,6 +297,7 @@ bool eval_xtype(const struct expr *expr, struct eval_state *state);
bool eval_lname(const struct expr *expr, struct eval_state *state);
bool eval_name(const struct expr *expr, struct eval_state *state);
bool eval_path(const struct expr *expr, struct eval_state *state);
+bool eval_regex(const struct expr *expr, struct eval_state *state);
bool eval_delete(const struct expr *expr, struct eval_state *state);
bool eval_exec(const struct expr *expr, struct eval_state *state);
diff --git a/bftw.c b/bftw.c
index c9dd47b..eb4f0c0 100644
--- a/bftw.c
+++ b/bftw.c
@@ -341,7 +341,10 @@ static DIR *dircache_entry_open(struct dircache *cache, struct dircache_entry *e
const char *at_path = path;
struct dircache_entry *base = dircache_entry_base(cache, entry, &at_fd, &at_path);
- int flags = O_RDONLY | O_DIRECTORY | O_CLOEXEC;
+ int flags = O_RDONLY | O_CLOEXEC;
+#ifdef O_DIRECTORY
+ flags |= O_DIRECTORY;
+#endif
int fd = openat(at_fd, at_path, flags);
if (fd < 0 && dircache_should_retry(cache, base)) {
diff --git a/eval.c b/eval.c
index ebead3b..123c37a 100644
--- a/eval.c
+++ b/eval.c
@@ -743,6 +743,39 @@ bool eval_quit(const struct expr *expr, struct eval_state *state) {
}
/**
+ * -i?regex test.
+ */
+bool eval_regex(const struct expr *expr, struct eval_state *state) {
+ const char *path = state->ftwbuf->path;
+ size_t len = strlen(path);
+ regmatch_t match = {
+ .rm_so = 0,
+ .rm_eo = len,
+ };
+
+ int flags = 0;
+#ifdef REG_STARTEND
+ flags |= REG_STARTEND;
+#endif
+ int err = regexec(expr->regex, path, 1, &match, flags);
+ if (err == 0) {
+ return match.rm_so == 0 && match.rm_eo == len;
+ } else {
+ if (err != REG_NOMATCH) {
+ char *str = xregerror(err, expr->regex);
+ if (str) {
+ pretty_error(state->cmdline->stderr_colors,
+ "'%s': %s\n", path, str);
+ free(str);
+ } else {
+ perror("xregerror()");
+ }
+ }
+ return false;
+ }
+}
+
+/**
* -samefile test.
*/
bool eval_samefile(const struct expr *expr, struct eval_state *state) {
diff --git a/parse.c b/parse.c
index 2a78686..b9e979c 100644
--- a/parse.c
+++ b/parse.c
@@ -19,6 +19,7 @@
#include <grp.h>
#include <limits.h>
#include <pwd.h>
+#include <regex.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
@@ -71,6 +72,11 @@ static void free_expr(struct expr *expr) {
}
}
+ if (expr->regex) {
+ regfree(expr->regex);
+ free(expr->regex);
+ }
+
free_expr(expr->lhs);
free_expr(expr->rhs);
free(expr);
@@ -94,6 +100,7 @@ static struct expr *new_expr(eval_fn *eval, bool pure, size_t argc, char **argv)
expr->argc = argc;
expr->argv = argv;
expr->file = NULL;
+ expr->regex = NULL;
}
return expr;
}
@@ -196,8 +203,8 @@ struct parser_state {
/** The current tail of the root path list. */
struct root **roots_tail;
- /** The optimization level. */
- int optlevel;
+ /** The current regex flags to use. */
+ int regex_flags;
/** Whether a -print action is implied. */
bool implicit_print;
@@ -530,6 +537,21 @@ static struct expr *parse_nullary_positional_option(struct parser_state *state)
}
/**
+ * Parse a positional option that takes a single value.
+ */
+static struct expr *parse_unary_positional_option(struct parser_state *state, const char **value) {
+ const char *arg = state->argv[0];
+ *value = state->argv[1];
+ if (!*value) {
+ pretty_error(state->cmdline->stderr_colors,
+ "error: %s needs a value.\n", arg);
+ return NULL;
+ }
+
+ return parse_positional_option(state, 2);
+}
+
+/**
* Parse a single test.
*/
static struct expr *parse_test(struct parser_state *state, eval_fn *eval, size_t argc) {
@@ -1496,6 +1518,91 @@ static struct expr *parse_quit(struct parser_state *state, int arg1, int arg2) {
}
/**
+ * Parse -i?regex.
+ */
+static struct expr *parse_regex(struct parser_state *state, int flags, int arg2) {
+ struct expr *expr = parse_unary_test(state, eval_regex);
+ if (!expr) {
+ goto fail;
+ }
+
+ expr->regex = malloc(sizeof(regex_t));
+ if (!expr->regex) {
+ perror("malloc()");
+ goto fail;
+ }
+
+ int err = regcomp(expr->regex, expr->sdata, state->regex_flags | flags);
+ if (err != 0) {
+ char *str = xregerror(err, expr->regex);
+ if (str) {
+ pretty_error(state->cmdline->stderr_colors,
+ "error: %s %s: %s.\n",
+ expr->argv[0], expr->argv[1], str);
+ free(str);
+ } else {
+ perror("xregerror()");
+ }
+ goto fail_regex;
+ }
+
+ return expr;
+
+fail_regex:
+ free(expr->regex);
+ expr->regex = NULL;
+fail:
+ free_expr(expr);
+ return NULL;
+}
+
+/**
+ * Parse -E.
+ */
+static struct expr *parse_regex_extended(struct parser_state *state, int arg1, int arg2) {
+ state->regex_flags = REG_EXTENDED;
+ return parse_nullary_flag(state);
+}
+
+/**
+ * Parse -regextype TYPE.
+ */
+static struct expr *parse_regextype(struct parser_state *state, int arg1, int arg2) {
+ const char *type;
+ struct expr *expr = parse_unary_positional_option(state, &type);
+ if (!expr) {
+ goto fail;
+ }
+
+ FILE *file = stderr;
+
+ if (strcmp(type, "posix-basic") == 0) {
+ state->regex_flags = 0;
+ } else if (strcmp(type, "posix-extended") == 0) {
+ state->regex_flags = REG_EXTENDED;
+ } else if (strcmp(type, "help") == 0) {
+ state->just_info = true;
+ file = stdout;
+ goto fail_list_types;
+ } else {
+ goto fail_bad_type;
+ }
+
+ return expr;
+
+fail_bad_type:
+ pretty_error(state->cmdline->stderr_colors,
+ "error: Unsupported -regextype '%s'.\n\n", type);
+fail_list_types:
+ fputs("Supported types are:\n\n", file);
+ fputs(" posix-basic: POSIX basic regular expressions (BRE)\n", file);
+ fputs(" posix-extended: POSIX extended regular expressions (ERE)\n", file);
+fail:
+ free_expr(expr);
+ return NULL;
+}
+
+/**
* Parse -samefile FILE.
*/
static struct expr *parse_samefile(struct parser_state *state, int arg1, int arg2) {
@@ -1676,6 +1783,7 @@ struct table_entry {
*/
static const struct table_entry parse_table[] = {
{"D", false, parse_debug},
+ {"E", false, parse_regex_extended},
{"O", true, parse_optlevel},
{"P", false, parse_follow, 0, false},
{"H", false, parse_follow, BFTW_COMFOLLOW, false},
@@ -1711,6 +1819,7 @@ static const struct table_entry parse_table[] = {
{"iname", false, parse_name, true},
{"inum", false, parse_inum},
{"ipath", false, parse_path, true},
+ {"iregex", false, parse_regex, REG_ICASE},
{"iwholename", false, parse_path, true},
{"links", false, parse_links},
{"lname", false, parse_lname, false},
@@ -1739,6 +1848,8 @@ static const struct table_entry parse_table[] = {
{"prune", false, parse_prune},
{"quit", false, parse_quit},
{"readable", false, parse_access, R_OK},
+ {"regex", false, parse_regex, 0},
+ {"regextype", false, parse_regextype},
{"samefile", false, parse_samefile},
{"size", false, parse_size},
{"true", false, parse_const, true},
@@ -2275,6 +2386,7 @@ struct cmdline *parse_cmdline(int argc, char *argv[]) {
.argv = argv + 1,
.command = argv[0],
.roots_tail = &cmdline->roots,
+ .regex_flags = 0,
.implicit_print = true,
.warn = isatty(STDIN_FILENO),
.non_option_seen = false,
diff --git a/tests.sh b/tests.sh
index b880612..afd7275 100755
--- a/tests.sh
+++ b/tests.sh
@@ -550,7 +550,35 @@ function test_0100() {
bfs_diff /// -maxdepth 0 -execdir echo '{}' ';'
}
-for i in {1..100}; do
+function test_0101() {
+ bfs_diff basic -regex 'basic/./.'
+}
+
+function test_0102() {
+ bfs_diff basic -iregex 'basic/[A-Z]/[a-z]'
+}
+
+function test_0103() {
+ cd weirdnames
+ bfs_diff -regex '\./\((\)'
+}
+
+function test_0104() {
+ cd weirdnames
+ bfs_diff -E -regex '\./(\()'
+}
+
+function test_0105() {
+ cd weirdnames
+ bfs_diff -regextype posix-basic -regex '\./\((\)'
+}
+
+function test_0106() {
+ cd weirdnames
+ bfs_diff -regextype posix-extended -regex '\./(\()'
+}
+
+for i in {1..106}; do
test="test_$(printf '%04d' $i)"
if [ -t 1 ]; then
diff --git a/tests/test_0101.out b/tests/test_0101.out
new file mode 100644
index 0000000..cfc113b
--- /dev/null
+++ b/tests/test_0101.out
@@ -0,0 +1,3 @@
+basic/c/d
+basic/e/f
+basic/g/h
diff --git a/tests/test_0102.out b/tests/test_0102.out
new file mode 100644
index 0000000..cfc113b
--- /dev/null
+++ b/tests/test_0102.out
@@ -0,0 +1,3 @@
+basic/c/d
+basic/e/f
+basic/g/h
diff --git a/tests/test_0103.out b/tests/test_0103.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/test_0103.out
@@ -0,0 +1 @@
+./(
diff --git a/tests/test_0104.out b/tests/test_0104.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/test_0104.out
@@ -0,0 +1 @@
+./(
diff --git a/tests/test_0105.out b/tests/test_0105.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/test_0105.out
@@ -0,0 +1 @@
+./(
diff --git a/tests/test_0106.out b/tests/test_0106.out
new file mode 100644
index 0000000..0f0971e
--- /dev/null
+++ b/tests/test_0106.out
@@ -0,0 +1 @@
+./(
diff --git a/util.c b/util.c
index bc611a8..ce0b458 100644
--- a/util.c
+++ b/util.c
@@ -12,7 +12,9 @@
#include "util.h"
#include <errno.h>
#include <fcntl.h>
+#include <regex.h>
#include <stdarg.h>
+#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
@@ -76,3 +78,12 @@ int dup_cloexec(int fd) {
return ret;
#endif
}
+
+char *xregerror(int err, const regex_t *regex) {
+ size_t len = regerror(err, regex, NULL, 0);
+ char *str = malloc(len);
+ if (str) {
+ regerror(err, regex, str, len);
+ }
+ return str;
+}
diff --git a/util.h b/util.h
index f65e9f5..4fd5962 100644
--- a/util.h
+++ b/util.h
@@ -15,6 +15,7 @@
#include <dirent.h>
#include <fcntl.h>
#include <fnmatch.h>
+#include <regex.h>
#include <stdbool.h>
#include <sys/stat.h>
@@ -30,10 +31,6 @@
# define FNM_CASEFOLD FNM_IGNORECASE
#endif
-#ifndef O_DIRECTORY
-# define O_DIRECTORY 0
-#endif
-
#ifndef S_ISDOOR
# define S_ISDOOR(mode) false
#endif
@@ -80,4 +77,15 @@ int redirect(int fd, const char *path, int flags, ...);
*/
int dup_cloexec(int fd);
+/**
+ * Dynamically allocate a regex error message.
+ *
+ * @param err
+ * The error code to stringify.
+ * @param regex
+ * The (partially) compiled regex.
+ * @return A human-readable description of the error, allocated with malloc().
+ */
+char *xregerror(int err, const regex_t *regex);
+
#endif // BFS_UTIL_H