X-Git-Url: http://erislabs.net/gitweb/?a=blobdiff_plain;f=lib%2Fexclude.c;h=08a4829242f2e9d0df942e5009dbbf51f4f8305c;hb=48fe477c9008efadab8cf8c0c3240d824c12a8b9;hp=32f2a0a3e5f4ebe23ff840f91efc015b46a7ca26;hpb=bab00aef25c236cd0b910fa70465854d94cdbf0e;p=gnulib.git diff --git a/lib/exclude.c b/lib/exclude.c index 32f2a0a3e..08a482924 100644 --- a/lib/exclude.c +++ b/lib/exclude.c @@ -1,7 +1,7 @@ /* exclude.c -- exclude file names - Copyright (C) 1992, 1993, 1994, 1997, 1999, 2000, 2001, 2002, 2003, - 2004, 2005, 2006, 2007, 2009 Free Software Foundation, Inc. + Copyright (C) 1992-1994, 1997, 1999-2007, 2009-2012 Free Software + Foundation, Inc. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -31,6 +31,7 @@ #include #include #include +#include #include "exclude.h" #include "hash.h" @@ -55,9 +56,9 @@ #endif verify (((EXCLUDE_ANCHORED | EXCLUDE_INCLUDE | EXCLUDE_WILDCARDS) - & (FNM_PATHNAME | FNM_NOESCAPE | FNM_PERIOD | FNM_LEADING_DIR - | FNM_CASEFOLD | FNM_EXTMATCH)) - == 0); + & (FNM_PATHNAME | FNM_NOESCAPE | FNM_PERIOD | FNM_LEADING_DIR + | FNM_CASEFOLD | FNM_EXTMATCH)) + == 0); /* Exclusion patterns are grouped into a singly-linked list of @@ -103,34 +104,47 @@ struct exclude_segment } v; }; -/* The exclude structure keeps a singly-linked list of exclude segments */ +/* The exclude structure keeps a singly-linked list of exclude segments, + maintained in reverse order. */ struct exclude { - struct exclude_segment *head, *tail; + struct exclude_segment *head; }; -/* Return true if str has wildcard characters */ +/* Return true if STR has or may have wildcards, when matched with OPTIONS. + Return false if STR definitely does not have wildcards. */ bool fnmatch_pattern_has_wildcards (const char *str, int options) { - const char *cset = "\\?*[]"; - if (options & FNM_NOESCAPE) - cset++; - while (*str) + while (1) { - size_t n = strcspn (str, cset); - if (str[n] == 0) - break; - else if (str[n] == '\\') - { - str += n + 1; - if (*str) - str++; - } - else - return true; + switch (*str++) + { + case '\\': + str += ! (options & FNM_NOESCAPE) && *str; + break; + + case '+': case '@': case '!': + if (options & FNM_EXTMATCH && *str == '(') + return true; + break; + + case '?': case '*': case '[': + return true; + + case '\0': + return false; + } } - return false; +} + +static void +unescape_pattern (char *str) +{ + char const *q = str; + do + q += *q == '\\' && q[1]; + while ((*str++ = *q++)); } /* Return a newly allocated and empty exclude list. */ @@ -163,9 +177,9 @@ string_hasher_ci (void const *data, size_t n_buckets) wchar_t wc; if (m.wc_valid) - wc = towlower (m.wc); + wc = towlower (m.wc); else - wc = *m.ptr; + wc = *m.ptr; value = (value * 31 + wc) % n_buckets; } @@ -198,8 +212,8 @@ string_free (void *data) } /* Create new exclude segment of given TYPE and OPTIONS, and attach it - to the tail of list in EX */ -static struct exclude_segment * + to the head of EX. */ +static void new_exclude_segment (struct exclude *ex, enum exclude_type type, int options) { struct exclude_segment *sp = xzalloc (sizeof (struct exclude_segment)); @@ -212,21 +226,17 @@ new_exclude_segment (struct exclude *ex, enum exclude_type type, int options) case exclude_hash: sp->v.table = hash_initialize (0, NULL, - (options & FNM_CASEFOLD) ? - string_hasher_ci - : string_hasher, - (options & FNM_CASEFOLD) ? - string_compare_ci - : string_compare, - string_free); + (options & FNM_CASEFOLD) ? + string_hasher_ci + : string_hasher, + (options & FNM_CASEFOLD) ? + string_compare_ci + : string_compare, + string_free); break; } - if (ex->tail) - ex->tail->next = sp; - else - ex->head = sp; - ex->tail = sp; - return sp; + sp->next = ex->head; + ex->head = sp; } /* Free a single exclude segment */ @@ -268,41 +278,41 @@ fnmatch_no_wildcards (char const *pattern, char const *f, int options) { if (! (options & FNM_LEADING_DIR)) return ((options & FNM_CASEFOLD) - ? mbscasecmp (pattern, f) - : strcmp (pattern, f)); + ? mbscasecmp (pattern, f) + : strcmp (pattern, f)); else if (! (options & FNM_CASEFOLD)) { size_t patlen = strlen (pattern); int r = strncmp (pattern, f, patlen); if (! r) - { - r = f[patlen]; - if (r == '/') - r = 0; - } + { + r = f[patlen]; + if (r == '/') + r = 0; + } return r; } else { /* Walk through a copy of F, seeing whether P matches any prefix - of F. + of F. - FIXME: This is an O(N**2) algorithm; it should be O(N). - Also, the copy should not be necessary. However, fixing this - will probably involve a change to the mbs* API. */ + FIXME: This is an O(N**2) algorithm; it should be O(N). + Also, the copy should not be necessary. However, fixing this + will probably involve a change to the mbs* API. */ char *fcopy = xstrdup (f); char *p; int r; for (p = fcopy; ; *p++ = '/') - { - p = strchr (p, '/'); - if (p) - *p = '\0'; - r = mbscasecmp (pattern, fcopy); - if (!p || r <= 0) - break; - } + { + p = strchr (p, '/'); + if (p) + *p = '\0'; + r = mbscasecmp (pattern, fcopy); + if (!p || r <= 0) + break; + } free (fcopy); return r; } @@ -321,41 +331,38 @@ exclude_fnmatch (char const *pattern, char const *f, int options) if (! (options & EXCLUDE_ANCHORED)) for (p = f; *p && ! matched; p++) if (*p == '/' && p[1] != '/') - matched = ((*matcher) (pattern, p + 1, options) == 0); + matched = ((*matcher) (pattern, p + 1, options) == 0); return matched; } -/* Return true if the exclude_pattern segment SEG excludes F. */ +/* Return true if the exclude_pattern segment SEG matches F. */ static bool -excluded_file_pattern_p (struct exclude_segment const *seg, char const *f) +file_pattern_matches (struct exclude_segment const *seg, char const *f) { size_t exclude_count = seg->v.pat.exclude_count; struct patopts const *exclude = seg->v.pat.exclude; size_t i; - bool excluded = !! (exclude[0].options & EXCLUDE_INCLUDE); - /* Scan through the options, until they change excluded */ for (i = 0; i < exclude_count; i++) { char const *pattern = exclude[i].pattern; int options = exclude[i].options; - if (excluded != exclude_fnmatch (pattern, f, options)) - return !excluded; + if (exclude_fnmatch (pattern, f, options)) + return true; } - return excluded; + return false; } -/* Return true if the exclude_hash segment SEG excludes F. +/* Return true if the exclude_hash segment SEG matches F. BUFFER is an auxiliary storage of the same length as F (with nul terminator included) */ static bool -excluded_file_name_p (struct exclude_segment const *seg, char const *f, - char *buffer) +file_name_matches (struct exclude_segment const *seg, char const *f, + char *buffer) { int options = seg->options; - bool excluded = !! (options & EXCLUDE_INCLUDE); Hash_table *table = seg->v.table; do @@ -364,32 +371,33 @@ excluded_file_name_p (struct exclude_segment const *seg, char const *f, strcpy (buffer, f); while (1) - { - if (hash_lookup (table, buffer)) - return !excluded; - if (options & FNM_LEADING_DIR) - { - char *p = strrchr (buffer, '/'); - if (p) - { - *p = 0; - continue; - } - } - break; - } + { + if (hash_lookup (table, buffer)) + return true; + if (options & FNM_LEADING_DIR) + { + char *p = strrchr (buffer, '/'); + if (p) + { + *p = 0; + continue; + } + } + break; + } if (!(options & EXCLUDE_ANCHORED)) - { - f = strchr (f, '/'); - if (f) - f++; - } + { + f = strchr (f, '/'); + if (f) + f++; + } else - break; + break; } while (f); - return excluded; + + return false; } /* Return true if EX excludes F. */ @@ -398,41 +406,46 @@ bool excluded_file_name (struct exclude const *ex, char const *f) { struct exclude_segment *seg; - bool excluded; + bool invert = false; char *filename = NULL; /* If no patterns are given, the default is to include. */ if (!ex->head) return false; - /* Otherwise, the default is the opposite of the first option. */ - excluded = !! (ex->head->options & EXCLUDE_INCLUDE); - /* Scan through the segments, seeing whether they change status from - excluded to included or vice versa. */ - for (seg = ex->head; seg; seg = seg->next) + /* Scan through the segments, reporting the status of the first match. + The segments are in reverse order, so this reports the status of + the last match in the original option list. */ + for (seg = ex->head; ; seg = seg->next) { - bool rc; - - switch (seg->type) - { - case exclude_pattern: - rc = excluded_file_pattern_p (seg, f); - break; - - case exclude_hash: - if (!filename) - filename = xmalloc (strlen (f) + 1); - rc = excluded_file_name_p (seg, f, filename); - break; - } - if (rc != excluded) - { - excluded = rc; - break; - } + if (seg->type == exclude_hash) + { + if (!filename) + filename = xmalloc (strlen (f) + 1); + if (file_name_matches (seg, f, filename)) + break; + } + else + { + if (file_pattern_matches (seg, f)) + break; + } + + if (! seg->next) + { + /* If patterns are given but none match, the default is the + opposite of the last segment (i.e., the first in the + original option list). For example, in the command + 'grep -r --exclude="a*" --include="*b" pat dir', the + first option is --exclude so any file name matching + neither a* nor *b is included. */ + invert = true; + break; + } } + free (filename); - return excluded; + return invert ^ ! (seg->options & EXCLUDE_INCLUDE); } /* Append to EX the exclusion PATTERN with OPTIONS. */ @@ -448,17 +461,16 @@ add_exclude (struct exclude *ex, char const *pattern, int options) struct exclude_pattern *pat; struct patopts *patopts; - if (ex->tail && ex->tail->type == exclude_pattern - && ((ex->tail->options & EXCLUDE_INCLUDE) == - (options & EXCLUDE_INCLUDE))) - seg = ex->tail; - else - seg = new_exclude_segment (ex, exclude_pattern, options); + if (! (ex->head && ex->head->type == exclude_pattern + && ((ex->head->options & EXCLUDE_INCLUDE) + == (options & EXCLUDE_INCLUDE)))) + new_exclude_segment (ex, exclude_pattern, options); + seg = ex->head; pat = &seg->v.pat; if (pat->exclude_count == pat->exclude_alloc) - pat->exclude = x2nrealloc (pat->exclude, &pat->exclude_alloc, - sizeof *pat->exclude); + pat->exclude = x2nrealloc (pat->exclude, &pat->exclude_alloc, + sizeof *pat->exclude); patopts = &pat->exclude[pat->exclude_count++]; patopts->pattern = pattern; patopts->options = options; @@ -466,19 +478,20 @@ add_exclude (struct exclude *ex, char const *pattern, int options) else { char *str, *p; -#define EXCLUDE_HASH_FLAGS (EXCLUDE_INCLUDE|EXCLUDE_ANCHORED|\ - FNM_LEADING_DIR|FNM_CASEFOLD) - if (ex->tail && ex->tail->type == exclude_hash - && ((ex->tail->options & EXCLUDE_HASH_FLAGS) == - (options & EXCLUDE_HASH_FLAGS))) - seg = ex->tail; - else - seg = new_exclude_segment (ex, exclude_hash, options); + int exclude_hash_flags = (EXCLUDE_INCLUDE | EXCLUDE_ANCHORED + | FNM_LEADING_DIR | FNM_CASEFOLD); + if (! (ex->head && ex->head->type == exclude_hash + && ((ex->head->options & exclude_hash_flags) + == (options & exclude_hash_flags)))) + new_exclude_segment (ex, exclude_hash, options); + seg = ex->head; str = xstrdup (pattern); + if ((options & (EXCLUDE_WILDCARDS | FNM_NOESCAPE)) == EXCLUDE_WILDCARDS) + unescape_pattern (str); p = hash_insert (seg->v.table, str); if (p != str) - free (str); + free (str); } } @@ -489,8 +502,8 @@ add_exclude (struct exclude *ex, char const *pattern, int options) int add_exclude_file (void (*add_func) (struct exclude *, char const *, int), - struct exclude *ex, char const *file_name, int options, - char line_end) + struct exclude *ex, char const *file_name, int options, + char line_end) { bool use_stdin = file_name[0] == '-' && !file_name[1]; FILE *in; @@ -511,7 +524,7 @@ add_exclude_file (void (*add_func) (struct exclude *, char const *, int), while ((c = getc (in)) != EOF) { if (buf_count == buf_alloc) - buf = x2realloc (buf, &buf_alloc); + buf = x2realloc (buf, &buf_alloc); buf[buf_count++] = c; } @@ -529,22 +542,22 @@ add_exclude_file (void (*add_func) (struct exclude *, char const *, int), for (p = buf; p < lim; p++) if (*p == line_end) { - char *pattern_end = p; + char *pattern_end = p; - if (isspace ((unsigned char) line_end)) - { - for (; ; pattern_end--) - if (pattern_end == pattern) - goto next_pattern; - else if (! isspace ((unsigned char) pattern_end[-1])) - break; - } + if (isspace ((unsigned char) line_end)) + { + for (; ; pattern_end--) + if (pattern_end == pattern) + goto next_pattern; + else if (! isspace ((unsigned char) pattern_end[-1])) + break; + } - *pattern_end = '\0'; - (*add_func) (ex, pattern, options); + *pattern_end = '\0'; + (*add_func) (ex, pattern, options); next_pattern: - pattern = p + 1; + pattern = p + 1; } errno = e;