xref: /illumos-gate/usr/src/cmd/bart/rules.c (revision c40f76e346ad844b9326c2049644b7b1d1a93e48)
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 (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 /*
22  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 #pragma ident	"%Z%%M%	%I%	%E% SMI"
26 
27 #include <dirent.h>
28 #include <fnmatch.h>
29 #include <string.h>
30 #include "bart.h"
31 
32 static int count_slashes(const char *);
33 static struct rule *gen_rulestruct(void);
34 static struct tree_modifier *gen_tree_modifier(void);
35 static struct dir_component *gen_dir_component(void);
36 static void init_rule(uint_t, struct rule *);
37 static void add_modifier(struct rule *, char *);
38 static struct rule *add_subtree_rule(char *, char *, int, int *);
39 static struct rule *add_single_rule(char *);
40 static void dirs_cleanup(struct dir_component *);
41 static void add_dir(struct dir_component **, char *);
42 static char *lex(FILE *);
43 static int match_subtree(const char *, char *);
44 static struct rule *get_last_entry(boolean_t);
45 
46 static int	lex_linenum;	/* line number in current input file	*/
47 static struct rule	*first_rule = NULL, *current_rule = NULL;
48 
49 /*
50  * This function is responsible for validating whether or not a given file
51  * should be cataloged, based upon the modifiers for a subtree.
52  * For example, a line in the rules file: '/home/nickiso *.c' should only
53  * catalog the C files (based upon pattern matching) in the subtree
54  * '/home/nickiso'.
55  *
56  * Return non-zero if it should be excluded, 0 if it should be cataloged.
57  */
58 int
59 exclude_fname(const char *fname, char fname_type, struct rule *rule_ptr)
60 {
61 	char	*pattern, *ptr, *fname_ptr, fname_cp[PATH_MAX],
62 		pattern_cp[PATH_MAX], saved_char;
63 	int	match, num_pattern_slash, num_fname_slash, i, slashes_to_adv,
64 		ret_val = 0;
65 	struct  tree_modifier   *mod_ptr;
66 	boolean_t		dir_flag;
67 
68 	/*
69 	 * For a given entry in the rules struct, the modifiers, e.g., '*.c',
70 	 * are kept in a linked list.  Get a ptr to the head of the list.
71 	 */
72 	mod_ptr = rule_ptr->modifiers;
73 
74 	/*
75 	 * Walk through all the modifiers until its they are exhausted OR
76 	 * until the file should definitely be excluded.
77 	 */
78 	while ((mod_ptr != NULL) && !ret_val) {
79 		/* First, see if we should be matching files or dirs */
80 		if (mod_ptr->mod_str[(strlen(mod_ptr->mod_str)-1)] == '/')
81 			dir_flag = B_TRUE;
82 		else
83 			dir_flag = B_FALSE;
84 
85 		if (mod_ptr->mod_str[0] == '!') {
86 			pattern = (mod_ptr->mod_str + 1);
87 		} else {
88 			pattern = mod_ptr->mod_str;
89 		}
90 
91 		if (dir_flag == B_FALSE) {
92 			/*
93 			 * In the case when a user is trying to filter on
94 			 * FILES and the entry is a directory, its excluded!
95 			 */
96 			if (fname_type == 'D') {
97 				ret_val = 1;
98 				break;
99 			}
100 
101 			/*
102 			 * Match patterns against filenames.
103 			 * Need to be able to handle multi-level patterns,
104 			 * eg. "SCCS/<star-wildcard>.c", which means
105 			 * 'only match C files under SCCS directories.
106 			 *
107 			 * Determine the number of levels in the filename and
108 			 * in the pattern.
109 			 */
110 			num_pattern_slash = count_slashes(pattern);
111 			num_fname_slash = count_slashes(fname);
112 
113 			/* Check for trivial exclude condition */
114 			if (num_pattern_slash > num_fname_slash) {
115 				ret_val = 1;
116 				break;
117 			}
118 
119 			/*
120 			 * Do an apples to apples comparison, based upon the
121 			 * number of levels:
122 			 *
123 			 * Assume fname is /A/B/C/D/E and the pattern is D/E.
124 			 * In that case, 'ptr' will point to "D/E" and
125 			 * 'slashes_to_adv' will be '4'.
126 			 */
127 			(void) strlcpy(fname_cp, fname, sizeof (fname_cp));
128 			ptr = fname_cp;
129 			slashes_to_adv = num_fname_slash - num_pattern_slash;
130 			for (i = 0; i < slashes_to_adv; i++)  {
131 				ptr = strchr(ptr, '/');
132 				ptr++;
133 			}
134 			if ((pattern[0] == '.') && (pattern[1] == '.') &&
135 			    (pattern[2] == '/')) {
136 				pattern = strchr(pattern, '/');
137 				ptr = strchr(ptr, '/');
138 			}
139 
140 
141 			/* OK, now do the fnmatch() and set the return value */
142 			match = fnmatch(pattern, ptr, FNM_PATHNAME);
143 
144 			if (match == 0)
145 				ret_val = 0;
146 			else
147 				ret_val = 1;
148 
149 		} else {
150 			/*
151 			 * The rule requires directory matching.
152 			 *
153 			 * First, make copies, since both the pattern and
154 			 * filename need to be modified.
155 			 *
156 			 * When copying 'fname', ignore the relocatable root
157 			 * since pattern matching is done for the string AFTER
158 			 * the relocatable root.  For example, if the
159 			 * relocatable root is "/dir1/dir2/dir3" and the
160 			 * pattern is "dir3/", we do NOT want to include every
161 			 * directory in the relocatable root.  Instead, we
162 			 * only want to include subtrees that look like:
163 			 * "/dir1/dir2/dir3/....dir3/....."
164 			 *
165 			 * NOTE: the 'fname_cp' does NOT have a trailing '/':
166 			 * necessary for fnmatch().
167 			 */
168 			(void) strlcpy(fname_cp,
169 			    (fname+strlen(rule_ptr->subtree)),
170 			    sizeof (fname_cp));
171 			(void) strlcpy(pattern_cp, pattern,
172 			    sizeof (pattern_cp));
173 
174 			/*
175 			 * For non-directory entries, remove the trailing
176 			 * name, e.g., for a file /A/B/C/D where 'D' is
177 			 * the actual filename, remove the 'D' since it
178 			 * should *not* be considered in the directory match.
179 			 */
180 			if (fname_type != 'D') {
181 				ptr = strrchr(fname_cp, '/');
182 				if (ptr != NULL)
183 					*ptr = '\0';
184 
185 				/* Trivial case: simple filename */
186 				if (strlen(fname_cp) == 0) {
187 					if (mod_ptr->include == B_FALSE)
188 						ret_val = 0;
189 					else
190 						ret_val = 1;
191 					break;
192 				}
193 			}
194 
195 			/* Count the # of slashes in the pattern and fname */
196 			num_pattern_slash = count_slashes(pattern_cp);
197 			num_fname_slash = count_slashes(fname_cp);
198 
199 			/*
200 			 * fname_cp is too short, bail!
201 			 */
202 			if (num_pattern_slash > num_fname_slash) {
203 				if (mod_ptr->include == B_FALSE)
204 					ret_val = 0;
205 				else
206 					ret_val = 1;
207 
208 				break;
209 			}
210 
211 			/* set the return value before we enter the loop */
212 			ret_val = 1;
213 
214 			/*
215 			 * Take the leading '/' from fname_cp before
216 			 * decrementing the number of slashes.
217 			 */
218 			if (fname_cp[0] == '/') {
219 				(void) strlcpy(fname_cp,
220 				    strchr(fname_cp, '/') + 1,
221 				    sizeof (fname_cp));
222 				num_fname_slash--;
223 			}
224 
225 			/*
226 			 * Begin the loop, walk through the file name until
227 			 * it can be determined that there is no match.
228 			 * For example: if pattern is C/D/, and fname_cp is
229 			 * A/B/C/D/E then compare A/B/ with C/D/, if it doesn't
230 			 * match, then walk further so that the next iteration
231 			 * checks B/C/ against C/D/, continue until we have
232 			 * exhausted options.
233 			 * In the above case, the 3rd iteration will match
234 			 * C/D/ with C/D/.
235 			 */
236 			while (num_pattern_slash <= num_fname_slash) {
237 				/* get a pointer to our filename */
238 				fname_ptr = fname_cp;
239 
240 				/*
241 				 * Walk the filename through the slashes
242 				 * so that we have a component of the same
243 				 * number of slashes as the pattern.
244 				 */
245 
246 				for (i = 0; i < num_pattern_slash; i++) {
247 					ptr = strchr(fname_ptr, '/');
248 					fname_ptr = ptr + 1;
249 				}
250 
251 				/*
252 				 * Save the character after our target slash
253 				 * before breaking the string for use with
254 				 * fnmatch
255 				 */
256 				saved_char = *(++ptr);
257 
258 				*ptr = '\0';
259 
260 				/*
261 				 * Try to match the current component with the
262 				 * pattern we are looking for.
263 				 */
264 				match = fnmatch(pattern_cp, fname_cp,
265 				    FNM_PATHNAME);
266 
267 				/*
268 				 * If we matched, set ret_val and break.
269 				 * No need to invert ret_val here as it is done
270 				 * outside of this inner while loop.
271 				 */
272 				if (match == 0) {
273 					ret_val = 0;
274 					break;
275 				}
276 
277 				/*
278 				 * We didn't match, so restore the saved
279 				 * character to the original position.
280 				 */
281 				*ptr = saved_char;
282 
283 				/*
284 				 * Break down fname_cp, if it was A/B/C
285 				 * then after this operation it will be B/C
286 				 * in preparation for the next iteration.
287 				 */
288 				(void) strlcpy(fname_cp,
289 				    strchr(fname_cp, '/') + 1,
290 				    sizeof (fname_cp));
291 
292 				/*
293 				 * Decrement the number of slashes to
294 				 * compensate for the one removed above.
295 				 */
296 				num_fname_slash--;
297 			}
298 
299 			/*
300 			 * If we didn't get a match above then we may be on the
301 			 * last component of our filename.
302 			 * This is to handle the following cases
303 			 *    - filename is A/B/C/D/E and pattern may be D/E/
304 			 *    - filename is D/E and pattern may be D/E/
305 			 */
306 			if ((ret_val != 0) &&
307 			    (num_pattern_slash == (num_fname_slash + 1))) {
308 
309 				/* strip the trailing slash from the pattern */
310 				ptr = strrchr(pattern_cp, '/');
311 				*ptr = '\0';
312 
313 				match = fnmatch(pattern_cp,
314 				    fname_cp, FNM_PATHNAME);
315 				if (match == 0) {
316 					/*
317 					 * No need to invert ret_val as it is
318 					 * done below.
319 					 */
320 					ret_val = 0;
321 				}
322 			}
323 		}
324 
325 		/*
326 		 * Take into account whether or not this rule began with
327 		 * a '!'
328 		 */
329 		if (mod_ptr->include == B_FALSE) {
330 			if (ret_val == 0)
331 				ret_val = 1;
332 			else ret_val = 0;
333 		}
334 
335 		/* Advance to the next modifier */
336 		mod_ptr = mod_ptr->next;
337 	}
338 
339 	return (ret_val);
340 }
341 
342 static int
343 count_slashes(const char *in_path)
344 {
345 	int num_fname_slash = 0;
346 	const char *p;
347 	for (p = in_path; *p != '\0'; p++)
348 		if (*p == '/')
349 			num_fname_slash++;
350 	return (num_fname_slash);
351 }
352 
353 static struct rule *
354 gen_rulestruct(void)
355 {
356 	struct rule	*new_rule;
357 
358 	new_rule = (struct rule *)safe_calloc(sizeof (struct rule));
359 	new_rule->traversed = B_FALSE;
360 	return (new_rule);
361 }
362 
363 static struct tree_modifier *
364 gen_tree_modifier(void)
365 {
366 	struct tree_modifier	*new_modifier;
367 
368 	new_modifier = (struct tree_modifier *)safe_calloc
369 	    (sizeof (struct tree_modifier));
370 	return (new_modifier);
371 }
372 
373 static struct dir_component *
374 gen_dir_component(void)
375 {
376 	struct dir_component	*new_dir;
377 
378 	new_dir = (struct dir_component *)safe_calloc
379 	    (sizeof (struct dir_component));
380 	return (new_dir);
381 }
382 
383 /*
384  * Set up a default rule when there is no rules file.
385  */
386 static struct rule *
387 setup_default_rule(char *reloc_root, uint_t flags)
388 {
389 	struct	rule	*new_rule;
390 
391 	new_rule = add_single_rule(reloc_root[0] == '\0' ? "/" : reloc_root);
392 	init_rule(flags, new_rule);
393 	add_modifier(new_rule, "*");
394 
395 	return (new_rule);
396 }
397 
398 /*
399  * Utility function, used to initialize the flag in a new rule structure.
400  */
401 static void
402 init_rule(uint_t flags, struct rule *new_rule)
403 {
404 
405 	if (new_rule == NULL)
406 		return;
407 	new_rule->attr_list = flags;
408 }
409 
410 /*
411  * Function to read the rulesfile.  Used by both 'bart create' and
412  * 'bart compare'.
413  */
414 int
415 read_rules(FILE *file, char *reloc_root, uint_t in_flags, int create)
416 {
417 	char		*s;
418 	struct rule	*block_begin = NULL, *new_rule, *rp;
419 	struct attr_keyword *akp;
420 	int		check_flag, ignore_flag, syntax_err, ret_code;
421 
422 	ret_code = EXIT;
423 
424 	lex_linenum = 0;
425 	check_flag = 0;
426 	ignore_flag = 0;
427 	syntax_err = 0;
428 
429 	if (file == NULL) {
430 		(void) setup_default_rule(reloc_root, in_flags);
431 		return (ret_code);
432 	} else if (!create) {
433 		block_begin = setup_default_rule("/", in_flags);
434 	}
435 
436 	while (!feof(file)) {
437 		/* Read a line from the file */
438 		s = lex(file);
439 
440 		/* skip blank lines and comments */
441 		if (s == NULL || *s == 0 || *s == '#')
442 			continue;
443 
444 		/*
445 		 * Beginning of a subtree and possibly a new block.
446 		 *
447 		 * If this is a new block, keep track of the beginning of
448 		 * the block. if there are directives later on, we need to
449 		 * apply that directive to all members of the block.
450 		 *
451 		 * If the first stmt in the file was an 'IGNORE all' or
452 		 * 'IGNORE contents', we need to keep track of it and
453 		 * automatically switch off contents checking for new
454 		 * subtrees.
455 		 */
456 		if (s[0] == '/') {
457 			new_rule = add_subtree_rule(s, reloc_root, create,
458 			    &ret_code);
459 
460 			s = lex(0);
461 			while ((s != NULL) && (*s != 0) && (*s != '#')) {
462 				add_modifier(new_rule, s);
463 				s = lex(0);
464 			}
465 
466 			/* Found a new block, keep track of the beginning */
467 			if (block_begin == NULL ||
468 			    (ignore_flag != 0) || (check_flag != 0)) {
469 				block_begin = new_rule;
470 				check_flag = 0;
471 				ignore_flag = 0;
472 			}
473 
474 			/* Apply global settings to this block, if any */
475 			init_rule(in_flags, new_rule);
476 		} else if (IGNORE_KEYWORD(s) || CHECK_KEYWORD(s)) {
477 			int check_kw;
478 
479 			if (IGNORE_KEYWORD(s)) {
480 				ignore_flag++;
481 				check_kw = 0;
482 			} else {
483 				check_flag++;
484 				check_kw = 1;
485 			}
486 
487 			/* Parse next token */
488 			s = lex(0);
489 			while ((s != NULL) && (*s != 0) && (*s != '#')) {
490 				akp = attr_keylookup(s);
491 				if (akp == NULL) {
492 					(void) fprintf(stderr, SYNTAX_ERR, s);
493 					syntax_err++;
494 					exit(2);
495 				}
496 
497 				/*
498 				 * For all the flags, check if this is a global
499 				 * IGNORE/CHECK. If so, set the global flag.
500 				 *
501 				 * NOTE: The only time you can have a
502 				 * global ignore is when its the
503 				 * stmt before any blocks have been
504 				 * spec'd.
505 				 */
506 				if (block_begin == NULL) {
507 					if (check_kw)
508 						in_flags |= akp->ak_flags;
509 					else
510 						in_flags &= ~(akp->ak_flags);
511 				} else {
512 					for (rp = block_begin; rp != NULL;
513 					    rp = rp->next) {
514 						if (check_kw)
515 							rp->attr_list |=
516 							    akp->ak_flags;
517 						else
518 							rp->attr_list &=
519 							    ~(akp->ak_flags);
520 					}
521 				}
522 
523 				/* Parse next token */
524 				s = lex(0);
525 			}
526 		} else {
527 			(void) fprintf(stderr, SYNTAX_ERR, s);
528 			s = lex(0);
529 			while (s != NULL && *s != 0) {
530 				(void) fprintf(stderr, " %s", s);
531 				s = lex(0);
532 			}
533 			(void) fprintf(stderr, "\n");
534 			syntax_err++;
535 		}
536 	}
537 
538 	(void) fclose(file);
539 
540 	if (syntax_err) {
541 		(void) fprintf(stderr, SYNTAX_ABORT);
542 		exit(2);
543 	}
544 
545 	return (ret_code);
546 }
547 
548 static void
549 add_modifier(struct rule *rule, char *modifier_str)
550 {
551 	struct tree_modifier	*new_mod_ptr, *curr_mod_ptr;
552 	struct rule		*this_rule;
553 
554 	this_rule = rule;
555 	while (this_rule != NULL) {
556 		new_mod_ptr = gen_tree_modifier();
557 		new_mod_ptr->mod_str = safe_strdup(modifier_str);
558 		/* Next, see if the pattern is an include or an exclude */
559 		if (new_mod_ptr->mod_str[0] == '!') {
560 			new_mod_ptr->mod_str = (new_mod_ptr->mod_str + 1);
561 			new_mod_ptr->include = B_FALSE;
562 		} else {
563 			new_mod_ptr->include = B_TRUE;
564 		}
565 
566 		if (this_rule->modifiers == NULL)
567 			this_rule->modifiers = new_mod_ptr;
568 		else {
569 			curr_mod_ptr = this_rule->modifiers;
570 			while (curr_mod_ptr->next != NULL)
571 				curr_mod_ptr = curr_mod_ptr->next;
572 
573 			curr_mod_ptr->next = new_mod_ptr;
574 		}
575 		this_rule = this_rule->next;
576 	}
577 }
578 
579 /*
580  * This funtion is invoked when reading rulesfiles.  A subtree may have
581  * wildcards in it, e.g., '/home/n*', which is expected to match all home
582  * dirs which start with an 'n'.
583  *
584  * This function needs to break down the subtree into its components.  For
585  * each component, see how many directories match.  Take the subtree list just
586  * generated and run it through again, this time looking at the next component.
587  * At each iteration, keep a linked list of subtrees that currently match.
588  * Once the final list is created, invoke add_single_rule() to create the
589  * rule struct with the correct information.
590  *
591  * This function returns a ptr to the first element in the block of subtrees
592  * which matched the subtree def'n in the rulesfile.
593  */
594 static struct rule *
595 add_subtree_rule(char *rule, char *reloc_root, int create, int *err_code)
596 {
597 	char			full_path[PATH_MAX], pattern[PATH_MAX],
598 				new_dirname[PATH_MAX], *beg_pattern,
599 				*end_pattern, *curr_dirname;
600 	struct	dir_component	*current_level = NULL, *next_level = NULL,
601 				*tmp_ptr;
602 	DIR			*dir_ptr;
603 	struct dirent		*dir_entry;
604 	struct rule		*begin_rule = NULL;
605 	int			ret;
606 	struct stat64		statb;
607 
608 	(void) snprintf(full_path, sizeof (full_path),
609 	    (rule[0] == '/') ? "%s%s" : "%s/%s", reloc_root, rule);
610 
611 	/*
612 	 * In the case of 'bart compare', don't validate
613 	 * the subtrees, since the machine running the
614 	 * comparison may not be the machine which generated
615 	 * the manifest.
616 	 */
617 	if (create == 0)
618 		return (add_single_rule(full_path));
619 
620 
621 	/* Insert 'current_level' into the linked list */
622 	add_dir(&current_level, NULL);
623 
624 	/* Special case: occurs when -R is "/" and the subtree is "/" */
625 	if (strcmp(full_path, "/") == 0)
626 		(void) strcpy(current_level->dirname, "/");
627 
628 	beg_pattern = full_path;
629 
630 	while (beg_pattern != NULL) {
631 		/*
632 		 * Extract the pathname component starting at 'beg_pattern'.
633 		 * Take those chars and put them into 'pattern'.
634 		 */
635 		while (*beg_pattern == '/')
636 			beg_pattern++;
637 		if (*beg_pattern == '\0')	/* end of pathname */
638 			break;
639 		end_pattern = strchr(beg_pattern, '/');
640 		if (end_pattern != NULL)
641 			(void) strlcpy(pattern, beg_pattern,
642 			    end_pattern - beg_pattern + 1);
643 		else
644 			(void) strlcpy(pattern, beg_pattern, sizeof (pattern));
645 		beg_pattern = end_pattern;
646 
647 		/*
648 		 * At this point, search for 'pattern' as a *subdirectory* of
649 		 * the dirs in the linked list.
650 		 */
651 		while (current_level != NULL) {
652 			/* curr_dirname used to make the code more readable */
653 			curr_dirname = current_level->dirname;
654 
655 			/* Initialization case */
656 			if (strlen(curr_dirname) == 0)
657 				(void) strcpy(curr_dirname, "/");
658 
659 			/* Open up the dir for this element in the list */
660 			dir_ptr = opendir(curr_dirname);
661 			dir_entry = NULL;
662 
663 			if (dir_ptr == NULL) {
664 				perror(curr_dirname);
665 				*err_code = WARNING_EXIT;
666 			} else
667 				dir_entry = readdir(dir_ptr);
668 
669 			/*
670 			 * Now iterate through the subdirs of 'curr_dirname'
671 			 * In the case of a match against 'pattern',
672 			 * add the path to the next linked list, which
673 			 * will be matched on the next iteration.
674 			 */
675 			while (dir_entry != NULL) {
676 				/* Skip the dirs "." and ".." */
677 				if ((strcmp(dir_entry->d_name, ".") == 0) ||
678 				    (strcmp(dir_entry->d_name, "..") == 0)) {
679 					dir_entry = readdir(dir_ptr);
680 					continue;
681 				}
682 				if (fnmatch(pattern, dir_entry->d_name,
683 				    FNM_PATHNAME) == 0) {
684 					/*
685 					 * Build 'new_dirname' which will be
686 					 * examined on the next iteration.
687 					 */
688 					if (curr_dirname[strlen(curr_dirname)-1]
689 									!= '/')
690 						(void) snprintf(new_dirname,
691 						    sizeof (new_dirname),
692 						    "%s/%s", curr_dirname,
693 						    dir_entry->d_name);
694 					else
695 						(void) snprintf(new_dirname,
696 						    sizeof (new_dirname),
697 						    "%s%s", curr_dirname,
698 						    dir_entry->d_name);
699 
700 					/* Add to the next lined list */
701 					add_dir(&next_level, new_dirname);
702 				}
703 				dir_entry = readdir(dir_ptr);
704 			}
705 
706 			/* Close directory */
707 			if (dir_ptr != NULL)
708 				(void) closedir(dir_ptr);
709 
710 			/* Free this entry and move on.... */
711 			tmp_ptr = current_level;
712 			current_level = current_level->next;
713 			free(tmp_ptr);
714 		}
715 
716 		/*
717 		 * OK, done with this level.  Move to the next level and
718 		 * advance the ptrs which indicate the component name.
719 		 */
720 		current_level = next_level;
721 		next_level = NULL;
722 	}
723 
724 	tmp_ptr = current_level;
725 
726 	/* Error case: the subtree doesn't exist! */
727 	if (current_level == NULL) {
728 		(void) fprintf(stderr, INVALID_SUBTREE, full_path);
729 		*err_code = WARNING_EXIT;
730 	}
731 
732 	/*
733 	 * Iterate through all the dirnames which match the pattern and
734 	 * add them to to global list of subtrees which must be examined.
735 	 */
736 	while (current_level != NULL) {
737 		/*
738 		 * Sanity check for 'bart create', make sure the subtree
739 		 * points to a valid object.
740 		 */
741 		ret = lstat64(current_level->dirname, &statb);
742 		if (ret < 0) {
743 			(void) fprintf(stderr, INVALID_SUBTREE,
744 			    current_level->dirname);
745 			current_level = current_level->next;
746 			*err_code = WARNING_EXIT;
747 			continue;
748 		}
749 
750 		if (begin_rule == NULL) {
751 			begin_rule =
752 			    add_single_rule(current_level->dirname);
753 		} else
754 			(void) add_single_rule(current_level->dirname);
755 
756 		current_level = current_level->next;
757 	}
758 
759 	/*
760 	 * Free up the memory and return a ptr to the first entry in the
761 	 * subtree block.  This is necessary for the parser, which may need
762 	 * to add modifier strings to all the elements in this block.
763 	 */
764 	dirs_cleanup(tmp_ptr);
765 
766 	return (begin_rule);
767 }
768 
769 
770 /*
771  * Add a single entry to the linked list of rules to be read.  Does not do
772  * the wildcard expansion of 'add_subtree_rule', so is much simpler.
773  */
774 static struct rule *
775 add_single_rule(char *path)
776 {
777 
778 	/*
779 	 * If the rules list does NOT exist, then create it.
780 	 * If the rules list does exist, then traverse the next element.
781 	 */
782 	if (first_rule == NULL) {
783 		first_rule = gen_rulestruct();
784 		current_rule = first_rule;
785 	} else {
786 		current_rule->next = gen_rulestruct();
787 		current_rule->next->prev = current_rule;
788 		current_rule = current_rule->next;
789 	}
790 
791 	/* Setup the rule struct, handle relocatable roots, i.e. '-R' option */
792 	(void) strlcpy(current_rule->subtree, path,
793 	    sizeof (current_rule->subtree));
794 
795 	return (current_rule);
796 }
797 
798 /*
799  * Code stolen from filesync utility, used by read_rules() to read in the
800  * rulesfile.
801  */
802 static char *
803 lex(FILE *file)
804 {
805 	char c, delim;
806 	char *p;
807 	char *s;
808 	static char *savep;
809 	static char namebuf[ BUF_SIZE ];
810 	static char inbuf[ BUF_SIZE ];
811 
812 	if (file) {			/* read a new line		*/
813 		p = inbuf + sizeof (inbuf);
814 
815 		s = inbuf;
816 		/* read the next input line, with all continuations	*/
817 		while (savep = fgets(s, p - s, file)) {
818 			lex_linenum++;
819 
820 			/* go find the last character of the input line	*/
821 			while (*s && s[1])
822 				s++;
823 			if (*s == '\n')
824 				s--;
825 
826 			/* see whether or not we need a continuation	*/
827 			if (s < inbuf || *s != '\\')
828 				break;
829 
830 			continue;
831 		}
832 
833 		if (savep == NULL)
834 			return (0);
835 
836 		s = inbuf;
837 	} else {			/* continue with old line	*/
838 		if (savep == NULL)
839 			return (0);
840 		s = savep;
841 	}
842 	savep = NULL;
843 
844 	/* skip over leading white space	*/
845 	while (isspace(*s))
846 		s++;
847 	if (*s == 0)
848 		return (0);
849 
850 	/* see if this is a quoted string	*/
851 	c = *s;
852 	if (c == '\'' || c == '"') {
853 		delim = c;
854 		s++;
855 	} else
856 		delim = 0;
857 
858 	/* copy the token into the buffer	*/
859 	for (p = namebuf; (c = *s) != 0; s++) {
860 		/* literal escape		*/
861 		if (c == '\\') {
862 			s++;
863 			*p++ = *s;
864 			continue;
865 		}
866 
867 		/* closing delimiter		*/
868 		if (c == delim) {
869 			s++;
870 			break;
871 		}
872 
873 		/* delimiting white space	*/
874 		if (delim == 0 && isspace(c))
875 			break;
876 
877 		/* ordinary characters		*/
878 		*p++ = *s;
879 	}
880 
881 
882 	/* remember where we left off		*/
883 	savep = *s ? s : 0;
884 
885 	/* null terminate and return the buffer	*/
886 	*p = 0;
887 	return (namebuf);
888 }
889 
890 /*
891  * Iterate through the dir strcutures and free memory.
892  */
893 static void
894 dirs_cleanup(struct dir_component *dir)
895 {
896 	struct	dir_component	*next;
897 
898 	while (dir != NULL) {
899 		next = dir->next;
900 		free(dir);
901 		dir = next;
902 	}
903 }
904 
905 /*
906  * Create and initialize a new dir structure.  Used by add_subtree_rule() when
907  * doing expansion of directory names caused by wildcards.
908  */
909 static void
910 add_dir(struct dir_component **dir, char *dirname)
911 {
912 	struct	dir_component	*new, *next_dir;
913 
914 	new = gen_dir_component();
915 	if (dirname != NULL)
916 		(void) strlcpy(new->dirname, dirname, sizeof (new->dirname));
917 
918 	if (*dir == NULL)
919 		*dir = new;
920 	else {
921 		next_dir = *dir;
922 		while (next_dir->next != NULL)
923 			next_dir = next_dir->next;
924 
925 		next_dir->next = new;
926 	}
927 }
928 
929 /*
930  * Traverse the linked list of rules in a REVERSE order.
931  */
932 static struct rule *
933 get_last_entry(boolean_t reset)
934 {
935 	static struct rule	*curr_root = NULL;
936 
937 	if (reset) {
938 
939 		curr_root = first_rule;
940 
941 		/* RESET: set cur_root to the end of the list */
942 		while (curr_root != NULL)
943 			if (curr_root->next == NULL)
944 				break;
945 			else
946 				curr_root = curr_root->next;
947 	} else
948 		curr_root = (curr_root->prev);
949 
950 	return (curr_root);
951 }
952 
953 /*
954  * Traverse the first entry, used by 'bart create' to iterate through
955  * subtrees or individual filenames.
956  */
957 struct rule *
958 get_first_subtree()
959 {
960 	return (first_rule);
961 }
962 
963 /*
964  * Traverse the next entry, used by 'bart create' to iterate through
965  * subtrees or individual filenames.
966  */
967 struct rule *
968 get_next_subtree(struct rule *entry)
969 {
970 	return (entry->next);
971 }
972 
973 char *
974 safe_strdup(char *s)
975 {
976 	char *ret;
977 	size_t len;
978 
979 	len = strlen(s) + 1;
980 	ret = safe_calloc(len);
981 	(void) strlcpy(ret, s, len);
982 	return (ret);
983 }
984 
985 /*
986  * Function to match a filename against the subtrees in the link list
987  * of 'rule' strcutures.  Upon finding a matching rule, see if it should
988  * be excluded.  Keep going until a match is found OR all rules have been
989  * exhausted.
990  * NOTES: Rules are parsed in reverse;
991  * satisfies the spec that "Last rule wins".  Also, the default rule should
992  * always match, so this function should NEVER return NULL.
993  */
994 struct rule *
995 check_rules(const char *fname, char type)
996 {
997 	struct rule		*root;
998 
999 	root = get_last_entry(B_TRUE);
1000 	while (root != NULL) {
1001 		if (match_subtree(fname, root->subtree)) {
1002 			if (exclude_fname(fname, type, root) == 0)
1003 				break;
1004 		}
1005 		root = get_last_entry(B_FALSE);
1006 	}
1007 
1008 	return (root);
1009 }
1010 
1011 /*
1012  * Function to determine if an entry in a rules file (see bart_rules(4)) applies
1013  * to a filename. We truncate "fname" such that it has the same number of
1014  * components as "rule" and let fnmatch(3C) do the rest. A "component" is one
1015  * part of an fname as delimited by slashes ('/'). So "/A/B/C/D" has four
1016  * components: "A", "B", "C" and "D".
1017  *
1018  * For example:
1019  *
1020  * 1. the rule "/home/nickiso" applies to fname "/home/nickiso/src/foo.c" so
1021  * should match.
1022  *
1023  * 2. the rule "/home/nickiso/temp/src" does not apply to fname
1024  * "/home/nickiso/foo.c" so should not match.
1025  */
1026 static int
1027 match_subtree(const char *fname, char *rule)
1028 {
1029 	int	match, num_rule_slash;
1030 	char	*ptr, fname_cp[PATH_MAX];
1031 
1032 	/* If rule has more components than fname, it cannot match. */
1033 	if ((num_rule_slash = count_slashes(rule)) > count_slashes(fname))
1034 		return (0);
1035 
1036 	/* Create a copy of fname that we can truncate. */
1037 	(void) strlcpy(fname_cp, fname, sizeof (fname_cp));
1038 
1039 	/*
1040 	 * Truncate fname_cp such that it has the same number of components
1041 	 * as rule. If rule ends with '/', so should fname_cp. ie:
1042 	 *
1043 	 * rule		fname			fname_cp	matches
1044 	 * ----		-----			--------	-------
1045 	 * /home/dir*	/home/dir0/dir1/fileA	/home/dir0	yes
1046 	 * /home/dir/	/home/dir0/dir1/fileA	/home/dir0/	no
1047 	 */
1048 	for (ptr = fname_cp; num_rule_slash > 0; num_rule_slash--, ptr++)
1049 		ptr = strchr(ptr, '/');
1050 	if (*(rule + strlen(rule) - 1) != '/') {
1051 		while (*ptr != '\0') {
1052 			if (*ptr == '/')
1053 				break;
1054 			ptr++;
1055 		}
1056 	}
1057 	*ptr = '\0';
1058 
1059 	/* OK, now see if they match. */
1060 	match = fnmatch(rule, fname_cp, FNM_PATHNAME);
1061 
1062 	/* No match, return failure */
1063 	if (match != 0)
1064 		return (0);
1065 	else
1066 		return (1);
1067 }
1068 
1069 void
1070 process_glob_ignores(char *ignore_list, uint_t *flags)
1071 {
1072 	char	*cp;
1073 	struct attr_keyword *akp;
1074 
1075 	if (ignore_list == NULL)
1076 		usage();
1077 
1078 	cp = strtok(ignore_list, ",");
1079 	while (cp != NULL) {
1080 		akp = attr_keylookup(cp);
1081 		if (akp == NULL)
1082 			(void) fprintf(stderr, "ERROR: Invalid keyword %s\n",
1083 			    cp);
1084 		else
1085 			*flags &= ~akp->ak_flags;
1086 		cp = strtok(NULL, ",");
1087 	}
1088 }
1089