usual/fnmatch: fnmatch compat
authorMarko Kreen <markokr@gmail.com>
Sat, 29 Dec 2012 17:21:13 +0000 (19:21 +0200)
committerMarko Kreen <markokr@gmail.com>
Thu, 3 Jan 2013 22:02:19 +0000 (00:02 +0200)
Makefile
doc/mainpage.dox
m4/usual.m4
test/Makefile
test/force_compat.sed
test/test_common.c
test/test_common.h
test/test_fnmatch.c [new file with mode: 0644]
usual/fnmatch.c [new file with mode: 0644]
usual/fnmatch.h [new file with mode: 0644]

index bb342cfbdc0b4e2129022d9e07b97c6282e6cb89..83e2993b66bc4321258b55d8e0814d3df50140b6 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -32,6 +32,7 @@ libusual_la_SOURCES = usual/config.h.in \
        usual/err.h usual/err.c \
        usual/event.h usual/event.c \
        usual/fileutil.h usual/fileutil.c \
+       usual/fnmatch.h usual/fnmatch.c \
        usual/getopt.h usual/getopt.c \
        usual/hashing/crc32.h usual/hashing/crc32.c \
        usual/hashing/lookup3.h usual/hashing/lookup3.c \
index 31ac12bae67ae39955f5b56c8e20e704b1914533..b106d6d52c17d8459d4722e47bd88bd754353c05 100644 (file)
@@ -41,6 +41,7 @@
  * <tr><td>  <usual/socket.h>        </td><td>  Socket compat and helper functions   </td></tr>
  * <tr><td>  <usual/string.h>        </td><td>  String compat and helper functions   </td></tr>
  * <tr><td>  <usual/time.h>          </td><td>  Time compat and helper functions   </td></tr>
+ * <tr><td>  <usual/fnmatch.h>       </td><td>  fnmatch compat   </td></tr>
  * <tr><th colspan=2>  Data Structures  </th></tr>
  * <tr><td>  <usual/aatree.h>        </td><td>  Binary Tree   </td></tr>
  * <tr><td>  <usual/cbtree.h>        </td><td>  Crit-Bit Tree   </td></tr>
index ada48270897218a70c07d7c0e4bca8f3251a6958..734580bfa025189b7cd5fcb5933536d8e4c9bfa1 100644 (file)
@@ -170,7 +170,7 @@ AC_CHECK_HEADERS([arpa/inet.h netinet/in.h netinet/tcp.h])
 AC_CHECK_HEADERS([sys/param.h sys/uio.h pwd.h grp.h])
 AC_CHECK_HEADERS([sys/wait.h sys/mman.h syslog.h netdb.h dlfcn.h])
 AC_CHECK_HEADERS([err.h pthread.h endian.h sys/endian.h byteswap.h])
