From a215ab6de0ae34db7311136404d0f9feab34ef04 Mon Sep 17 00:00:00 2001 From: Tavian Barnes Date: Wed, 5 Jul 2023 10:45:56 -0400 Subject: Use strcmp() instead of fnmatch() if possible --- src/eval.c | 21 +++++++++++++++++---- src/expr.h | 10 ++++++++++ src/parse.c | 17 ++++++++++++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/src/eval.c b/src/eval.c index 5f27681..56753af 100644 --- a/src/eval.c +++ b/src/eval.c @@ -547,6 +547,20 @@ 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. */ @@ -568,7 +582,7 @@ bool eval_lname(const struct bfs_expr *expr, struct bfs_eval *state) { goto done; } - ret = fnmatch(expr->argv[1], name, expr->num) == 0; + ret = eval_fnmatch(expr, name); done: free(name); @@ -589,7 +603,7 @@ bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) { name = copy = xbasename(name); } - bool ret = fnmatch(expr->argv[1], name, expr->num) == 0; + bool ret = eval_fnmatch(expr, name); free(copy); return ret; } @@ -598,8 +612,7 @@ bool eval_name(const struct bfs_expr *expr, struct bfs_eval *state) { * -i?path test. */ bool eval_path(const struct bfs_expr *expr, struct bfs_eval *state) { - const struct BFTW *ftwbuf = state->ftwbuf; - return fnmatch(expr->argv[1], ftwbuf->path, expr->num) == 0; + return eval_fnmatch(expr, state->ftwbuf->path); } /** diff --git a/src/expr.h b/src/expr.h index 356689d..95118b9 100644 --- a/src/expr.h +++ b/src/expr.h @@ -143,6 +143,16 @@ struct bfs_expr { }; }; + /** String comparisons. */ + struct { + /** String pattern. */ + const char *pattern; + /** fnmatch() flags. */ + int fnm_flags; + /** Whether strcmp() can be used instead of fnmatch(). */ + bool literal; + }; + /** Printing actions. */ struct { /** The output stream. */ diff --git a/src/parse.c b/src/parse.c index 623a528..c225a5b 100644 --- a/src/parse.c +++ b/src/parse.c @@ -1676,16 +1676,18 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b return NULL; } + expr->pattern = expr->argv[1]; + if (casefold) { #ifdef FNM_CASEFOLD - expr->num = FNM_CASEFOLD; + expr->fnm_flags = FNM_CASEFOLD; #else parse_expr_error(state, expr, "Missing platform support.\n"); bfs_expr_free(expr); return NULL; #endif } else { - expr->num = 0; + expr->fnm_flags = 0; } // POSIX says, about fnmatch(): @@ -1694,10 +1696,9 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b // return a non-zero value (indicating either no match or an error). // // But not all implementations obey this, so check for it ourselves. - const char *pattern = expr->argv[1]; - size_t i, len = strlen(pattern); + size_t i, len = strlen(expr->pattern); for (i = 0; i < len; ++i) { - if (pattern[len - i - 1] != '\\') { + if (expr->pattern[len - i - 1] != '\\') { break; } } @@ -1707,6 +1708,12 @@ static struct bfs_expr *parse_fnmatch(const struct parser_state *state, struct b 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; } -- cgit v1.2.3