Skip to content

Commit

Permalink
Use strcmp() instead of fnmatch() if possible
Browse files Browse the repository at this point in the history
  • Loading branch information
tavianator committed Jul 6, 2023
1 parent 66264f1 commit a215ab6
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 9 deletions.
21 changes: 17 additions & 4 deletions src/eval.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand All @@ -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);
Expand All @@ -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;
}
Expand All @@ -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);
}

/**
Expand Down
10 changes: 10 additions & 0 deletions src/expr.h
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down
17 changes: 12 additions & 5 deletions src/parse.c
Original file line number Diff line number Diff line change
Expand Up @@ -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():
Expand All @@ -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;
}
}
Expand All @@ -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;
}

Expand Down

0 comments on commit a215ab6

Please sign in to comment.