+/* Return true if the exclude_pattern segment SEG matches F. */
+
+static bool
+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;
+
+ for (i = 0; i < exclude_count; i++)
+ {
+ char const *pattern = exclude[i].pattern;
+ int options = exclude[i].options;
+ if (exclude_fnmatch (pattern, f, options))
+ return true;
+ }
+ return false;
+}
+
+/* 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
+file_name_matches (struct exclude_segment const *seg, char const *f,
+ char *buffer)
+{
+ int options = seg->options;
+ Hash_table *table = seg->v.table;
+
+ do
+ {
+ /* initialize the pattern */
+ strcpy (buffer, f);
+
+ while (1)
+ {
+ 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++;
+ }
+ else
+ break;
+ }
+ while (f);
+
+ return false;
+}
+
+/* Return true if EX excludes F. */
+
+bool
+excluded_file_name (struct exclude const *ex, char const *f)
+{
+ struct exclude_segment *seg;
+ bool invert = false;
+ char *filename = NULL;
+
+ /* If no patterns are given, the default is to include. */
+ if (!ex->head)
+ return false;
+
+ /* 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)
+ {
+ 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 invert ^ ! (seg->options & EXCLUDE_INCLUDE);
+}
+
+/* Append to EX the exclusion PATTERN with OPTIONS. */
+
+void
+add_exclude (struct exclude *ex, char const *pattern, int options)
+{
+ struct exclude_segment *seg;
+
+ if ((options & EXCLUDE_WILDCARDS)
+ && fnmatch_pattern_has_wildcards (pattern, options))
+ {
+ struct exclude_pattern *pat;
+ struct patopts *patopts;
+
+ 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);
+ patopts = &pat->exclude[pat->exclude_count++];
+ patopts->pattern = pattern;
+ patopts->options = options;
+ }
+ else
+ {
+ char *str, *p;
+ 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);
+ }
+}
+
+/* Use ADD_FUNC to append to EX the patterns in FILE_NAME, each with
+ OPTIONS. LINE_END terminates each pattern in the file. If
+ LINE_END is a space character, ignore trailing spaces and empty
+ lines in FILE. Return -1 on failure, 0 on success. */