fnmatch: implement the GNU FNM_FILE_NAME and FNM_LEADING_DIR extensions

GCC 16's libiberty/fnmatch.c uses both and failed to build against relibc:

    fnmatch.c:84:  error: 'FNM_FILE_NAME' undeclared
    fnmatch.c:213: error: 'FNM_LEADING_DIR' undeclared

FNM_FILE_NAME is the GNU synonym for FNM_PATHNAME, so it is an alias.

FNM_LEADING_DIR is real behaviour, not just a constant: the pattern may
match a leading directory of the input rather than all of it, so 'foo'
matches 'foo/bar'. Implemented by trying the exact match first and then
each prefix ending immediately before a '/'. Declaring the flag without
honouring it would be a stub, which local/AGENTS.md forbids and which
would silently mismatch for any caller that passes it.

Value 16 is the next free bit in relibc's own numbering (NOESCAPE 1,
PATHNAME 2, PERIOD 4, CASEFOLD 8); relibc does not use glibc's bit
assignments, so the value is chosen for this header, not copied.

cargo check passes for x86_64-unknown-redox.
This commit is contained in:
2026-08-03 19:06:40 +03:00
parent 9c5c07f776
commit 259d64e372
+20 -4
View File
@@ -28,6 +28,11 @@ pub const FNM_PERIOD: c_int = 4;
pub const FNM_CASEFOLD: c_int = 8;
/// Equivalent to `FNM_CASEFOLD`.
pub const FNM_IGNORECASE: c_int = FNM_CASEFOLD;
/// Equivalent to `FNM_PATHNAME`. GNU extension.
pub const FNM_FILE_NAME: c_int = FNM_PATHNAME;
/// The pattern may match a leading directory of the input string, i.e.
/// `foo` matches `foo/bar`. GNU extension.
pub const FNM_LEADING_DIR: c_int = 16;
// TODO: FNM_EXTMATCH (Non-POSIX)
unsafe fn tokenize(mut pattern: *const u8, flags: c_int) -> Tree {
@@ -160,10 +165,21 @@ pub unsafe extern "C" fn fnmatch(
let tokens = unsafe { tokenize(pattern.cast::<u8>(), flags) };
if PosixRegex::new(Cow::Owned(tokens))
.case_insensitive(flags & FNM_CASEFOLD == FNM_CASEFOLD)
.matches_exact(input)
.is_some()
let case_insensitive = flags & FNM_CASEFOLD == FNM_CASEFOLD;
let regex = PosixRegex::new(Cow::Owned(tokens)).case_insensitive(case_insensitive);
// FNM_LEADING_DIR: the pattern may match a leading directory of the input
// rather than the whole of it, so `foo` matches `foo/bar`. Try the exact
// match first, then each prefix that ends immediately before a '/'.
let matched = regex.clone().matches_exact(input).is_some()
|| (flags & FNM_LEADING_DIR == FNM_LEADING_DIR
&& input
.iter()
.enumerate()
.filter(|&(_, b)| *b == b'/')
.any(|(i, _)| regex.clone().matches_exact(&input[..i]).is_some()));
if matched
{
0
} else {