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