diff --git a/libc/Makefile b/libc/Makefile
index ff29daca..f480861b 100644
--- a/libc/Makefile
+++ b/libc/Makefile
@@ -25,6 +25,7 @@ dirent/alphasort.o \
dirent/dir.o \
dirent/versionsort.o \
errno/errno.o \
+fnmatch/fnmatch.o \
inttypes/imaxabs.o \
inttypes/imaxdiv.o \
inttypes/strtoimax.o \
diff --git a/libc/fnmatch/fnmatch.cpp b/libc/fnmatch/fnmatch.cpp
new file mode 100644
index 00000000..f3c81457
--- /dev/null
+++ b/libc/fnmatch/fnmatch.cpp
@@ -0,0 +1,151 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2013.
+
+ This file is part of the Sortix C Library.
+
+ The Sortix C Library is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or (at your
+ option) any later version.
+
+ The Sortix C Library is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with the Sortix C Library. If not, see .
+
+ fnmatch/fnmatch.cpp
+ Filename matching.
+
+*******************************************************************************/
+
+#include
+#include
+#include
+
+#define __FNM_NOT_LEADING (1 << 31)
+
+// TODO: This doesn't properly handle multibyte sequences.
+// TODO: This doesn't fully implement all the POSIX requirements.
+static bool is_allowed_bracket_pattern(const char* pattern, int flags,
+ const char** pattern_end)
+{
+ size_t pi = 0;
+ if ( pattern[pi++] != '[' )
+ return false;
+ if ( pattern[pi] == '!' || pattern[pi] == '^' )
+ pi++;
+ bool escaped = false;
+ while ( escaped || pattern[pi] != ']' )
+ {
+ if ( !pattern[pi] )
+ return false;
+ else if ( !escaped && pattern[pi] == '\\' )
+ escaped = true;
+ else
+ {
+ if ( (flags & FNM_PATHNAME) && pattern[pi] == '/' )
+ return false;
+ escaped = false;
+ }
+ pi++;
+ }
+ return *pattern_end = pattern + pi + 1, true;
+}
+
+// TODO: This doesn't properly handle multibyte sequences.
+// TODO: This doesn't fully implement all the POSIX requirements.
+static bool matches_bracket_pattern(char c, const char* pattern, int flags)
+{
+ if ( (flags & FNM_PATHNAME) && c == '/' )
+ return false;
+ size_t pi = 1;
+ bool negated = (pattern[pi] == '!' && (pi++, true)) ||
+ (pattern[pi] == '^' && (pi++, true));
+ if ( (flags & FNM_PERIOD) && c == '.' )
+ {
+ if ( negated && !(flags & __FNM_NOT_LEADING) )
+ return false;
+ }
+ bool escaped = false;
+ bool matched_any = false;
+ while ( escaped || pattern[pi] != ']' )
+ {
+ if ( !escaped && pattern[pi] == '\\' )
+ escaped = true;
+ else if ( pattern[pi] == c )
+ {
+ if ( negated )
+ return false;
+ else
+ matched_any = true;
+ escaped = false;
+ }
+ else
+ escaped = false;
+ pi++;
+ }
+ return negated || matched_any;
+}
+
+extern "C" int fnmatch(const char* pattern, const char* string, int flags)
+{
+ int next_flags = flags | __FNM_NOT_LEADING;
+ const char* pattern_end;
+ if ( !pattern[0] )
+ {
+ if ( !string[0] )
+ return 0;
+ }
+ else if ( pattern[0] == '*' )
+ {
+ if ( fnmatch(pattern + 1, string, flags) == 0 )
+ return 0;
+ if ( (flags & FNM_PERIOD) && string[0] == '.' )
+ if ( !(flags & __FNM_NOT_LEADING) )
+ return FNM_NOMATCH;
+ if ( (flags & FNM_PATHNAME) && string[0] == '/' )
+ return FNM_NOMATCH;
+ if ( !string[0] )
+ return FNM_NOMATCH;
+ return fnmatch(pattern, string + 1, next_flags);
+ }
+ else if ( !string[0] )
+ return FNM_NOMATCH;
+ else if ( is_allowed_bracket_pattern(pattern, flags, &pattern_end) )
+ {
+ if ( !matches_bracket_pattern(string[0], pattern, flags) )
+ return FNM_NOMATCH;
+ return fnmatch(pattern_end, string + 1, next_flags);
+ }
+ else if ( !(flags & FNM_NOESCAPE) && pattern[0] == '\\' )
+ {
+ if ( !pattern[1] )
+ return errno = EINVAL, -1;
+ if ( pattern[1] == string[0] )
+ {
+ if ( (flags & FNM_PATHNAME) && string[0] == '/' )
+ next_flags &= ~__FNM_NOT_LEADING;
+ return fnmatch(pattern + 2, string + 1, next_flags);
+ }
+ }
+ else if ( pattern[0] == '?' )
+ {
+ if ( (flags & FNM_PERIOD) && string[0] == '.' )
+ if ( !(flags & __FNM_NOT_LEADING) )
+ return FNM_NOMATCH;
+ if ( (flags & FNM_PATHNAME) && string[0] == '/' )
+ return FNM_NOMATCH;
+ return fnmatch(pattern + 1, string + 1, next_flags);
+ }
+ else if ( pattern[0] == string[0] )
+ {
+ if ( (flags & FNM_PATHNAME) && string[0] == '/' )
+ next_flags &= ~__FNM_NOT_LEADING;
+ return fnmatch(pattern + 1, string + 1, next_flags);
+ }
+ return FNM_NOMATCH;
+}
diff --git a/libc/include/fnmatch.h b/libc/include/fnmatch.h
new file mode 100644
index 00000000..ab71223a
--- /dev/null
+++ b/libc/include/fnmatch.h
@@ -0,0 +1,42 @@
+/*******************************************************************************
+
+ Copyright(C) Jonas 'Sortie' Termansen 2013.
+
+ This file is part of the Sortix C Library.
+
+ The Sortix C Library is free software: you can redistribute it and/or modify
+ it under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or (at your
+ option) any later version.
+
+ The Sortix C Library is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+ License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with the Sortix C Library. If not, see .
+
+ fnmatch.h
+ Filename matching.
+
+*******************************************************************************/
+
+#ifndef INCLUDE_FNMATCH_H
+#define INCLUDE_FNMATCH_H
+
+#include
+
+__BEGIN_DECLS
+
+#define FNM_NOMATCH 1
+
+#define FNM_PATHNAME (1 << 0)
+#define FNM_NOESCAPE (1 << 1)
+#define FNM_PERIOD (1 << 2)
+
+int fnmatch(const char*, const char*, int);
+
+__END_DECLS
+
+#endif
diff --git a/libc/sortix/fnmatch/.gitignore b/libc/sortix/fnmatch/.gitignore
new file mode 100644
index 00000000..e69de29b