[klibc] Add fnmatch() function for udev support

Add fnmatch() function to support udev.  Thanks to Aaron Griffin for the
original implementation.

Signed-off-by: H. Peter Anvin <hpa@zytor.com>
diff --git a/usr/include/fnmatch.h b/usr/include/fnmatch.h
new file mode 100644
index 0000000..d58ecf6
--- /dev/null
+++ b/usr/include/fnmatch.h
@@ -0,0 +1,16 @@
+#ifndef _FNMATCH_H
+#define _FNMATCH_H
+
+#include <klibc/extern.h>
+
+#define FNM_NOMATCH		1
+
+#define FNM_PATHNAME		1
+#define FNM_FILE_NAME		FNM_PATHNAME
+#define FNM_NOESCAPE		2
+#define FNM_PERIOD		4
+
+__extern int fnmatch(const char *, const char *, int);
+
+#endif /* _FNMATCH_H */
+
diff --git a/usr/klibc/Kbuild b/usr/klibc/Kbuild
index b47d6e3..e6b96b6 100644
--- a/usr/klibc/Kbuild
+++ b/usr/klibc/Kbuild
@@ -36,6 +36,7 @@
 	  strncat.o strlcpy.o strlcat.o \
 	  strstr.o strncmp.o strncpy.o strrchr.o \
 	  strxspn.o strspn.o strcspn.o strpbrk.o strsep.o strtok.o \
+	  fnmatch.o \
 	  gethostname.o getdomainname.o getcwd.o \
 	  seteuid.o setegid.o \
 	  getenv.o setenv.o putenv.o __put_env.o unsetenv.o \
diff --git a/usr/klibc/fnmatch.c b/usr/klibc/fnmatch.c
new file mode 100644
index 0000000..39f1f0e
--- /dev/null
+++ b/usr/klibc/fnmatch.c
@@ -0,0 +1,72 @@
+/*
+ * fnmatch.c
+ *
+ * Original implementation by Aaron Griffin, modified by H. Peter Anvin
+ */
+
+#include <fnmatch.h>
+
+int fnmatch(const char *p, const char *s, int flags)
+{
+	if (flags & FNM_PATHNAME && *s == '/')
+		return (*p != '/') || fnmatch(p+1, s+1, flags);
+	if (flags & FNM_PERIOD && *s == '.')
+		return (*p != '.') || fnmatch(p+1, s+1, flags);
+
+	flags &= ~FNM_PERIOD;	/* Only applies at beginning */
+
+	if (!(flags & FNM_NOESCAPE) && *p == '\\') {
+		p++;
+		return (*p != *s) || fnmatch(p+1, s+1, flags);
+	}
+
+	if (*s == '\0') {
+		while (*p == '*')
+			p++;
+		return (*p != '\0');
+	}
+
+	switch (*p) {
+	case '[':
+		{
+			int not = 0;
+			p++;
+			if (*p == '!') {
+				not = 1;
+				p++;
+			}
+			while ((*p != '\0') && (*p != ']')) {
+				int match = 0;
+				if (p[1] == '-') {
+					if ((*s >= *p) && (*s <= p[2]))
+						match = 1;
+					p += 3;
+				} else {
+					match = (*p == *s);
+					p++;
+				}
+				if (match ^ not) {
+					while ((*p != '\0') && (*p != ']'))
+						p++;
+					if (*p == ']')
+						return fnmatch(p+1, s+1, flags);
+				}
+			}
+		}
+		break;
+	case '*':
+		if (fnmatch(p, s+1, flags))
+			return fnmatch(p+1, s, flags);
+		return 0;
+	case '\0':
+		if (*s == '\0') {
+			return 0;
+		}
+		break;
+	default:
+		if ((*p == *s) || (*p == '?'))
+			return fnmatch(p+1, s+1, flags);
+		break;
+	}
+	return 1;
+}
diff --git a/usr/klibc/tests/Kbuild b/usr/klibc/tests/Kbuild
index a97e86c..af14763 100644
--- a/usr/klibc/tests/Kbuild
+++ b/usr/klibc/tests/Kbuild
@@ -15,6 +15,7 @@
 
 environ.shared-y	:= environ.o
 fcntl.shared-y		:= fcntl.o
+fnmatch.shared-y	:= fnmatch.o
 getopttest.shared-y	:= getopttest.o
 getpagesize.shared-y	:= getpagesize.o
 hello.shared-y		:= hello.o
diff --git a/usr/klibc/tests/fnmatch.c b/usr/klibc/tests/fnmatch.c
new file mode 100644
index 0000000..4150857
--- /dev/null
+++ b/usr/klibc/tests/fnmatch.c
@@ -0,0 +1,15 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <fnmatch.h>
+
+int main(int argc, char *argv[])
+{
+	int flags = atoi(argv[3]);
+	int match = fnmatch(argv[1], argv[2], flags);
+
+	printf("\"%s\" matches \"%s\": %d\n", argv[1], argv[2], match);
+
+	return match;
+}
+
+