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