// SPDX-License-Identifier: GPL-2.0-only//// Traverse the source tree, parsing all .gitignore files, and print file paths// that are ignored by git.// The output is suitable to the --exclude-from option of tar.// This is useful until the --exclude-vcs-ignores option gets working correctly.//// Copyright (C) 2023 Masahiro Yamada <masahiroy@kernel.org>// (a lot of code imported from GIT) #include <assert.h>#include <dirent.h>#include <errno.h>#include <fcntl.h>#include <getopt.h>#include <stdarg.h>#include <stdbool.h>#include <stdio.h>#include <stdlib.h>#include <string.h>#include <sys/stat.h>#include <sys/types.h>#include <unistd.h> // Imported from commit 23c56f7bd5f1667f8b793d796bf30e39545920f6 in GIT////---------------------------(IMPORT FROM GIT BEGIN)--------------------------- // Copied from environment.c static bool ignore_case; // Copied from git-compat-util.h /* Sane ctype - no locale, and works with signed chars */#undef isascii#undef isspace#undef isdigit#undef isalpha#undef isalnum#undef isprint#undef islower#undef isupper#undef tolower#undef toupper#undef iscntrl#undef ispunct#undef isxdigit static const unsigned char sane_ctype[256];#define GIT_SPACE 0x01#define GIT_DIGIT 0x02#define GIT_ALPHA 0x04#define GIT_GLOB_SPECIAL 0x08#define GIT_REGEX_SPECIAL 0x10#define GIT_PATHSPEC_MAGIC 0x20#define GIT_CNTRL 0x40#define GIT_PUNCT 0x80#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)#define isascii(x) (((x) & ~0x7f) == 0)#define isspace(x) sane_istest(x,GIT_SPACE)#define isdigit(x) sane_istest(x,GIT_DIGIT)#define isalpha(x) sane_istest(x,GIT_ALPHA)#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)#define isprint(x) ((x) >= 0x20 && (x) <= 0x7e)#define islower(x) sane_iscase(x, 1)#define isupper(x) sane_iscase(x, 0)#define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL)#define iscntrl(x) (sane_istest(x,GIT_CNTRL))#define ispunct(x) sane_istest(x, GIT_PUNCT | GIT_REGEX_SPECIAL | \ GIT_GLOB_SPECIAL | GIT_PATHSPEC_MAGIC)#define isxdigit(x) (hexval_table[(unsigned char)(x)] != -1)#define tolower(x) sane_case((unsigned char)(x), 0x20)#define toupper(x) sane_case((unsigned char)(x), 0) static inline int sane_case(int x, int high){ if (sane_istest(x, GIT_ALPHA)) x = (x & ~0x20) | high; return x;} static inline int sane_iscase(int x, int is_lower){ if (!sane_istest(x, GIT_ALPHA)) return 0; if (is_lower) return (x & 0x20) != 0; else return (x & 0x20) == 0;} // Copied from ctype.c enum { S = GIT_SPACE, A = GIT_ALPHA, D = GIT_DIGIT, G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ X = GIT_CNTRL, U = GIT_PUNCT, Z = GIT_CNTRL | GIT_SPACE}; static const unsigned char sane_ctype[256] = { X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ /* Nothing in the 128.. range */}; // Copied from hex.c static const signed char hexval_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */}; // Copied from wildmatch.h #define WM_CASEFOLD 1#define WM_PATHNAME 2 #define WM_NOMATCH 1#define WM_MATCH 0#define WM_ABORT_ALL -1#define WM_ABORT_TO_STARSTAR -2 // Copied from wildmatch.c typedef unsigned char uchar; // local modification: remove NEGATE_CLASS(2) #define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ && *(class) == *(litmatch) \ && strncmp((char*)class, litmatch, len) == 0) // local modification: simpilify macros#define ISBLANK(c) ((c) == ' ' || (c) == '\t')#define ISGRAPH(c) (isprint(c) && !isspace(c))#define ISPRINT(c) isprint(c)#define ISDIGIT(c) isdigit(c)#define ISALNUM(c) isalnum(c)#define ISALPHA(c) isalpha(c)#define ISCNTRL(c) iscntrl(c)#define ISLOWER(c) islower(c)#define ISPUNCT(c) ispunct(c)#define ISSPACE(c) isspace(c)#define ISUPPER(c) isupper(c)#define ISXDIGIT(c) isxdigit(c) /* Match pattern "p" against "text" */static int dowild(const uchar *p, const uchar *text, unsigned int flags){ uchar p_ch; const uchar *pattern = p; for ( ; (p_ch = *p) != '\0'; text++, p++) { int matched, match_slash, negated; uchar t_ch, prev_ch; if ((t_ch = *text) == '\0' && p_ch != '*') return WM_ABORT_ALL; if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) t_ch = tolower(t_ch); if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) p_ch = tolower(p_ch); switch (p_ch) { case '\\': /* Literal match with following character. Note that the test * in "default" handles the p[1] == '\0' failure case. */ p_ch = *++p; /* FALLTHROUGH */ default: if (t_ch != p_ch) return WM_NOMATCH; continue; case '?': /* Match anything but '/'. */ if ((flags & WM_PATHNAME) && t_ch == '/') return WM_NOMATCH; continue; case '*': if (*++p == '*') { const uchar *prev_p = p - 2; while (*++p == '*') {} if (!(flags & WM_PATHNAME)) /* without WM_PATHNAME, '*' == '**' */ match_slash = 1; else if ((prev_p < pattern || *prev_p == '/') && (*p == '\0' || *p == '/' || (p[0] == '\\' && p[1] == '/'))) { /* * Assuming we already match 'foo/' and are at * <star star slash>, just assume it matches * nothing and go ahead match the rest of the * pattern with the remaining string. This * helps make foo/<*><*>/bar (<> because * otherwise it breaks C comment syntax) match * both foo/bar and foo/a/bar. */ if (p[0] == '/' && dowild(p + 1, text, flags) == WM_MATCH) return WM_MATCH; match_slash = 1; } else /* WM_PATHNAME is set */ match_slash = 0; } else /* without WM_PATHNAME, '*' == '**' */ match_slash = flags & WM_PATHNAME ? 0 : 1; if (*p == '\0') { /* Trailing "**" matches everything. Trailing "*" matches * only if there are no more slash characters. */ if (!match_slash) { if (strchr((char *)text, '/')) return WM_NOMATCH; } return WM_MATCH; } else if (!match_slash && *p == '/') { /* * _one_ asterisk followed by a slash * with WM_PATHNAME matches the next * directory */ const char *slash = strchr((char*)text, '/'); if (!slash) return WM_NOMATCH; text = (const uchar*)slash; /* the slash is consumed by the top-level for loop */ break; } while (1) { if (t_ch == '\0') break; /* * Try to advance faster when an asterisk is * followed by a literal. We know in this case * that the string before the literal * must belong to "*". * If match_slash is false, do not look past * the first slash as it cannot belong to '*'. */ if (!is_glob_special(*p)) { p_ch = *p; if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) p_ch = tolower(p_ch); while ((t_ch = *text) != '\0' && (match_slash || t_ch != '/')) { if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) t_ch = tolower(t_ch); if (t_ch == p_ch) break; text++; } if (t_ch != p_ch) return WM_NOMATCH; } if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { if (!match_slash || matched != WM_ABORT_TO_STARSTAR) return matched; } else if (!match_slash && t_ch == '/') return WM_ABORT_TO_STARSTAR; t_ch = *++text; } return WM_ABORT_ALL; case '[': p_ch = *++p; if (p_ch == '^') p_ch = '!'; /* Assign literal 1/0 because of "matched" comparison. */ negated = p_ch == '!' ? 1 : 0; if (negated) { /* Inverted character class. */ p_ch = *++p; } prev_ch = 0; matched = 0; do { if (!p_ch) return WM_ABORT_ALL; if (p_ch == '\\') { p_ch = *++p; if (!p_ch) return WM_ABORT_ALL; if (t_ch == p_ch) matched = 1; } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { p_ch = *++p; if (p_ch == '\\') { p_ch = *++p; if (!p_ch) return WM_ABORT_ALL; } if (t_ch <= p_ch && t_ch >= prev_ch) matched = 1; else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { uchar t_ch_upper = toupper(t_ch); if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) matched = 1; } p_ch = 0; /* This makes "prev_ch" get set to 0. */ } else if (p_ch == '[' && p[1] == ':') { const uchar *s; int i; for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ if (!p_ch) return WM_ABORT_ALL; i = p - s - 1; if (i < 0 || p[-1] != ':') { /* Didn't find ":]", so treat like a normal set. */ p = s - 2; p_ch = '['; if (t_ch == p_ch) matched = 1; continue; } if (CC_EQ(s,i, "alnum")) { if (ISALNUM(t_ch)) matched = 1; } else if (CC_EQ(s,i, "alpha")) { if (ISALPHA(t_ch)) matched = 1; } else if (CC_EQ(s,i, "blank")) { if (ISBLANK(t_ch)) matched = 1; } else if (CC_EQ(s,i, "cntrl")) { if (ISCNTRL(t_ch)) matched = 1; } else if (CC_EQ(s,i, "digit")) { if (ISDIGIT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "graph")) { if (ISGRAPH(t_ch)) matched = 1; } else if (CC_EQ(s,i, "lower")) { if (ISLOWER(t_ch)) matched = 1; } else if (CC_EQ(s,i, "print")) { if (ISPRINT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "punct")) { if (ISPUNCT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "space")) { if (ISSPACE(t_ch)) matched = 1; } else if (CC_EQ(s,i, "upper")) { if (ISUPPER(t_ch)) matched = 1; else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) matched = 1; } else if (CC_EQ(s,i, "xdigit")) { if (ISXDIGIT(t_ch)) matched = 1; } else /* malformed [:class:] string */ return WM_ABORT_ALL; p_ch = 0; /* This makes "prev_ch" get set to 0. */ } else if (t_ch == p_ch) matched = 1; } while (prev_ch = p_ch, (p_ch = *++p) != ']'); if (matched == negated || ((flags & WM_PATHNAME) && t_ch == '/')) return WM_NOMATCH; continue; } } return *text ? WM_NOMATCH : WM_MATCH;} /* Match the "pattern" against the "text" string. */static int wildmatch(const char *pattern, const char *text, unsigned int flags){ // local modification: move WM_CASEFOLD here if (ignore_case) flags |= WM_CASEFOLD; return dowild((const uchar*)pattern, (const uchar*)text, flags);} // Copied from dir.h #define PATTERN_FLAG_NODIR 1#define PATTERN_FLAG_ENDSWITH 4#define PATTERN_FLAG_MUSTBEDIR 8#define PATTERN_FLAG_NEGATIVE 16 // Copied from dir.c static int fspathncmp(const char *a, const char *b, size_t count){ return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count);} static int simple_length(const char *match){ int len = -1; for (;;) { unsigned char c = *match++; len++; if (c == '\0' || is_glob_special(c)) return len; }} static int no_wildcard(const char *string){ return string[simple_length(string)] == '\0';} static void parse_path_pattern(const char **pattern, int *patternlen, unsigned *flags, int *nowildcardlen){ const char *p = *pattern; size_t i, len; *flags = 0; if (*p == '!') { *flags |= PATTERN_FLAG_NEGATIVE; p++; } len = strlen(p); if (len && p[len - 1] == '/') { len--; *flags |= PATTERN_FLAG_MUSTBEDIR; } for (i = 0; i < len; i++) { if (p[i] == '/') break; } if (i == len) *flags |= PATTERN_FLAG_NODIR; *nowildcardlen = simple_length(p); /* * we should have excluded the trailing slash from 'p' too, * but that's one more allocation. Instead just make sure * nowildcardlen does not exceed real patternlen */ if (*nowildcardlen > len) *nowildcardlen = len; if (*p == '*' && no_wildcard(p + 1)) *flags |= PATTERN_FLAG_ENDSWITH; *pattern = p; *patternlen = len;} static void trim_trailing_spaces(char *buf){ char *p, *last_space = NULL; for (p = buf; *p; p++) switch (*p) { case ' ': if (!last_space) last_space = p; break; case '\\': p++; if (!*p)Lines 1–500 of 1057. Load more
// SPDX-License-Identifier: GPL-2.0-only // // Traverse the source tree, parsing all .gitignore files, and print file paths // that are ignored by git. // The output is suitable to the --exclude-from option of tar. // This is useful until the --exclude-vcs-ignores option gets working correctly. // // Copyright (C) 2023 Masahiro Yamada <masahiroy@kernel.org> // (a lot of code imported from GIT) #include <assert.h> #include <dirent.h> #include <errno.h> #include <fcntl.h> #include <getopt.h> #include <stdarg.h> #include <stdbool.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/stat.h> #include <sys/types.h> #include <unistd.h> // Imported from commit 23c56f7bd5f1667f8b793d796bf30e39545920f6 in GIT // //---------------------------(IMPORT FROM GIT BEGIN)--------------------------- // Copied from environment.c static bool ignore_case; // Copied from git-compat-util.h /* Sane ctype - no locale, and works with signed chars */ #undef isascii #undef isspace #undef isdigit #undef isalpha #undef isalnum #undef isprint #undef islower #undef isupper #undef tolower #undef toupper #undef iscntrl #undef ispunct #undef isxdigit static const unsigned char sane_ctype[256]; #define GIT_SPACE 0x01 #define GIT_DIGIT 0x02 #define GIT_ALPHA 0x04 #define GIT_GLOB_SPECIAL 0x08 #define GIT_REGEX_SPECIAL 0x10 #define GIT_PATHSPEC_MAGIC 0x20 #define GIT_CNTRL 0x40 #define GIT_PUNCT 0x80 #define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0) #define isascii(x) (((x) & ~0x7f) == 0) #define isspace(x) sane_istest(x,GIT_SPACE) #define isdigit(x) sane_istest(x,GIT_DIGIT) #define isalpha(x) sane_istest(x,GIT_ALPHA) #define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT) #define isprint(x) ((x) >= 0x20 && (x) <= 0x7e) #define islower(x) sane_iscase(x, 1) #define isupper(x) sane_iscase(x, 0) #define is_glob_special(x) sane_istest(x,GIT_GLOB_SPECIAL) #define iscntrl(x) (sane_istest(x,GIT_CNTRL)) #define ispunct(x) sane_istest(x, GIT_PUNCT | GIT_REGEX_SPECIAL | \ GIT_GLOB_SPECIAL | GIT_PATHSPEC_MAGIC) #define isxdigit(x) (hexval_table[(unsigned char)(x)] != -1) #define tolower(x) sane_case((unsigned char)(x), 0x20) #define toupper(x) sane_case((unsigned char)(x), 0) static inline int sane_case(int x, int high) { if (sane_istest(x, GIT_ALPHA)) x = (x & ~0x20) | high; return x; } static inline int sane_iscase(int x, int is_lower) { if (!sane_istest(x, GIT_ALPHA)) return 0; if (is_lower) return (x & 0x20) != 0; else return (x & 0x20) == 0; } // Copied from ctype.c enum { S = GIT_SPACE, A = GIT_ALPHA, D = GIT_DIGIT, G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */ R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | */ P = GIT_PATHSPEC_MAGIC, /* other non-alnum, except for ] and } */ X = GIT_CNTRL, U = GIT_PUNCT, Z = GIT_CNTRL | GIT_SPACE }; static const unsigned char sane_ctype[256] = { X, X, X, X, X, X, X, X, X, Z, Z, X, X, Z, X, X, /* 0.. 15 */ X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, X, /* 16.. 31 */ S, P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */ A, A, A, A, A, A, A, A, A, A, A, G, G, U, R, P, /* 80.. 95 */ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */ A, A, A, A, A, A, A, A, A, A, A, R, R, U, P, X, /* 112..127 */ /* Nothing in the 128.. range */ }; // Copied from hex.c static const signed char hexval_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 10-17 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 18-1f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 20-27 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 28-2f */ 0, 1, 2, 3, 4, 5, 6, 7, /* 30-37 */ 8, 9, -1, -1, -1, -1, -1, -1, /* 38-3f */ -1, 10, 11, 12, 13, 14, 15, -1, /* 40-47 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 48-4f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 50-57 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 58-5f */ -1, 10, 11, 12, 13, 14, 15, -1, /* 60-67 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 68-67 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 70-77 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 78-7f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 80-87 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 88-8f */ -1, -1, -1, -1, -1, -1, -1, -1, /* 90-97 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 98-9f */ -1, -1, -1, -1, -1, -1, -1, -1, /* a0-a7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* a8-af */ -1, -1, -1, -1, -1, -1, -1, -1, /* b0-b7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* b8-bf */ -1, -1, -1, -1, -1, -1, -1, -1, /* c0-c7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* c8-cf */ -1, -1, -1, -1, -1, -1, -1, -1, /* d0-d7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* d8-df */ -1, -1, -1, -1, -1, -1, -1, -1, /* e0-e7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* e8-ef */ -1, -1, -1, -1, -1, -1, -1, -1, /* f0-f7 */ -1, -1, -1, -1, -1, -1, -1, -1, /* f8-ff */ }; // Copied from wildmatch.h #define WM_CASEFOLD 1 #define WM_PATHNAME 2 #define WM_NOMATCH 1 #define WM_MATCH 0 #define WM_ABORT_ALL -1 #define WM_ABORT_TO_STARSTAR -2 // Copied from wildmatch.c typedef unsigned char uchar; // local modification: remove NEGATE_CLASS(2) #define CC_EQ(class, len, litmatch) ((len) == sizeof (litmatch)-1 \ && *(class) == *(litmatch) \ && strncmp((char*)class, litmatch, len) == 0) // local modification: simpilify macros #define ISBLANK(c) ((c) == ' ' || (c) == '\t') #define ISGRAPH(c) (isprint(c) && !isspace(c)) #define ISPRINT(c) isprint(c) #define ISDIGIT(c) isdigit(c) #define ISALNUM(c) isalnum(c) #define ISALPHA(c) isalpha(c) #define ISCNTRL(c) iscntrl(c) #define ISLOWER(c) islower(c) #define ISPUNCT(c) ispunct(c) #define ISSPACE(c) isspace(c) #define ISUPPER(c) isupper(c) #define ISXDIGIT(c) isxdigit(c) /* Match pattern "p" against "text" */ static int dowild(const uchar *p, const uchar *text, unsigned int flags) { uchar p_ch; const uchar *pattern = p; for ( ; (p_ch = *p) != '\0'; text++, p++) { int matched, match_slash, negated; uchar t_ch, prev_ch; if ((t_ch = *text) == '\0' && p_ch != '*') return WM_ABORT_ALL; if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) t_ch = tolower(t_ch); if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) p_ch = tolower(p_ch); switch (p_ch) { case '\\': /* Literal match with following character. Note that the test * in "default" handles the p[1] == '\0' failure case. */ p_ch = *++p; /* FALLTHROUGH */ default: if (t_ch != p_ch) return WM_NOMATCH; continue; case '?': /* Match anything but '/'. */ if ((flags & WM_PATHNAME) && t_ch == '/') return WM_NOMATCH; continue; case '*': if (*++p == '*') { const uchar *prev_p = p - 2; while (*++p == '*') {} if (!(flags & WM_PATHNAME)) /* without WM_PATHNAME, '*' == '**' */ match_slash = 1; else if ((prev_p < pattern || *prev_p == '/') && (*p == '\0' || *p == '/' || (p[0] == '\\' && p[1] == '/'))) { /* * Assuming we already match 'foo/' and are at * <star star slash>, just assume it matches * nothing and go ahead match the rest of the * pattern with the remaining string. This * helps make foo/<*><*>/bar (<> because * otherwise it breaks C comment syntax) match * both foo/bar and foo/a/bar. */ if (p[0] == '/' && dowild(p + 1, text, flags) == WM_MATCH) return WM_MATCH; match_slash = 1; } else /* WM_PATHNAME is set */ match_slash = 0; } else /* without WM_PATHNAME, '*' == '**' */ match_slash = flags & WM_PATHNAME ? 0 : 1; if (*p == '\0') { /* Trailing "**" matches everything. Trailing "*" matches * only if there are no more slash characters. */ if (!match_slash) { if (strchr((char *)text, '/')) return WM_NOMATCH; } return WM_MATCH; } else if (!match_slash && *p == '/') { /* * _one_ asterisk followed by a slash * with WM_PATHNAME matches the next * directory */ const char *slash = strchr((char*)text, '/'); if (!slash) return WM_NOMATCH; text = (const uchar*)slash; /* the slash is consumed by the top-level for loop */ break; } while (1) { if (t_ch == '\0') break; /* * Try to advance faster when an asterisk is * followed by a literal. We know in this case * that the string before the literal * must belong to "*". * If match_slash is false, do not look past * the first slash as it cannot belong to '*'. */ if (!is_glob_special(*p)) { p_ch = *p; if ((flags & WM_CASEFOLD) && ISUPPER(p_ch)) p_ch = tolower(p_ch); while ((t_ch = *text) != '\0' && (match_slash || t_ch != '/')) { if ((flags & WM_CASEFOLD) && ISUPPER(t_ch)) t_ch = tolower(t_ch); if (t_ch == p_ch) break; text++; } if (t_ch != p_ch) return WM_NOMATCH; } if ((matched = dowild(p, text, flags)) != WM_NOMATCH) { if (!match_slash || matched != WM_ABORT_TO_STARSTAR) return matched; } else if (!match_slash && t_ch == '/') return WM_ABORT_TO_STARSTAR; t_ch = *++text; } return WM_ABORT_ALL; case '[': p_ch = *++p; if (p_ch == '^') p_ch = '!'; /* Assign literal 1/0 because of "matched" comparison. */ negated = p_ch == '!' ? 1 : 0; if (negated) { /* Inverted character class. */ p_ch = *++p; } prev_ch = 0; matched = 0; do { if (!p_ch) return WM_ABORT_ALL; if (p_ch == '\\') { p_ch = *++p; if (!p_ch) return WM_ABORT_ALL; if (t_ch == p_ch) matched = 1; } else if (p_ch == '-' && prev_ch && p[1] && p[1] != ']') { p_ch = *++p; if (p_ch == '\\') { p_ch = *++p; if (!p_ch) return WM_ABORT_ALL; } if (t_ch <= p_ch && t_ch >= prev_ch) matched = 1; else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) { uchar t_ch_upper = toupper(t_ch); if (t_ch_upper <= p_ch && t_ch_upper >= prev_ch) matched = 1; } p_ch = 0; /* This makes "prev_ch" get set to 0. */ } else if (p_ch == '[' && p[1] == ':') { const uchar *s; int i; for (s = p += 2; (p_ch = *p) && p_ch != ']'; p++) {} /*SHARED ITERATOR*/ if (!p_ch) return WM_ABORT_ALL; i = p - s - 1; if (i < 0 || p[-1] != ':') { /* Didn't find ":]", so treat like a normal set. */ p = s - 2; p_ch = '['; if (t_ch == p_ch) matched = 1; continue; } if (CC_EQ(s,i, "alnum")) { if (ISALNUM(t_ch)) matched = 1; } else if (CC_EQ(s,i, "alpha")) { if (ISALPHA(t_ch)) matched = 1; } else if (CC_EQ(s,i, "blank")) { if (ISBLANK(t_ch)) matched = 1; } else if (CC_EQ(s,i, "cntrl")) { if (ISCNTRL(t_ch)) matched = 1; } else if (CC_EQ(s,i, "digit")) { if (ISDIGIT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "graph")) { if (ISGRAPH(t_ch)) matched = 1; } else if (CC_EQ(s,i, "lower")) { if (ISLOWER(t_ch)) matched = 1; } else if (CC_EQ(s,i, "print")) { if (ISPRINT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "punct")) { if (ISPUNCT(t_ch)) matched = 1; } else if (CC_EQ(s,i, "space")) { if (ISSPACE(t_ch)) matched = 1; } else if (CC_EQ(s,i, "upper")) { if (ISUPPER(t_ch)) matched = 1; else if ((flags & WM_CASEFOLD) && ISLOWER(t_ch)) matched = 1; } else if (CC_EQ(s,i, "xdigit")) { if (ISXDIGIT(t_ch)) matched = 1; } else /* malformed [:class:] string */ return WM_ABORT_ALL; p_ch = 0; /* This makes "prev_ch" get set to 0. */ } else if (t_ch == p_ch) matched = 1; } while (prev_ch = p_ch, (p_ch = *++p) != ']'); if (matched == negated || ((flags & WM_PATHNAME) && t_ch == '/')) return WM_NOMATCH; continue; } } return *text ? WM_NOMATCH : WM_MATCH; } /* Match the "pattern" against the "text" string. */ static int wildmatch(const char *pattern, const char *text, unsigned int flags) { // local modification: move WM_CASEFOLD here if (ignore_case) flags |= WM_CASEFOLD; return dowild((const uchar*)pattern, (const uchar*)text, flags); } // Copied from dir.h #define PATTERN_FLAG_NODIR 1 #define PATTERN_FLAG_ENDSWITH 4 #define PATTERN_FLAG_MUSTBEDIR 8 #define PATTERN_FLAG_NEGATIVE 16 // Copied from dir.c static int fspathncmp(const char *a, const char *b, size_t count) { return ignore_case ? strncasecmp(a, b, count) : strncmp(a, b, count); } static int simple_length(const char *match) { int len = -1; for (;;) { unsigned char c = *match++; len++; if (c == '\0' || is_glob_special(c)) return len; } } static int no_wildcard(const char *string) { return string[simple_length(string)] == '\0'; } static void parse_path_pattern(const char **pattern, int *patternlen, unsigned *flags, int *nowildcardlen) { const char *p = *pattern; size_t i, len; *flags = 0; if (*p == '!') { *flags |= PATTERN_FLAG_NEGATIVE; p++; } len = strlen(p); if (len && p[len - 1] == '/') { len--; *flags |= PATTERN_FLAG_MUSTBEDIR; } for (i = 0; i < len; i++) { if (p[i] == '/') break; } if (i == len) *flags |= PATTERN_FLAG_NODIR; *nowildcardlen = simple_length(p); /* * we should have excluded the trailing slash from 'p' too, * but that's one more allocation. Instead just make sure * nowildcardlen does not exceed real patternlen */ if (*nowildcardlen > len) *nowildcardlen = len; if (*p == '*' && no_wildcard(p + 1)) *flags |= PATTERN_FLAG_ENDSWITH; *pattern = p; *patternlen = len; } static void trim_trailing_spaces(char *buf) { char *p, *last_space = NULL; for (p = buf; *p; p++) switch (*p) { case ' ': if (!last_space) last_space = p; break; case '\\': p++; if (!*p)