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