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