1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License"). You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22 /*
23 * Copyright (c) 1995 Sun Microsystems, Inc. All Rights Reserved
24 *
25 * module:
26 * ignore.c
27 *
28 * purpose:
29 * routines to manage the ignore lists and test names against them,
30 *
31 * contents:
32 * ignore_check ... is a particular file covered by an ignore rule
33 * ignore_file .... add a specific file name to be ignored
34 * ignore_expr .... add a regular expression for files to be ignored
35 * ignore_pgm ..... add a rule to run a program to generate a list
36 * ignore_reset ... flush the internal optimization data structures
37 *
38 * static
39 * ign_hash ... maintain a hash table of ignored names
40 * cheap_check. build up a table of safe suffixes
41 *
42 * notes:
43 * a much simpler implementation could have been provided, but
44 * this test (every file tested against every rule) has the
45 * potential to be EXTREMELY expensive. This module implements
46 * an engine that attempts to optimize the process of determining
47 * that a file has not been ignored.
48 *
49 * the usage scenario is
50 * per base
51 * call ignore_{file,expr,pgm} for each ignore rule
52 * call ignore_check for every file under the base
53 * call ignore_reset when you are done
54 */
55 #ident "%W% %E% SMI"
56
57 #include <stdio.h>
58 #include <stdlib.h>
59 #include <string.h>
60 #include <libgen.h>
61
62 #include "filesync.h"
63 #include "messages.h"
64
65 /*
66 * routines:
67 */
68 static struct list *ign_hash(const char *, int);
69 static void cheap_check(const char *);
70
71 /*
72 * globals
73 */
74 struct list {
75 char *l_value; /* the actual string */
76 struct list *l_next; /* pointer to next element */
77 };
78
79 static struct list *expr_list; /* list of regular expressions */
80 static struct list *file_list[ HASH_SIZE ]; /* hash table of literal names */
81
82 static char cheap_last[256]; /* cheap test: last char */
83 static char cheap_penu[256]; /* cheap test: penultimate char */
84
85 /*
86 * routine:
87 * ignore_check
88 *
89 * purpose:
90 * determine whether or not a particular name matches an ignore pattern.
91 *
92 * parameters:
93 * file name
94 *
95 * returns:
96 * true/false
97 *
98 * note:
99 * becuse this routine is called on every single file in
100 * every single sub-directory, it is critical that we make
101 * it fail quickly for most files. The purpose of the cheap_last
102 * and cheap_penu arrays is to quickly determine there is no chance
103 * that a name will match any expression. Most expressions have
104 * wildcards near the front and constant suffixes, so our cheap
105 * test is to look at the last two bytes.
106 */
107 bool_t
ignore_check(const char * name)108 ignore_check(const char *name)
109 { struct list *lp;
110 const char *s;
111
112 /*
113 * start with the cheap test
114 */
115 for (s = name; *s; s++);
116 if (cheap_last[ (unsigned char) s[-1] ] == 0 ||
117 cheap_penu[ (unsigned char) s[-2] ] == 0)
118 return (FALSE);
119
120 /* check the literal names in the hash table */
121 if (ign_hash(name, 0)) {
122 if (opt_debug & DBG_IGNORE)
123 fprintf(stderr, "IGNO: match %s\n", name);
124 return (TRUE);
125 }
126
127 /* check all the regular expressions */
128 for (lp = expr_list; lp; lp = lp->l_next) {
129 if (gmatch(name, lp->l_value) == 0)
130 continue;
131
132 if (opt_debug & DBG_IGNORE)
133 fprintf(stderr, "IGNO: regex %s : %s\n",
134 lp->l_value, name);
135 return (TRUE);
136 }
137
138 return (FALSE);
139 }
140
141 /*
142 * routine:
143 * ignore_file
144 *
145 * purpose:
146 * to add a specific file to an ignore list
147 *
148 * parameters:
149 * command to run
150 */
151 void
ignore_file(const char * name)152 ignore_file(const char *name)
153 {
154 cheap_check(name);
155
156 (void) ign_hash(name, 1);
157
158 if (opt_debug & DBG_IGNORE)
159 fprintf(stderr, "IGNO: add file %s\n", name);
160 }
161
162 /*
163 * routine:
164 * ignore_expr
165 *
166 * purpose:
167 * to add a regular expression to an ignore list
168 *
169 * parameters:
170 * command to run
171 */
172 void
ignore_expr(const char * expr)173 ignore_expr(const char *expr)
174 { struct list *lp;
175
176 cheap_check(expr);
177
178 /* allocate a new node and stick it on the front of the list */
179 lp = malloc(sizeof (*lp));
180 if (lp == 0)
181 nomem("ignore list");
182 lp->l_value = strdup(expr);
183 lp->l_next = expr_list;
184 expr_list = lp;
185
186 if (opt_debug & DBG_IGNORE)
187 fprintf(stderr, "IGNO: add expr %s\n", expr);
188 }
189
190 /*
191 * routine:
192 * ignore_pgm
193 *
194 * purpose:
195 * to run a program and gather up the ignore list it produces
196 *
197 * parameters:
198 * command to run
199 */
200 void
ignore_pgm(const char * cmd)201 ignore_pgm(const char *cmd)
202 { char *s;
203 FILE *fp;
204 char inbuf[ MAX_LINE ];
205
206 if (opt_debug & DBG_IGNORE)
207 fprintf(stderr, "IGNO: add pgm %s\n", cmd);
208
209 /* run the command and collect its ouput */
210 fp = popen(cmd, "r");
211 if (fp == NULL) {
212 fprintf(stderr, gettext(ERR_badrun), cmd);
213 return;
214 }
215
216 /*
217 * read each line, strip off the newline and add it to the list
218 */
219 while (fgets(inbuf, sizeof (inbuf), fp) != 0) {
220 /* strip off any trailing newline */
221 for (s = inbuf; *s && *s != '\n'; s++);
222 *s = 0;
223
224 /* skip any leading white space */
225 for (s = inbuf; *s == ' ' || *s == '\t'; s++);
226
227 /* add this file to the list */
228 if (*s) {
229 cheap_check(s);
230 (void) ign_hash(s, 1);
231
232 if (opt_debug & DBG_IGNORE)
233 fprintf(stderr, "IGNO: ... %s\n", s);
234 }
235 }
236
237 pclose(fp);
238 }
239
240 /*
241 * routine:
242 * ign_hash
243 *
244 * purpose:
245 * to find an entry in the hash list
246 *
247 * parameters:
248 * name
249 * allocate flag
250 *
251 * returns:
252 * pointer to new list entry or 0
253 */
254 static struct list *
ign_hash(const char * name,int alloc)255 ign_hash(const char *name, int alloc)
256 { const unsigned char *s;
257 int i;
258 struct list *lp;
259 struct list **pp;
260
261 /* perform the hash and find the chain */
262 for (s = (const unsigned char *) name, i = 0; *s; s++)
263 i += *s;
264 pp = &file_list[i % HASH_SIZE ];
265
266 /* search for the specified entry */
267 for (lp = *pp; lp; lp = *pp) {
268 if (strcmp(name, lp->l_value) == 0)
269 return (lp);
270 pp = &(lp->l_next);
271 }
272
273 /* if caller said alloc, buy a new node and chain it in */
274 if (alloc) {
275 lp = malloc(sizeof (*lp));
276 if (lp == 0)
277 nomem("ignore list");
278 lp->l_value = strdup(name);
279 lp->l_next = 0;
280 *pp = lp;
281 }
282
283 return (lp);
284 }
285
286 /*
287 * routine:
288 * cheap_check
289 *
290 * purpose:
291 * to update the cheap-check arrays for an ignore expression
292 *
293 * parameters:
294 * name/expression
295 */
296 static void
cheap_check(const char * name)297 cheap_check(const char *name)
298 { const char *s;
299 unsigned char c;
300 int i;
301
302 for (s = name; *s; s++);
303 s--;
304
305 /* if expr ends in a wild card, we are undone */
306 c = *s;
307 if (c == '*' || c == '?' || c == ']' || c == '}') {
308 for (i = 0; i < 256; i++) {
309 cheap_last[i] = 1;
310 cheap_penu[i] = 1;
311 }
312 return;
313 } else
314 cheap_last[c] = 1;
315
316 if (s <= name)
317 return;
318
319 /* check the next to last character too */
320 c = s[-1];
321 if (c == '*' || c == '?' || c == ']' || c == '}') {
322 for (i = 0; i < 256; i++)
323 cheap_penu[i] = 1;
324 } else
325 cheap_penu[c] = 1;
326 }
327
328 /*
329 * routine:
330 * ignore_reset
331 *
332 * purpose:
333 * to free up all the ignore entries so we can start anew
334 */
335 void
ignore_reset(void)336 ignore_reset(void)
337 { int i;
338 struct list *np = 0; /* for LINT */
339 struct list *lp;
340
341 /* clear the cheap check arrays */
342 for (i = 0; i < 255; i++) {
343 cheap_last[i] = 0;
344 cheap_penu[i] = 0;
345 }
346
347 /* free all of the literal hash chains */
348 for (i = 0; i < HASH_SIZE; i++) {
349 for (lp = file_list[i]; lp; lp = np) {
350 np = lp->l_next;
351 free(lp->l_value);
352 free(lp);
353 }
354 file_list[i] = 0;
355 }
356
357 /* free all of the expressions on the chain */
358 for (lp = expr_list; lp; lp = np) {
359 np = lp->l_next;
360 free(lp->l_value);
361 free(lp);
362 }
363 expr_list = 0;
364 }
365