Re: lib/60517: fnmatch(2) does not implement FNM_PATHNAME correctly
"Luke Mewburn via gnats" <[email protected]>
| Newsgroups | gmane.os.netbsd.bugs |
|---|---|
| Message-ID | <[email protected]> |
The following reply was made to PR lib/60517; it has been noted by GNATS. From: Luke Mewburn <[email protected]> To: [email protected], [email protected] Cc: [email protected], [email protected], [email protected] Subject: Re: lib/60517: fnmatch(2) does not implement FNM_PATHNAME correctly Date: Wed, 5 Aug 2026 18:32:02 +1000 On 26-07-29 17:35, [email protected] via gnats wrote: | >Description: | fnmatch(3) does not implement FNM_PATHNAME correctly in rangematch, | causing some patterns to not match correctly. | | >How-To-Repeat: | The following program reproduces the issue: | | #include <fnmatch.h> | #include <stdio.h> | | int main(int argc, char *argv[]) { | int match = fnmatch(argv[1], argv[2], 0); | printf("%d\n", match); | return 0; | } | | Run: | | ./fnmatch '[\\/$]*' '/usr/lib' | | And it should print 0. (+ Christos) Hi Drew, tl;dr: I think you have identified a bug with fnmatch without FNM_PATHNAME, even if I think your test case pattern was a bit tricky at first :) Unless Christos (or someone else) disagrees with your report and my analysis, I think this should be committed. Details: When I first analyzed your report it wasn't clear what the fault was: 1. Your pattern was a bit tricky. Now I see that the intention is to match any string starting with `\', `/', or `$', relying upon `\\' to become `\` because you're not using FNM_NOESCAPE. 2. You're not using FNM_PATHNAME. I've dug into the commit that caused the issue, and read multiple versions of the relevant POSIX pages, and I now understand your bug report and think your proposed fix is correct. The commit to lib/libc/gen/fnmatch.c 1.26 on 2014-10-12 was by Christos, with commit message: Fix fnmatch issues according to POSIX. http://pubs.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_13_01 1. A [...] pattern containing a slash is not a pattern; the [ ]'s become regular characters 2. A [] or a [!] is not an empty pattern, why would it? The first would never match and the second would always match which makes it equivalent to ? In those cases the ] is taken as a literal character and does not have special meaning. Reading that POSIX (issue 6) "Shell Command Language" page linked in item 1, I don't actually see the text relating to item 1 in section 2.13.1. There's related text in section 2.13.3 "Patterns Used for Filename Expansion". However, the POSIX (issue 6) fnmatch page at https://pubs.opengroup.org/onlinepubs/009695399/functions/fnmatch.html doesn't cross-reference back to this shell section 2.13.3 about filename expansion. It seems to me that fnmatch FNM_PATHNAME has a limited and different implementation than the shell. The relevant text in fnmatch for slash without FNM_PATHNAME is: If the FNM_PATHNAME flag is not set, the slash character shall be treated as an ordinary character. The latest POSIX (issue 8) page for fnmatch at https://pubs.opengroup.org/onlinepubs/9799919799/functions/fnmatch.html has similar text: If the FNM_PATHNAME flag is not set, the <slash> character shall be treated as an ordinary character. Regards, Luke. | >Fix: | --- src.orig/lib/libc/gen/fnmatch.c 2014-10-13 00:32:33.000000000 +0200 | +++ src/lib/libc/gen/fnmatch.c 2026-07-29 15:25:45.271817024 +0200 | @@ -91,7 +91,7 @@ | need = 1; | for (ok = 0; (c = FOLDCASE(*pattern++, flags)) != ']' || need;) { | need = 0; | - if (c == '/') | + if (c == '/' && (flags & FNM_PATHNAME)) | return (void *)-1; | if (c == '\\' && !(flags & FNM_NOESCAPE)) | c = FOLDCASE(*pattern++, flags); | --- src.orig/tests/lib/libc/gen/t_fnmatch.c 2016-10-31 06:08:53.000000000 +0100 | +++ src/tests/lib/libc/gen/t_fnmatch.c 2026-07-29 19:25:10.723264988 +0200 | @@ -125,6 +125,8 @@ | ATF_CHECK(fnmatch("???x", "xxxx", FNM_PATHNAME) == 0); | ATF_CHECK(fnmatch("*/xxx", "/xxx", FNM_PATHNAME) == 0); | ATF_CHECK(fnmatch("x/*.y", "x/z.y", FNM_PATHNAME) == 0); | + | + ATF_CHECK(fnmatch("[\\\\/$]*", "/usr/lib", 0) == 0); | } |