-AC_CHECK_HEADERS([malloc.h regex.h getopt.h])
+AC_CHECK_HEADERS([malloc.h regex.h getopt.h fnmatch.h])
 dnl ucred.h may have prereqs
 AC_CHECK_HEADERS([ucred.h sys/ucred.h], [], [], [
 #ifdef HAVE_SYS_TYPES_H
@@ -231,6 +231,7 @@ AC_CHECK_FUNCS(err errx warn warnx getprogname setprogname)
 AC_CHECK_FUNCS(posix_memalign memalign valloc)
 AC_CHECK_FUNCS(getopt getopt_long getopt_long_only)
 AC_CHECK_FUNCS(fls flsl flsll ffs ffsl ffsll)
+AC_CHECK_FUNCS(fnmatch)
 ### Functions provided only on win32
 AC_CHECK_FUNCS(localtime_r gettimeofday recvmsg sendmsg usleep getrusage)
 ### Functions used by libusual itself
index 9f362897e1ff81a55418a0deede9feaba6c44f0f..c232ec75b8cc60345ce682d3180fd26e227440cc 100644 (file)
@@ -13,7 +13,7 @@ regtest_system_SOURCES = \
        test_cxalloc.c test_bits.c test_base.c test_netdb.c \
        test_cfparser.c test_endian.c test_hashtab.c test_mdict.c \
        test_shlist.c test_time.c test_hashing.c test_fileutil.c \
-       test_socket.c test_getopt.c test_ctype.c \
+       test_socket.c test_getopt.c test_ctype.c test_fnmatch.c \
        test_common.h tinytest.h tinytest_macros.h
 
 regtest_compat_SOURCES := $(regtest_system_SOURCES)
index 6ae33135adf06a5e780a8179be5f37c0d7d44fcd..6b438491f86ab3ae79a0230e432aaffbfdb9d996 100644 (file)
@@ -10,3 +10,4 @@
 /INET_PTON/s,^,//,
 /GETOPT/s,^,//,
 /CTYPE_ON_CHAR/s,^,//,
+/FNMATCH/s,^,//,
index bfbbd94785c332d2cfed585c3646ce11db5397e1..5c407cce7ef965f1f3dbc1ed7dea059aeb8a7851 100644 (file)
@@ -12,6 +12,7 @@ struct testgroup_t groups[] = {
        { "hashing/", hashing_tests },
        { "endian/", endian_tests },
        { "string/", string_tests },
+       { "fnmatch/", fnmatch_tests },
        { "ctype/", ctype_tests },
        { "heap/", heap_tests },
        { "hashtab/", hashtab_tests },
index 2c5ff45885c95f01ee35d176cb4c81d6a87fd7cf..375fa7134c5f84c42990978ab5fe6c0b542cac0a 100644 (file)
@@ -33,3 +33,4 @@ extern struct testcase_t fileutil_tests[];
 extern struct testcase_t socket_tests[];
 extern struct testcase_t getopt_tests[];
 extern struct testcase_t ctype_tests[];
+extern struct testcase_t fnmatch_tests[];
diff --git a/test/test_fnmatch.c b/test/test_fnmatch.c
new file mode 100644 (file)
index 0000000..25d26dc
--- /dev/null
@@ -0,0 +1,215 @@
+
+#include <usual/fnmatch.h>
+
+#include <usual/string.h>
+#include "test_common.h"
+
+/*
+ * POSIX syntax.
+ */
+
+static void test_fnmatch_posix(void *p)
+{
+       /* literal */
+       int_check(0, fnmatch("", "", 0));
+       int_check(0, fnmatch("a", "a", 0));
+       int_check(0, fnmatch("abc", "abc", 0));
+       int_check(1, fnmatch("", "b", 0));
+       int_check(1, fnmatch("a", "", 0));
+       int_check(1, fnmatch("a", "b", 0));
+
+       /* single wildcard */
+       int_check(0, fnmatch("a?", "ax", 0));
+       int_check(0, fnmatch("??", "ax", 0));
+       int_check(1, fnmatch("?", "ax", 0));
+       int_check(1, fnmatch("???", "ax", 0));
+
+       /* wildcard */
+       int_check(0, fnmatch("a*", "ax", 0));
+       int_check(0, fnmatch("*", "", 0));
+       int_check(0, fnmatch("*", "qwe", 0));
+       int_check(0, fnmatch("ab*ab", "abxxab", 0));
+       int_check(1, fnmatch("ab*ab", "abxxabc", 0));
+
+       /* wildcard+ */
+       int_check(0, fnmatch("ab*ab*", "abxxabc", 0));
+       int_check(0, fnmatch("ab*ab*c", "abxxabc", 0));
+       int_check(0, fnmatch("ab*ab*c", "abxxabxc", 0));
+       int_check(0, fnmatch("??*??", "abxxab", 0));
+       int_check(0, fnmatch("*??", "abxxab", 0));
+       int_check(0, fnmatch("??*", "abxxab", 0));
+       int_check(0, fnmatch("a**c", "abc", 0));
+
+       /* classes */
+       int_check(0, fnmatch("[abc]", "b", 0));
+       int_check(1, fnmatch("[abc]", "x", 0));
+       int_check(0, fnmatch("[a-c]", "b", 0));
+       int_check(1, fnmatch("[a-c]", "x", 0));
+       int_check(0, fnmatch("[b-b]", "b", 0));
+       int_check(1, fnmatch("[!abc]", "b", 0));
+       int_check(1, fnmatch("[!a-c]", "b", 0));
+       int_check(0, fnmatch("[!a-c]", "x", 0));
+       int_check(0, fnmatch("[*?[][*?[][*?[]", "*?[", 0));
+       int_check(0, fnmatch("[[:alpha:]][![:alpha:]]", "a9", 0));
+       int_check(0, fnmatch("[[:alnum:]][![:alnum:]]", "9-", 0));
+       int_check(0, fnmatch("[[:blank:]][![:blank:]]", " -", 0));
+       int_check(0, fnmatch("[[:cntrl:]][![:cntrl:]]", "\tx", 0));
+       int_check(0, fnmatch("[[:digit:]][![:digit:]]", "9a", 0));
+       int_check(0, fnmatch("[[:graph:]][![:graph:]]", "a\t", 0));
+       int_check(0, fnmatch("[[:lower:]][![:lower:]]", "aA", 0));
+       int_check(0, fnmatch("[[:print:]][![:print:]]", "a\t", 0));
+       int_check(0, fnmatch("[[:punct:]][![:punct:]]", ".x", 0));
+       int_check(0, fnmatch("[[:space:]][![:space:]]", " x", 0));
+       int_check(0, fnmatch("[[:upper:]][![:upper:]]", "Ff", 0));
+       int_check(0, fnmatch("[[:xdigit:]][![:xdigit:]]", "Fx", 0));
+       int_check(0, fnmatch("[", "[", 0));
+       int_check(0, fnmatch("[f", "[f", 0));
+
+       /* escaping */
+       int_check(1, fnmatch("\\a\\?", "ax", 0));
+       int_check(0, fnmatch("\\a\\?", "a?", 0));
+       int_check(1, fnmatch("\\a\\*", "ax", 0));
+       int_check(0, fnmatch("\\a\\*", "a*", 0));
+       int_check(0, fnmatch("\\[a]", "[a]", 0));
+       int_check(0, fnmatch("\\\\", "\\", 0));
+       int_check(0, fnmatch("\\$\\'\\\"\\<\\>", "$'\"<>", 0));
+       int_check(1, fnmatch("a\\", "a", 0));
+       int_check(1, fnmatch("a\\", "a\\", 0));
+       int_check(0, fnmatch("a\\", "a\\", FNM_NOESCAPE));
+       int_check(0, fnmatch("\\[a]", "\\a", FNM_NOESCAPE));
+       int_check(0, fnmatch("\\*b", "\\aab", FNM_NOESCAPE));
+
+       /* FNM_PATHNAME */
+       int_check(0, fnmatch("ab*c", "ab/c", 0));
+       int_check(1, fnmatch("ab*c", "ab/c", FNM_PATHNAME));
+       int_check(1, fnmatch("ab?c", "ab/c", FNM_PATHNAME));
+       int_check(1, fnmatch("ab[/]c", "ab/c", FNM_PATHNAME));
+       int_check(0, fnmatch("/*/", "//", FNM_PATHNAME));
+       int_check(1, fnmatch("a[b/c]d", "a/d", FNM_PATHNAME));
+       int_check(0, fnmatch("abd", "abd", FNM_PATHNAME));
+       int_check(1, fnmatch("a[b/c]d", "a[b/c]d", FNM_PATHNAME));
+
+       /* FNM_PERIOD */
+       int_check(0, fnmatch(".foo", ".foo", 0));
+       int_check(0, fnmatch("?foo", ".foo", 0));
+       int_check(0, fnmatch("[.]foo", ".foo", 0));
+       int_check(0, fnmatch("[!abc]foo", ".foo", 0));
+       int_check(0, fnmatch("*foo", ".foo", 0));
+       int_check(0, fnmatch(".foo", ".foo", FNM_PERIOD));
+       int_check(1, fnmatch("*foo", ".foo", FNM_PERIOD));
+       int_check(1, fnmatch("?foo", ".foo", FNM_PERIOD));
+       int_check(0, fnmatch("*/?foo", "sub/.foo", FNM_PERIOD));
+       int_check(1, fnmatch("*.foo", ".foo", FNM_PERIOD));
+       int_check(1, fnmatch("[.]foo", ".foo", FNM_PERIOD));
+
+       /* FNM_PATHNAME | FNM_PERIOD */
+       int_check(1, fnmatch("*/?foo", "sub/.foo", FNM_PERIOD|FNM_PATHNAME));
+       int_check(1, fnmatch("*/[.]foo", "sub/.foo", FNM_PERIOD|FNM_PATHNAME));
+       int_check(1, fnmatch("*/*.c", "sub/.foo.c", FNM_PERIOD|FNM_PATHNAME));
+       int_check(1, fnmatch("*/*", "sub/.foo.c", FNM_PERIOD|FNM_PATHNAME));
+       int_check(0, fnmatch("*/*.c", "sub/foo..c", FNM_PERIOD|FNM_PATHNAME));
+       int_check(1, fnmatch("*/*.foo", "sub/.foo", FNM_PERIOD|FNM_PATHNAME));
+
+       /* escapes in brackets ~ posix */
+       int_check(0, fnmatch("[A\\]]", "\\]", FNM_NOESCAPE));
+       int_check(0, fnmatch("[a\\-x]", "_", FNM_NOESCAPE));
+end:;
+}
+
+/*
+ * GNU syntax.
+ */
+
+static void test_fnmatch_gnu(void *p)
+{
+       /* FNM_CASEFOLD */
+       int_check(1, fnmatch("aaAA", "AaAa", 0));
+       int_check(1, fnmatch("[b][b][B][B][a-c][A-C][a-c][A-C]", "bBbBbbBB", 0));
+       int_check(0, fnmatch("aaAA", "AaAa", FNM_CASEFOLD));
+       int_check(0, fnmatch("[b][b][B][B][a-c][A-C][a-c][A-C]", "bBbBbbBB", FNM_CASEFOLD));
+
+       /* FNM_LEADING_DIR */
+       int_check(0, fnmatch("a", "a", FNM_LEADING_DIR|FNM_PATHNAME));
+       int_check(0, fnmatch("a", "a/b", FNM_LEADING_DIR|FNM_PATHNAME));
+       int_check(0, fnmatch("a/b", "a/b/c/d", FNM_LEADING_DIR|FNM_PATHNAME));
+       int_check(0, fnmatch("a/*/*", "a/b/c/d", FNM_LEADING_DIR|FNM_PATHNAME));
+       int_check(0, fnmatch("*", "/a", FNM_LEADING_DIR|FNM_PATHNAME));
+       /* seems wrong to allow it */
+       int_check(0, fnmatch("a", "a/b", FNM_LEADING_DIR));
+
+       /* escapes in brackets ~ gnu */
+       int_check(0, fnmatch("[A\\]][A\\]]", "]A", 0));
+       int_check(1, fnmatch("[a\\-x]", "_", 0));
+       int_check(0, fnmatch("[\\!x]", "!", 0));
+       int_check(1, fnmatch("[\\!x]", "\\", 0));
+       int_check(0, fnmatch("[\\[:alnum:]", ":", 0));
+end:;
+}
+
+/*
+ * DoS possibilities.
+ */
+
+static void test_fnmatch_weird(void *p)
+{
+       char pat[4096];
+       char str[4096];
+       int i;
+
+       memset(pat, 0, sizeof(pat));
+       memset(str, 0, sizeof(str));
+
+       memset(pat, '*', 1500);
+       memset(str, 'a', 1500);
+       int_check(0, fnmatch(pat, str, 0));
+
+       pat[10] = 'a';
+       pat[1200] = 'b';
+       int_check(0, fnmatch(pat, "ab", 0));
+
+       for (i = 0; i < 1200; i++) {
+               char c = 'a' + (i%26);
+               pat[i*2] = c;
+               pat[i*2+1] = '*';
+               str[i*2] = c;
+               str[i*2+1] = c;
+       }
+       pat[i*2] = 0;
+       str[i*2] = 0;
+       int_check(0, fnmatch(pat, str, 0));
+
+       for (i = 0; i < 2000; i++) {
+               pat[i*2] = '*';
+               pat[i*2+1] = '?';
+               str[i*2] = 'a';
+               str[i*2+1] = 'b';
+       }
+       str[i*2] = 0;
+       pat[i*2] = 0;
+       int_check(0, fnmatch(pat, str, 0));
+       pat[i*2] = 'a';
+       pat[i*2 + 1] = 0;
+       int_check(1, fnmatch(pat, str, 0));
+       pat[i*2] = 'b';
+       int_check(0, fnmatch(pat, str, 0));
+       pat[i*2] = '*';
+       pat[3] = 'x';
+       str[2000] = 'x';
+       int_check(0, fnmatch(pat, str, 0));
+
+       memset(pat, '?', sizeof(pat));
+       memset(str, 'x', sizeof(str));
+       str[4000] = 0;
+       pat[2000] = 0;
+       pat[0] = '*';
+       int_check(0, fnmatch(pat, str, 0));
+end:;
+}
+
+struct testcase_t fnmatch_tests[] = {
+       { "posix", test_fnmatch_posix },
+       { "gnu", test_fnmatch_gnu },
+       { "weird", test_fnmatch_weird },
+       END_OF_TESTCASES
+};
+
diff --git a/usual/fnmatch.c b/usual/fnmatch.c
new file mode 100644 (file)
index 0000000..8f11430
--- /dev/null
@@ -0,0 +1,267 @@
+/*
+ * fnmatch.c
+ *
+ * Copyright (c) 2012  Marko Kreen
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * Differences from POSIX:
+ * - ^ can be used in place of !
+ * - \ is escape in bracket expression, unless FNM_NOESCAPE is given.
+ * - FNM_CASEFOLD
+ * - FNM_LEADING_DIR
+ */
+
+#include <usual/fnmatch.h>
+#include <usual/wchar.h>
+
+#include <string.h>
+
+#ifdef NEED_USUAL_FNMATCH
+
+/* compare chars with case folding */
+static inline bool cmp_fold(wchar_t c1, wchar_t c2, int flags)
+{
+       if (c1 == c2)
+               return true;
+       if (flags & FNM_CASEFOLD) {
+               if (iswupper(c1) && iswlower(c2))
+                       return c1 == (wchar_t)towupper(c2);
+               else if (iswlower(c1) && iswupper(c2))
+                       return c1 == (wchar_t)towlower(c2);
+       }
+       return false;
+}
+
+/* compare char to range with case folding */
+static inline bool range_fold(wchar_t c, wchar_t r1, wchar_t r2, int flags)
+{
+       if (c >= r1 && c <= r2)
+               return true;
+       if (flags & FNM_CASEFOLD) {
+               /* convert only if it makes sense */
+               if (iswupper(c) && iswlower(r1) && iswlower(r2)) {
+                       c = towlower(c);
+                       if (c >= r1 && c <= r2)
+                               return true;
+               } else if (iswlower(c) && iswupper(r1) && iswupper(r2)) {
+                       c = towupper(c);
+                       if (c >= r1 && c <= r2)
+                               return true;
+               }
+       }
+       return false;
+}
+
+/* match bracket expression */
+static const wchar_t *match_class(const wchar_t *pat, wchar_t c, int flags)
+{
+       const wchar_t *p = pat;
+       const wchar_t *start;
+       bool neg = false;
+       bool match = false;
+       bool fallback_ok = true;
+       const wchar_t *n1, *n2;
+       wctype_t wct;
+
+       /* negation */
+       if (*p == '!' || *p == '^') {
+               neg = true;
+               p++;
+       }
+       start = p;
+loop:
+       /* named class, equivalence class or collating symbol */
+       if (p[0] == '[' && (p[1] == ':' || p[1] == '.' || p[1] == '=')) {
+               n1 = p + 2;
+               n2 = wcschr(n1, p[1]);
+               if (!n2 || n2[1] != ']')
+                       goto parse_fail;
+               if (p[1] != ':')
+                       return NULL;
+               p = n2 + 2;
+               wct = wctype_wcsn(n1, n2-n1);
+               if (wct == (wctype_t)0)
+                       return NULL;
+               if (iswctype(c, wct))
+                       match = true;
+               fallback_ok = false;
+               /* skip rest */
+               goto loop;
+       }
+parse_fail:
+
+       /* unexpected pattern end */
+       if (p[0] == '\0') {
+               /* only open bracket exists, take it as literal */
+               if (fallback_ok && c == '[')
+                       return pat - 1;
+               return NULL;
+       }
+
+       /* closing bracket */
+       if (p[0] == ']' && p != start)
+               return (match ^ neg) ? p : NULL;
+
+       /* escape next char */
+       if (p[0] == '\\' && !(flags & FNM_NOESCAPE)) {
+               if (p[1] == '\0')
+                       return NULL;
+               p++;
+       }
+
+       /* its either simple range or char */
+       if (p[1] == '-' && p[2] != ']' && p[2] != '\0') {
+               wchar_t r1 = p[0];
+               wchar_t r2 = p[2];
+               if (r2 == '\\' && !(flags & FNM_NOESCAPE)) {
+                       p++;
+                       r2 = p[2];
+                       if (r2 == '\0')
+                               return NULL;
+               }
+               if (range_fold(c, r1, r2, flags))
+                       match = true;
+               p += 3;
+       } else {
+               if (cmp_fold(c, p[0], flags))
+                       match = true;
+               p++;
+       }
+       goto loop;
+}
+
+/*
+ * FNM_PATHNAME disallows wildcard match for '/',
+ * FNM_PERIOD disallows wildcard match for leading '.',
+ * check for string end also.
+ */
+static bool disallow_wildcard(const wchar_t *s, const wchar_t *str, int flags)
+{
+       if (*s == '\0')
+               return true;
+       if (*s == '/')
+               return (flags & FNM_PATHNAME);
+       if (*s == '.' && (flags & FNM_PERIOD)) {
+               if (s == str)
+                       return true;
+               if (s[-1] == '/' && (flags & FNM_PATHNAME))
+                       return true;
+       }
+       return false;
+}
+
+/*
+ * Non-recursive fnmatch(), based on globmatch() by <linux@horizon.com>
+ */
+static int wfnmatch(const wchar_t *pat, const wchar_t *str, int flags)
+{
+       const wchar_t *p = pat;
+       const wchar_t *s = str;
+       const wchar_t *retry_p = NULL;
+       const wchar_t *skip_s = NULL;
+loop:
+       switch (*p) {
+       case '*':
+               /* match any number of chars from this position on */
+               retry_p = p + 1;
+               skip_s = s;
+               /* dot after '*' must not match leading dot */
+               if (p[1] == '.' && disallow_wildcard(s, str, flags))
+                       return FNM_NOMATCH;
+               break;
+       case '?':
+               /* match any char */
+               if (disallow_wildcard(s, str, flags))
+                       goto nomatch_retry;
+               s++;
+               break;
+       case '[':
+               /* match character class */
+               if (disallow_wildcard(s, str, flags))
+                       goto nomatch_retry;
+               p = match_class(p + 1, *s, flags);
+               if (p == NULL)
+                       goto nomatch_retry;
+               s++;
+               break;
+       case '\\':
+               /* escape next char */
+               if (!(flags & FNM_NOESCAPE)) {
+                       p++;
+                       if (*p == '\0')
+                               return FNM_NOMATCH;
+               }
+       default:
+               /* match single char */
+               if (*s == '/' && *p == '\0' && (flags & FNM_LEADING_DIR))
+                       return 0;
+               if (!cmp_fold(*p, *s, flags))
+                       goto nomatch_retry;
+               if (*s == '\0')
+                       return 0;
+               s++;
+       }
+       p++;
+       goto loop;
+
+nomatch_retry:
+       /* eat chars with '*', if possible */
+       if (retry_p == NULL || *s == '\0')
+               return FNM_NOMATCH;
+       s = skip_s++;
+       p = retry_p;
+       if (*s == '\0')
+               return (*p == '\0') ? 0 : FNM_NOMATCH;
+       if (disallow_wildcard(s, str, flags))
+               return FNM_NOMATCH;
+       s++;
+       goto loop;
+}
+
+/*
+ * Convert locale-specific encoding to wchar_t string
+ */
+int fnmatch(const char *pat, const char *str, int flags)
+{
+       const wchar_t *wpat, *wstr;
+       wchar_t pbuf[128];
+       wchar_t sbuf[128];
+       int plen = strlen(pat);
+       int slen = strlen(str);
+       int res;
+
+       /* convert encoding */
+       wpat = mbstr_decode(pat, plen, NULL, pbuf, sizeof(pbuf) / sizeof(wchar_t), false);
+       if (!wpat)
+               return (errno == EILSEQ) ? FNM_NOMATCH : -1;
+       wstr = mbstr_decode(str, slen, NULL, sbuf, sizeof(sbuf) / sizeof(wchar_t), true);
+       if (!wstr)
+               return -1;
+
+       /* run actual fnmatch */
+       res = wfnmatch(wpat, wstr, flags);
+
+       /* free buffers */
+       if (wstr != sbuf)
+               free(wstr);
+       if (wpat != pbuf)
+               free(wpat);
+
+       return res;
+}
+
+#endif
+
diff --git a/usual/fnmatch.h b/usual/fnmatch.h
new file mode 100644 (file)
index 0000000..7b73dc1
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * fnmatch.h
+ *
+ * Copyright (c) 2012  Marko Kreen
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/**
+ * \file
+ * Theme include for strings.
+ */
+
+#ifndef _USUAL_FNMATCH_H_
+#define _USUAL_FNMATCH_H_
+
+#include <usual/base.h>
+
+#ifdef HAVE_FNMATCH_H
+#include <fnmatch.h>
+#else
+#define NEED_USUAL_FNMATCH
+#endif
+
+#ifdef NEED_USUAL_FNMATCH
+#define fnmatch(p,s,f) usual_fnmatch(p,s,f)
+
+/** Do not allow wildcard to match '/' */
+#define FNM_PATHNAME   1
+/** Treat '\\' as literal value */
+#define FNM_NOESCAPE   2
+/** Do not allow wildcard to match leading '.' */
+#define FNM_PERIOD     4
+/** (GNU) Match case-insensitively */
+#define FNM_CASEFOLD   8
+/** (GNU) Match leading directory in path */
+#define FNM_LEADING_DIR        16
+
+/* (GNU) random alias */
+#define FNM_FILE_NAME  FNM_PATHNAME
+
+/** Returned on no match */
+#define FNM_NOMATCH    1
+
+/**
+ * Compat: fnmatch()
+ */
+int fnmatch(const char *pat, const char *str, int flags);
+
+#endif /* NEED_USUAL_FNMATCH */
+
+#endif /* !_USUAL_FNMATCH_H_ */