From 4b9592f93a68f88152b390898004e5e54b540cae Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Sun, 18 Dec 2016 12:38:19 -0500 Subject: Implement -regex, -iregex, and -regextype/-E --- bfs.h | 5 +++ bftw.c | 5 ++- eval.c | 33 +++++++++++++++ parse.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++++++- tests.sh | 30 +++++++++++++- tests/test_0101.out | 3 ++ tests/test_0102.out | 3 ++ tests/test_0103.out | 1 + tests/test_0104.out | 1 + tests/test_0105.out | 1 + tests/test_0106.out | 1 + util.c | 11 +++++ util.h | 16 ++++++-- 13 files changed, 218 insertions(+), 8 deletions(-) create mode 100644 tests/test_0101.out create mode 100644 tests/test_0102.out create mode 100644 tests/test_0103.out create mode 100644 tests/test_0104.out create mode 100644 tests/test_0105.out create mode 100644 tests/test_0106.out 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 #include #include #include @@ -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 @@ -742,6 +742,39 @@ bool eval_quit(const struct expr *expr, struct eval_state *state) { return true; } +/** + * -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. */ diff --git a/parse.c b/parse.c index 2a78686..b9e979c 100644 --- a/parse.c +++ b/parse.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -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; @@ -529,6 +536,21 @@ static struct expr *parse_nullary_positional_option(struct parser_state *state) return parse_positional_option(state, 1); } +/** + * 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. */ @@ -1495,6 +1517,91 @@ static struct expr *parse_quit(struct parser_state *state, int arg1, int arg2) { return parse_nullary_action(state, eval_quit); } +/** + * 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. */ @@ -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 #include +#include #include +#include #include #include #include @@ -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 #include #include +#include #include #include @@ -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 -- cgit v1.2.3