1/* exclude.c -- exclude file names 2 3 Copyright (C) 1992, 1993, 1994, 1997, 1999, 2000, 2001, 2002, 2003 Free 4 Software Foundation, Inc. 5 6 This program is free software; you can redistribute it and/or modify 7 it under the terms of the GNU General Public License as published by 8 the Free Software Foundation; either version 2, or (at your option) 9 any later version. 10 11 This program is distributed in the hope that it will be useful, 12 but WITHOUT ANY WARRANTY; without even the implied warranty of 13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 GNU General Public License for more details. 15 16 You should have received a copy of the GNU General Public License 17 along with this program; see the file COPYING. 18 If not, write to the Free Software Foundation, 19 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ 20 21/* Written by Paul Eggert <eggert@twinsun.com> */ 22 23#if HAVE_CONFIG_H 24# include <config.h> 25#endif 26 27#include <stdbool.h> 28 29#include <ctype.h> 30#include <errno.h> 31#ifndef errno 32extern int errno; 33#endif 34#include <stddef.h> 35#include <stdio.h> 36#include <stdlib.h> 37#include <string.h> 38 39#include "exclude.h" 40#include "fnmatch.h" 41#include "unlocked-io.h" 42#include "xalloc.h" 43 44#if STDC_HEADERS || (! defined isascii && ! HAVE_ISASCII) 45# define IN_CTYPE_DOMAIN(c) true 46#else 47# define IN_CTYPE_DOMAIN(c) isascii (c) 48#endif 49 50static inline bool 51is_space (unsigned char c) 52{ 53 return IN_CTYPE_DOMAIN (c) && isspace (c); 54} 55 56/* Verify a requirement at compile-time (unlike assert, which is runtime). */ 57#define verify(name, assertion) struct name { char a[(assertion) ? 1 : -1]; } 58 59/* Non-GNU systems lack these options, so we don't need to check them. */ 60#ifndef FNM_CASEFOLD 61# define FNM_CASEFOLD 0 62#endif 63#ifndef FNM_LEADING_DIR 64# define FNM_LEADING_DIR 0 65#endif 66 67verify (EXCLUDE_macros_do_not_collide_with_FNM_macros, 68 (((EXCLUDE_ANCHORED | EXCLUDE_INCLUDE | EXCLUDE_WILDCARDS) 69 & (FNM_PATHNAME | FNM_NOESCAPE | FNM_PERIOD | FNM_LEADING_DIR 70 | FNM_CASEFOLD)) 71 == 0)); 72 73/* An exclude pattern-options pair. The options are fnmatch options 74 ORed with EXCLUDE_* options. */ 75 76struct patopts 77 { 78 char const *pattern; 79 int options; 80 }; 81 82/* An exclude list, of pattern-options pairs. */ 83 84struct exclude 85 { 86 struct patopts *exclude; 87 size_t exclude_alloc; 88 size_t exclude_count; 89 }; 90 91/* Return a newly allocated and empty exclude list. */ 92 93struct exclude * 94new_exclude (void) 95{ 96 return xzalloc (sizeof *new_exclude ()); 97} 98 99/* Free the storage associated with an exclude list. */ 100 101void 102free_exclude (struct exclude *ex) 103{ 104 free (ex->exclude); 105 free (ex); 106} 107 108/* Return zero if PATTERN matches F, obeying OPTIONS, except that 109 (unlike fnmatch) wildcards are disabled in PATTERN. */ 110 111static int 112fnmatch_no_wildcards (char const *pattern, char const *f, int options) 113{ 114 if (! (options & FNM_LEADING_DIR)) 115 return ((options & FNM_CASEFOLD) 116 ? strcasecmp (pattern, f) 117 : strcmp (pattern, f)); 118 else 119 { 120 size_t patlen = strlen (pattern); 121 int r = ((options & FNM_CASEFOLD) 122 ? strncasecmp (pattern, f, patlen) 123 : strncmp (pattern, f, patlen)); 124 if (! r) 125 { 126 r = f[patlen]; 127 if (r == '/') 128 r = 0; 129 } 130 return r; 131 } 132} 133 134/* Return true if EX excludes F. */ 135 136bool 137excluded_filename (struct exclude const *ex, char const *f) 138{ 139 size_t exclude_count = ex->exclude_count; 140 141 /* If no options are given, the default is to include. */ 142 if (exclude_count == 0) 143 return false; 144 else 145 { 146 struct patopts const *exclude = ex->exclude; 147 size_t i; 148 149 /* Otherwise, the default is the opposite of the first option. */ 150 bool excluded = !! (exclude[0].options & EXCLUDE_INCLUDE); 151 152 /* Scan through the options, seeing whether they change F from 153 excluded to included or vice versa. */ 154 for (i = 0; i < exclude_count; i++) 155 { 156 char const *pattern = exclude[i].pattern; 157 int options = exclude[i].options; 158 if (excluded == !! (options & EXCLUDE_INCLUDE)) 159 { 160 int (*matcher) (char const *, char const *, int) = 161 (options & EXCLUDE_WILDCARDS 162 ? fnmatch 163 : fnmatch_no_wildcards); 164 bool matched = ((*matcher) (pattern, f, options) == 0); 165 char const *p; 166 167 if (! (options & EXCLUDE_ANCHORED)) 168 for (p = f; *p && ! matched; p++) 169 if (*p == '/' && p[1] != '/') 170 matched = ((*matcher) (pattern, p + 1, options) == 0); 171 172 excluded ^= matched; 173 } 174 } 175 176 return excluded; 177 } 178} 179 180/* Append to EX the exclusion PATTERN with OPTIONS. */ 181 182void 183add_exclude (struct exclude *ex, char const *pattern, int options) 184{ 185 struct patopts *patopts; 186 187 if (ex->exclude_count == ex->exclude_alloc) 188 ex->exclude = x2nrealloc (ex->exclude, &ex->exclude_alloc, 189 sizeof *ex->exclude); 190 191 patopts = &ex->exclude[ex->exclude_count++]; 192 patopts->pattern = pattern; 193 patopts->options = options; 194} 195 196/* Use ADD_FUNC to append to EX the patterns in FILENAME, each with 197 OPTIONS. LINE_END terminates each pattern in the file. If 198 LINE_END is a space character, ignore trailing spaces and empty 199 lines in FILE. Return -1 on failure, 0 on success. */ 200 201int 202add_exclude_file (void (*add_func) (struct exclude *, char const *, int), 203 struct exclude *ex, char const *filename, int options, 204 char line_end) 205{ 206 bool use_stdin = filename[0] == '-' && !filename[1]; 207 FILE *in; 208 char *buf = NULL; 209 char *p; 210 char const *pattern; 211 char const *lim; 212 size_t buf_alloc = 0; 213 size_t buf_count = 0; 214 int c; 215 int e = 0; 216 217 if (use_stdin) 218 in = stdin; 219 else if (! (in = fopen (filename, "r"))) 220 return -1; 221 222 while ((c = getc (in)) != EOF) 223 { 224 if (buf_count == buf_alloc) 225 buf = x2realloc (buf, &buf_alloc); 226 buf[buf_count++] = c; 227 } 228 229 if (ferror (in)) 230 e = errno; 231 232 if (!use_stdin && fclose (in) != 0) 233 e = errno; 234 235 buf = xrealloc (buf, buf_count + 1); 236 buf[buf_count] = line_end; 237 lim = buf + buf_count + ! (buf_count == 0 || buf[buf_count - 1] == line_end); 238 pattern = buf; 239 240 for (p = buf; p < lim; p++) 241 if (*p == line_end) 242 { 243 char *pattern_end = p; 244 245 if (is_space (line_end)) 246 { 247 for (; ; pattern_end--) 248 if (pattern_end == pattern) 249 goto next_pattern; 250 else if (! is_space (pattern_end[-1])) 251 break; 252 } 253 254 *pattern_end = '\0'; 255 (*add_func) (ex, pattern, options); 256 257 next_pattern: 258 pattern = p + 1; 259 } 260 261 errno = e; 262 return e ? -1 : 0; 263} 264