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
30static int count_slashes(const char *);
31static struct rule *gen_rulestruct(void);
32static struct tree_modifier *gen_tree_modifier(void);
33static struct dir_component *gen_dir_component(void);
34static void init_rule(uint_t, struct rule *);
35static void add_modifier(struct rule *, char *);
36static struct rule *add_subtree_rule(char *, char *, int, int *);
37static struct rule *add_single_rule(char *);
38static void dirs_cleanup(struct dir_component *);
39static void add_dir(struct dir_component **, char *);
40static char *lex(FILE *);
41static int match_subtree(const char *, char *);
42static struct rule *get_last_entry(boolean_t);
43
44static int	lex_linenum;	/* line number in current input file	*/
45static 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 */
64int
65exclude_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
341static int
342count_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
352static struct rule *
353gen_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
361static struct tree_modifier *
362gen_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
371static struct dir_component *
372gen_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 */
384static struct rule *
385setup_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 */
399static void
400init_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 */
412int
413read_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 */
555static void
556add_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 */
610static struct rule *
611add_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 */
790static struct rule *
791add_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 */
818static char *
819lex(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 */
909static void
910dirs_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 */
925static void
926add_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 */
948static struct rule *
949get_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 */
973struct rule *
974get_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 */
983struct rule *
984get_next_subtree(struct rule *entry)
985{
986	return (entry->next);
987}
988
989char *
990safe_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 */
1010struct rule *
1011check_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 */
1042static int
1043match_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
1085void
1086process_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