diff options
author | jilles <jilles@FreeBSD.org> | 2015-09-27 12:52:18 +0000 |
---|---|---|
committer | jilles <jilles@FreeBSD.org> | 2015-09-27 12:52:18 +0000 |
commit | 9ee306217426e87108150ba032bf29b14893e7a0 (patch) | |
tree | 04c39657afe8ad329b38146eabb7023b7ffe33fd /lib/libc | |
parent | 26dd2f1071b9db5d6b29ec51b0312626ade09f77 (diff) | |
download | FreeBSD-src-9ee306217426e87108150ba032bf29b14893e7a0.zip FreeBSD-src-9ee306217426e87108150ba032bf29b14893e7a0.tar.gz |
fnmatch(): Remove exponential behaviour as in sh r229201.
The old code was exponential in the number of asterisks in the pattern.
However, once a match has been found upto the next asterisk, the previous
asterisks are no longer relevant.
Diffstat (limited to 'lib/libc')
-rw-r--r-- | lib/libc/gen/fnmatch.c | 77 |
1 files changed, 49 insertions, 28 deletions
diff --git a/lib/libc/gen/fnmatch.c b/lib/libc/gen/fnmatch.c index 173592a..2f240a8 100644 --- a/lib/libc/gen/fnmatch.c +++ b/lib/libc/gen/fnmatch.c @@ -87,11 +87,14 @@ static int fnmatch1(const char *pattern, const char *string, const char *stringstart, int flags, mbstate_t patmbs, mbstate_t strmbs) { + const char *bt_pattern, *bt_string; + mbstate_t bt_patmbs, bt_strmbs; char *newp; char c; wchar_t pc, sc; size_t pclen, sclen; + bt_pattern = bt_string = NULL; for (;;) { pclen = mbrtowc(&pc, pattern, MB_LEN_MAX, &patmbs); if (pclen == (size_t)-1 || pclen == (size_t)-2) @@ -107,16 +110,18 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart, case EOS: if ((flags & FNM_LEADING_DIR) && sc == '/') return (0); - return (sc == EOS ? 0 : FNM_NOMATCH); + if (sc == EOS) + return (0); + goto backtrack; case '?': if (sc == EOS) return (FNM_NOMATCH); if (sc == '/' && (flags & FNM_PATHNAME)) - return (FNM_NOMATCH); + goto backtrack; if (sc == '.' && (flags & FNM_PERIOD) && (string == stringstart || ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); + goto backtrack; string += sclen; break; case '*': @@ -128,7 +133,7 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart, if (sc == '.' && (flags & FNM_PERIOD) && (string == stringstart || ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); + goto backtrack; /* Optimize for pattern with * at end or before /. */ if (c == EOS) @@ -144,33 +149,24 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart, break; } - /* General case, use recursion. */ - while (sc != EOS) { - if (!fnmatch1(pattern, string, stringstart, - flags, patmbs, strmbs)) - return (0); - sclen = mbrtowc(&sc, string, MB_LEN_MAX, - &strmbs); - if (sclen == (size_t)-1 || - sclen == (size_t)-2) { - sc = (unsigned char)*string; - sclen = 1; - memset(&strmbs, 0, sizeof(strmbs)); - } - if (sc == '/' && flags & FNM_PATHNAME) - break; - string += sclen; - } - return (FNM_NOMATCH); + /* + * First try the shortest match for the '*' that + * could work. We can forget any earlier '*' since + * there is no way having it match more characters + * can help us, given that we are already here. + */ + bt_pattern = pattern, bt_patmbs = patmbs; + bt_string = string, bt_strmbs = strmbs; + break; case '[': if (sc == EOS) return (FNM_NOMATCH); if (sc == '/' && (flags & FNM_PATHNAME)) - return (FNM_NOMATCH); + goto backtrack; if (sc == '.' && (flags & FNM_PERIOD) && (string == stringstart || ((flags & FNM_PATHNAME) && *(string - 1) == '/'))) - return (FNM_NOMATCH); + goto backtrack; switch (rangematch(pattern, sc, flags, &newp, &patmbs)) { @@ -180,7 +176,7 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart, pattern = newp; break; case RANGE_NOMATCH: - return (FNM_NOMATCH); + goto backtrack; } string += sclen; break; @@ -195,14 +191,39 @@ fnmatch1(const char *pattern, const char *string, const char *stringstart, /* FALLTHROUGH */ default: norm: + string += sclen; if (pc == sc) ; else if ((flags & FNM_CASEFOLD) && (towlower(pc) == towlower(sc))) ; - else - return (FNM_NOMATCH); - string += sclen; + else { + backtrack: + /* + * If we have a mismatch (other than hitting + * the end of the string), go back to the last + * '*' seen and have it match one additional + * character. + */ + if (bt_pattern == NULL) + return (FNM_NOMATCH); + sclen = mbrtowc(&sc, bt_string, MB_LEN_MAX, + &bt_strmbs); + if (sclen == (size_t)-1 || + sclen == (size_t)-2) { + sc = (unsigned char)*bt_string; + sclen = 1; + memset(&bt_strmbs, 0, + sizeof(bt_strmbs)); + } + if (sc == EOS) + return (FNM_NOMATCH); + if (sc == '/' && flags & FNM_PATHNAME) + return (FNM_NOMATCH); + bt_string += sclen; + pattern = bt_pattern, patmbs = bt_patmbs; + string = bt_string, strmbs = bt_strmbs; + } break; } } |