HelenOS sources
This source file includes following definitions.
- _coll_elm_get
- _coll_elm_char
- _coll_elm_match
- _coll_elm_between
- _get_delimited
- _class_compare
- _is_in_class
- _match_char_class
- _next_coll_elm
- _match_bracket_expr
- _partial_match
- _full_match
- _casefold
- fnmatch
- __posix_fnmatch_test
#include <stdbool.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include "internal/common.h"
#include <fnmatch.h>
#define INVALID_PATTERN -1
typedef int coll_elm_t;
#define COLL_ELM_INVALID -1
static coll_elm_t _coll_elm_get(const char *str)
{
if (str[0] == '\0' || str[1] != '\0') {
return COLL_ELM_INVALID;
}
return str[0];
}
static coll_elm_t _coll_elm_char(int c)
{
return c;
}
static int _coll_elm_match(coll_elm_t elm, const char *str)
{
return elm == *str;
}
static int _coll_elm_between(coll_elm_t first, coll_elm_t second,
const char *str)
{
return *str >= first && *str <= second;
}
static bool _get_delimited(
const char **pattern, int seq,
char *buf, size_t buf_sz, int flags)
{
const bool noescape = (flags & FNM_NOESCAPE) != 0;
const bool pathname = (flags & FNM_PATHNAME) != 0;
const char *p = *pattern;
assert(p[0] == '[' && p[1] == seq );
p += 2;
while (true) {
if (*p == seq && *(p + 1) == ']') {
*pattern = p + 2;
*buf = '\0';
return true;
}
if (!noescape && *p == '\\') {
p++;
}
if (*p == '\0') {
return false;
}
if (pathname && *p == '/') {
return false;
}
if (buf_sz > 1) {
*buf = *p;
buf++;
buf_sz--;
}
p++;
}
}
#define MAX_CLASS_OR_COLL_LEN 6
struct _char_class {
const char *name;
int (*func) (int);
};
static const struct _char_class _char_classes[] = {
{ "alnum", isalnum },
{ "alpha", isalpha },
{ "blank", isblank },
{ "cntrl", iscntrl },
{ "digit", isdigit },
{ "graph", isgraph },
{ "lower", islower },
{ "print", isprint },
{ "punct", ispunct },
{ "space", isspace },
{ "upper", isupper },
{ "xdigit", isxdigit }
};
static int _class_compare(const void *key, const void *elem)
{
const struct _char_class *class = elem;
return strcmp((const char *) key, class->name);
}
static bool _is_in_class (const char *cname, int c)
{
const struct _char_class *class = bsearch(cname, _char_classes,
sizeof(_char_classes) / sizeof(struct _char_class),
sizeof(struct _char_class), _class_compare);
if (class == NULL) {
return false;
} else {
return class->func(c);
}
}
static int _match_char_class(const char **pattern, const char *str, int flags)
{
char class[MAX_CLASS_OR_COLL_LEN + 1];
if (!_get_delimited(pattern, ':', class, sizeof(class), flags)) {
return INVALID_PATTERN;
}
return _is_in_class(class, *str);
}
static coll_elm_t _next_coll_elm(const char **pattern, int flags)
{
assert(pattern != NULL);
assert(*pattern != NULL);
assert(**pattern != '\0');
const char *p = *pattern;
const bool noescape = (flags & FNM_NOESCAPE) != 0;
const bool pathname = (flags & FNM_PATHNAME) != 0;
if (*p == '[') {
if (*(p + 1) == '.') {
char buf[MAX_CLASS_OR_COLL_LEN + 1];
if (!_get_delimited(pattern, '.', buf, sizeof(buf), flags)) {
return COLL_ELM_INVALID;
}
return _coll_elm_get(buf);
}
if (*(p + 1) == '=') {
char buf[MAX_CLASS_OR_COLL_LEN + 1];
if (!_get_delimited(pattern, '=', buf, sizeof(buf), flags)) {
return COLL_ELM_INVALID;
}
return _coll_elm_get(buf);
}
}
if (!noescape && *p == '\\') {
p++;
if (*p == '\0') {
*pattern = p;
return COLL_ELM_INVALID;
}
}
if (pathname && *p == '/') {
return COLL_ELM_INVALID;
}
*pattern = p + 1;
return _coll_elm_char(*p);
}
#define _matched(match) { \
int _match = match; \
if (_match < 0) { \
\
return _match; \
} else if (matched == 0 && _match > 0) { \
\
matched = _match; \
} \
}
static int _match_bracket_expr(const char **pattern, const char *str, int flags)
{
const bool pathname = (flags & FNM_PATHNAME) != 0;
const bool special_period = (flags & FNM_PERIOD) != 0;
const char *p = *pattern;
bool negative = false;
int matched = 0;
assert(*p == '[');
p++;
if (*str == '\0' || (pathname && *str == '/') ||
(pathname && special_period && *str == '.' && *(str - 1) == '/')) {
return 0;
}
if (*p == '^' || *p == '!') {
negative = true;
p++;
}
if (*p == ']') {
_matched(*str == ']');
p++;
}
coll_elm_t current_elm = COLL_ELM_INVALID;
while (*p != ']') {
if (*p == '-' && *(p + 1) != ']' &&
current_elm != COLL_ELM_INVALID) {
p++;
coll_elm_t end_elm = _next_coll_elm(&p, flags);
if (end_elm == COLL_ELM_INVALID) {
return INVALID_PATTERN;
}
_matched(_coll_elm_between(current_elm, end_elm, str));
continue;
}
if (*p == '[' && *(p + 1) == ':') {
current_elm = COLL_ELM_INVALID;
_matched(_match_char_class(&p, str, flags));
continue;
}
current_elm = _next_coll_elm(&p, flags);
if (current_elm == COLL_ELM_INVALID) {
return INVALID_PATTERN;
}
_matched(_coll_elm_match(current_elm, str));
}
*pattern = p + 1;
if (matched == 0) {
return negative;
} else {
return negative ? 0 : matched;
}
}
static bool _partial_match(const char **pattern, const char **string, int flags)
{
const bool pathname = (flags & FNM_PATHNAME) != 0;
const bool special_period = (flags & FNM_PERIOD) != 0;
const bool noescape = (flags & FNM_NOESCAPE) != 0;
const bool leading_dir = (flags & FNM_LEADING_DIR) != 0;
const char *s = *string;
const char *p = *pattern;
while (*p != '*') {
if (*p == '[') {
int matched = _match_bracket_expr(&p, s, flags);
if (matched == 0) {
return false;
}
if (matched != INVALID_PATTERN) {
s += matched;
continue;
}
assert(matched == INVALID_PATTERN);
}
if (*p == '?') {
if (*s == '\0') {
return false;
}
if (pathname && *s == '/') {
return false;
}
if (special_period && pathname &&
*s == '.' && *(s - 1) == '/') {
return false;
}
p++;
s++;
continue;
}
if (!noescape && *p == '\\') {
p++;
}
if (*p == '\0') {
if (*s == '\0' || (leading_dir && *s == '/')) {
break;
}
return false;
}
if (*p == *s) {
p++;
s++;
continue;
}
return false;
}
assert(*p == '\0' || *p == '*');
assert(*p != '\0' || *s == '\0' || (leading_dir && *s == '/'));
*pattern = p;
*string = s;
return true;
}
static bool _full_match(const char *pattern, const char *string, int flags)
{
const bool pathname = (flags & FNM_PATHNAME) != 0;
const bool special_period = (flags & FNM_PERIOD) != 0;
const bool leading_dir = (flags & FNM_LEADING_DIR) != 0;
if (special_period && *string == '.') {
if (*pattern != '.') {
return false;
}
pattern++;
string++;
}
if (*pattern != '*') {
if (!_partial_match(&pattern, &string, flags)) {
return false;
}
}
while (*pattern != '\0') {
assert(*pattern == '*');
pattern++;
bool matched = false;
const char *end;
if (pathname && special_period &&
*string == '.' && *(string - 1) == '/') {
end = string;
} else {
end = gnu_strchrnul(string, pathname ? '/' : '\0');
}
while (string <= end) {
if (_partial_match(&pattern, &string, flags)) {
matched = true;
break;
}
string++;
}
if (matched) {
continue;
}
return false;
}
return *string == '\0' || (leading_dir && *string == '/');
}
static char *_casefold(const char *s)
{
assert(s != NULL);
char *result = strdup(s);
for (char *i = result; *i != '\0'; ++i) {
*i = tolower(*i);
}
return result;
}
int fnmatch(const char *pattern, const char *string, int flags)
{
assert(pattern != NULL);
assert(string != NULL);
if ((flags & FNM_CASEFOLD) != 0) {
pattern = _casefold(pattern);
string = _casefold(string);
}
bool result = _full_match(pattern, string, flags);
if ((flags & FNM_CASEFOLD) != 0) {
if (pattern) {
free((char *) pattern);
}
if (string) {
free((char *) string);
}
}
return result ? 0 : FNM_NOMATCH;
}
#if 0
#include <stdio.h>
#define fnmatch_test(x) { if (x) printf("SUCCESS: "#x"\n"); else { printf("FAILED: "#x"\n"); fail++; } }
#define match(s1, s2, flags) fnmatch_test(fnmatch(s1, s2, flags) == 0)
#define nomatch(s1, s2, flags) fnmatch_test(fnmatch(s1, s2, flags) == FNM_NOMATCH)
void __posix_fnmatch_test()
{
int fail = 0;
static_assert(FNM_PATHNAME == FNM_FILE_NAME);
match("", "", 0);
match("*", "hello", 0);
match("hello", "hello", 0);
match("hello*", "hello", 0);
nomatch("hello?", "hello", 0);
match("*hello", "prdel hello", 0);
match("he[sl]lo", "hello", 0);
match("he[sl]lo", "heslo", 0);
nomatch("he[sl]lo", "heblo", 0);
nomatch("he[^sl]lo", "hello", 0);
nomatch("he[^sl]lo", "heslo", 0);
match("he[^sl]lo", "heblo", 0);
nomatch("he[!sl]lo", "hello", 0);
nomatch("he[!sl]lo", "heslo", 0);
match("he[!sl]lo", "heblo", 0);
match("al*[c-t]a*vis*ta", "alheimer talir jehovista", 0);
match("al*[c-t]a*vis*ta", "alfons had jehovista", 0);
match("[a-ce-z]", "a", 0);
match("[a-ce-z]", "c", 0);
nomatch("[a-ce-z]", "d", 0);
match("[a-ce-z]", "e", 0);
match("[a-ce-z]", "z", 0);
nomatch("[^a-ce-z]", "a", 0);
nomatch("[^a-ce-z]", "c", 0);
match("[^a-ce-z]", "d", 0);
nomatch("[^a-ce-z]", "e", 0);
nomatch("[^a-ce-z]", "z", 0);
match("helen??", "helenos", 0);
match("****booo****", "booo", 0);
match("hello[[:space:]]world", "hello world", 0);
nomatch("hello[[:alpha:]]world", "hello world", 0);
match("/hoooo*", "/hooooooo/hooo", 0);
nomatch("/hoooo*", "/hooooooo/hooo", FNM_PATHNAME);
nomatch("/hoooo*/", "/hooooooo/hooo", FNM_PATHNAME);
match("/hoooo*/*", "/hooooooo/hooo", FNM_PATHNAME);
match("/hoooo*/hooo", "/hooooooo/hooo", FNM_PATHNAME);
match("/hoooo*", "/hooooooo/hooo", FNM_PATHNAME | FNM_LEADING_DIR);
nomatch("/hoooo*/", "/hooooooo/hooo", FNM_PATHNAME | FNM_LEADING_DIR);
nomatch("/hoooo", "/hooooooo/hooo", FNM_LEADING_DIR);
match("/hooooooo", "/hooooooo/hooo", FNM_LEADING_DIR);
match("*", "hell", 0);
match("*?", "hell", 0);
match("?*?", "hell", 0);
match("?*??", "hell", 0);
match("??*??", "hell", 0);
nomatch("???*??", "hell", 0);
nomatch("", "hell", 0);
nomatch("?", "hell", 0);
nomatch("??", "hell", 0);
nomatch("???", "hell", 0);
match("????", "hell", 0);
match("*", "h.ello", FNM_PERIOD);
match("*", "h.ello", FNM_PATHNAME | FNM_PERIOD);
nomatch("*", ".hello", FNM_PERIOD);
match("h?ello", "h.ello", FNM_PERIOD);
nomatch("?hello", ".hello", FNM_PERIOD);
match("/home/user/.*", "/home/user/.hello", FNM_PATHNAME | FNM_PERIOD);
match("/home/user/*", "/home/user/.hello", FNM_PERIOD);
nomatch("/home/user/*", "/home/user/.hello", FNM_PATHNAME | FNM_PERIOD);
nomatch("HeLlO", "hello", 0);
match("HeLlO", "hello", FNM_CASEFOLD);
printf("Failed: %d\n", fail);
}
#endif
HelenOS homepage, sources at GitHub