# # CDDL HEADER START # # The contents of this file are subject to the terms of the # Common Development and Distribution License (the "License"). # You may not use this file except in compliance with the License. # # You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE # or http://www.opensolaris.org/os/licensing. # See the License for the specific language governing permissions # and limitations under the License. # # When distributing Covered Code, include this CDDL HEADER in each # file and include the License file at usr/src/OPENSOLARIS.LICENSE. # If applicable, add the following below this CDDL HEADER, with the # fields enclosed by brackets "[]" replaced with your own identifying # information: Portions Copyright [yyyy] [name of copyright owner] # # CDDL HEADER END # # # Copyright 2006 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. PROG= bart SRCS= rules.c create.c compare.c main.c lutbl.c OBJS= rules.o create.o compare.o main.o lutbl.o BART= bart include ../Makefile.cmd LDLIBS += -lsec -lmd CERRWARN += -Wno-parentheses CERRWARN += $(CNOWARN_UNINIT) SMOFF += indenting # # for messaging catalog # POFILE= bart.po DCFILE= bart.dc ROOTBINBART= $(BART:%=$(ROOTBIN)/%) ROOTLIBDIFFH= $(DIFFH:%=$(ROOTLIB)/%) .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) -o $(PROG) $(OBJS) $(LDLIBS) $(POST_PROCESS) clean: $(RM) $(OBJS) # # Use private rule # $(POFILE): $(RM) $@ $(COMPILE.cpp) $(SRCS) > bart.po.i $(XGETTEXT) $(XGETFLAGS) bart.po.i sed "/^domain/d" messages.po > $@ $(RM) bart.po.i messages.po $(DCFILE): $(RM) $@ $(COMPILE.cpp) $(SRCS) > bart.dc.i $(XGETTEXT) -c TRANSLATION_NOTE_FOR_DC -t bart.dc.i sed "/^domain/d" messages.po > $@ $(RM) bart.dc.i messages.po install: all $(ROOTBINBART) $(ROOTLIBDIFFH) clean: lint: lint_SRCS include ../Makefile.targ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _BART_H #define _BART_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include #include #include "msgs.h" #define EXIT 0 #define WARNING_EXIT 1 #define FATAL_EXIT 2 #define NO_EXCLUDE 0 #define EXCLUDE_SKIP 1 #define EXCLUDE_PRUNE 2 #define CHECK 0 #define NOCHECK 1 #define CHECK_KEYWORD(s) (strcmp(s, "CHECK") == 0) #define IGNORE_KEYWORD(s) (strcmp(s, "IGNORE") == 0) #define ALL_KEYWORD "all" #define CONTENTS_KEYWORD "contents" #define TYPE_KEYWORD "type" #define SIZE_KEYWORD "size" #define MODE_KEYWORD "mode" #define ACL_KEYWORD "acl" #define UID_KEYWORD "uid" #define GID_KEYWORD "gid" #define MTIME_KEYWORD "mtime" #define LNMTIME_KEYWORD "lnmtime" #define DIRMTIME_KEYWORD "dirmtime" #define DEST_KEYWORD "dest" #define DEVNODE_KEYWORD "devnode" #define ADD_KEYWORD "add" #define DELETE_KEYWORD "delete" #define MANIFEST_VER "! Version 1.0\n" #define FORMAT_STR "# Format:\n\ #fname D size mode acl dirmtime uid gid\n\ #fname P size mode acl mtime uid gid\n\ #fname S size mode acl mtime uid gid\n\ #fname F size mode acl mtime uid gid contents\n\ #fname L size mode acl lnmtime uid gid dest\n\ #fname B size mode acl mtime uid gid devnode\n\ #fname C size mode acl mtime uid gid devnode\n" /* * size of buffer - used in several places */ #define BUF_SIZE 65536 /* * size of ACL buffer - used in several places */ #define ACL_SIZE 1024 /* * size of MISC buffer - used in several places */ #define MISC_SIZE 20 /* * size of TYPE buffer - used in several places */ #define TYPE_SIZE 2 struct tree_modifier { char *mod_str; boolean_t include; boolean_t is_dir; struct tree_modifier *next; }; struct attr_keyword { char *ak_name; int ak_flags; }; #define ATTR_ALL ((uint_t)~0) #define ATTR_CONTENTS 0x0001 #define ATTR_TYPE 0x0002 #define ATTR_SIZE 0x0004 #define ATTR_MODE 0x0008 #define ATTR_UID 0x0010 #define ATTR_GID 0x0020 #define ATTR_ACL 0x0040 #define ATTR_DEST 0x0080 #define ATTR_DEVNODE 0x0100 #define ATTR_MTIME 0x0200 #define ATTR_LNMTIME 0x0400 #define ATTR_DIRMTIME 0x0800 #define ATTR_ADD 0x1000 #define ATTR_DELETE 0x2000 struct rule { char subtree[PATH_MAX]; uint_t attr_list; struct tree_modifier *modifiers; struct rule *next; struct rule *prev; }; struct dir_component { char dirname[PATH_MAX]; struct dir_component *next; }; struct attr_keyword *attr_keylookup(char *); void usage(void); int bart_create(int, char **); int bart_compare(int, char **); struct rule *check_rules(const char *, char); int exclude_fname(const char *, char, struct rule *); struct rule *get_first_subtree(void); struct rule *get_next_subtree(struct rule *); void process_glob_ignores(char *, uint_t *); void *safe_calloc(size_t); char *safe_strdup(char *); int read_rules(FILE *, char *, uint_t, int); int read_line(FILE *, char *, int, int, char **, char *); #ifdef __cplusplus } #endif #endif /* _BART_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include "bart.h" static int compare_manifests(FILE *rulesfile, char *control, char *test, boolean_t prog_fmt, uint_t flags); static void extract_fname_ftype(char *line, char *fname, char *type); static int report_add(char *fname, char *type); static int report_delete(char *fname, char *type); static int evaluate_differences(char *control_line, char *test_line, boolean_t prog_fmt, int flags); static void report_error(char *fname, char *type, char *ctrl_val, char *test_val, boolean_t prog_fmt); static int read_manifest_line(FILE *fd, char *buf, int buf_size, int start_pos, char **line, char *fname); static void parse_line(char *line, char *fname, char *type, char *size, char *mode, char *acl, char *mtime, char *uid, char *gid, char *contents, char *devnode, char *dest); static void init_default_flags(uint_t *flags); static void get_token(char *line, int *curr_pos, int line_len, char *buf, int buf_size); int bart_compare(int argc, char **argv) { char *control_fname, *test_fname; int c; FILE *rules_fd = NULL; uint_t glob_flags; boolean_t prog_fmt = B_FALSE; init_default_flags(&glob_flags); while ((c = getopt(argc, argv, "pr:i:")) != EOF) { switch (c) { case 'p': prog_fmt = B_TRUE; break; case 'r': if (optarg == NULL) usage(); if (strcmp(optarg, "-") == 0) rules_fd = stdin; else rules_fd = fopen(optarg, "r"); if (rules_fd == NULL) { perror(optarg); usage(); } break; case 'i': process_glob_ignores(optarg, &glob_flags); break; case '?': default: usage(); } } /* Make sure we have the right number of args */ if ((optind + 2) != argc) usage(); argv += optind; control_fname = argv[0]; test_fname = argv[1]; /* At this point, the filenames are sane, so do the comparison */ return (compare_manifests(rules_fd, control_fname, test_fname, prog_fmt, glob_flags)); } static int compare_manifests(FILE *rulesfile, char *control, char *test, boolean_t prog_fmt, uint_t flags) { FILE *control_fd, *test_fd; char *control_line, *test_line, control_buf[BUF_SIZE], test_buf[BUF_SIZE], control_fname[PATH_MAX], control_type[TYPE_SIZE], test_fname[PATH_MAX], test_type[TYPE_SIZE]; int control_pos, test_pos, ret, fname_cmp, return_status; return_status = EXIT; return_status = read_rules(rulesfile, "", flags, 0); control_fd = fopen(control, "r"); if (control_fd == NULL) { perror(control); return (FATAL_EXIT); } test_fd = fopen(test, "r"); if (test_fd == NULL) { perror(test); return (FATAL_EXIT); } control_pos = read_manifest_line(control_fd, control_buf, BUF_SIZE, 0, &control_line, control); test_pos = read_manifest_line(test_fd, test_buf, BUF_SIZE, 0, &test_line, test); while ((control_pos != -1) && (test_pos != -1)) { ret = strcmp(control_line, test_line); if (ret == 0) { /* Lines compare OK, just read the next lines.... */ control_pos = read_manifest_line(control_fd, control_buf, BUF_SIZE, control_pos, &control_line, control); test_pos = read_manifest_line(test_fd, test_buf, BUF_SIZE, test_pos, &test_line, test); continue; } /* * Something didn't compare properly. */ extract_fname_ftype(control_line, control_fname, control_type); extract_fname_ftype(test_line, test_fname, test_type); fname_cmp = strcmp(control_fname, test_fname); if (fname_cmp == 0) { /* * Filenames were the same, see what was * different and continue. */ if (evaluate_differences(control_line, test_line, prog_fmt, flags) != 0) return_status = WARNING_EXIT; control_pos = read_manifest_line(control_fd, control_buf, BUF_SIZE, control_pos, &control_line, control); test_pos = read_manifest_line(test_fd, test_buf, BUF_SIZE, test_pos, &test_line, test); } else if (fname_cmp > 0) { /* Filenames were different, a files was ADDED */ if (report_add(test_fname, test_type)) { report_error(test_fname, ADD_KEYWORD, NULL, NULL, prog_fmt); return_status = WARNING_EXIT; } test_pos = read_manifest_line(test_fd, test_buf, BUF_SIZE, test_pos, &test_line, test); } else if (fname_cmp < 0) { /* Filenames were different, a files was DELETED */ if (report_delete(control_fname, control_type)) { report_error(control_fname, DELETE_KEYWORD, NULL, NULL, prog_fmt); return_status = WARNING_EXIT; } control_pos = read_manifest_line(control_fd, control_buf, BUF_SIZE, control_pos, &control_line, control); } } /* * Entering this while loop means files were DELETED from the test * manifest. */ while (control_pos != -1) { (void) sscanf(control_line, "%1023s", control_fname); if (report_delete(control_fname, control_type)) { report_error(control_fname, DELETE_KEYWORD, NULL, NULL, prog_fmt); return_status = WARNING_EXIT; } control_pos = read_manifest_line(control_fd, control_buf, BUF_SIZE, control_pos, &control_line, control); } /* * Entering this while loop means files were ADDED to the test * manifest. */ while (test_pos != -1) { (void) sscanf(test_line, "%1023s", test_fname); if (report_add(test_fname, test_type)) { report_error(test_fname, ADD_KEYWORD, NULL, NULL, prog_fmt); return_status = WARNING_EXIT; } test_pos = read_manifest_line(test_fd, test_buf, BUF_SIZE, test_pos, &test_line, test); } (void) fclose(control_fd); (void) fclose(test_fd); /* For programmatic mode, add a newline for cosmetic reasons */ if (prog_fmt && (return_status != 0)) (void) printf("\n"); return (return_status); } static void parse_line(char *line, char *fname, char *type, char *size, char *mode, char *acl, char *mtime, char *uid, char *gid, char *contents, char *devnode, char *dest) { int pos, line_len; line_len = strlen(line); pos = 0; get_token(line, &pos, line_len, fname, PATH_MAX); get_token(line, &pos, line_len, type, TYPE_SIZE); get_token(line, &pos, line_len, size, MISC_SIZE); get_token(line, &pos, line_len, mode, MISC_SIZE); get_token(line, &pos, line_len, acl, ACL_SIZE); get_token(line, &pos, line_len, mtime, MISC_SIZE); get_token(line, &pos, line_len, uid, MISC_SIZE); get_token(line, &pos, line_len, gid, MISC_SIZE); /* Reset these fields... */ *contents = '\0'; *devnode = '\0'; *dest = '\0'; /* Handle filetypes which have a last field..... */ if (type[0] == 'F') get_token(line, &pos, line_len, contents, PATH_MAX); else if ((type[0] == 'B') || (type[0] == 'C')) get_token(line, &pos, line_len, devnode, PATH_MAX); else if (type[0] == 'L') get_token(line, &pos, line_len, dest, PATH_MAX); } static void get_token(char *line, int *curr_pos, int line_len, char *buf, int buf_size) { int cnt = 0; while (isspace(line[*curr_pos]) && (*curr_pos < line_len)) (*curr_pos)++; while (!isspace(line[*curr_pos]) && (*curr_pos < line_len) && (cnt < (buf_size-1))) { buf[cnt] = line[*curr_pos]; (*curr_pos)++; cnt++; } buf[cnt] = '\0'; } /* * Utility function: extract fname and type from this line */ static void extract_fname_ftype(char *line, char *fname, char *type) { int line_len, pos; pos = 0; line_len = strlen(line); get_token(line, &pos, line_len, fname, PATH_MAX); get_token(line, &pos, line_len, type, TYPE_SIZE); } /* * Utility function: tells us whether or not this addition should be reported * * Returns 0 if the discrepancy is ignored, non-zero if the discrepancy is * reported. */ static int report_add(char *fname, char *type) { struct rule *rule_ptr; rule_ptr = check_rules(fname, type[0]); if ((rule_ptr != NULL) && (rule_ptr->attr_list & ATTR_ADD)) return (1); else return (0); } /* * Utility function: tells us whether or not this deletion should be reported * * Returns 0 if the discrepancy is ignored, non-zero if the discrepancy is * reported. */ static int report_delete(char *fname, char *type) { struct rule *rule_ptr; rule_ptr = check_rules(fname, type[0]); if ((rule_ptr != NULL) && (rule_ptr->attr_list & ATTR_DELETE)) return (1); else return (0); } /* * This function takes in the two entries, which have been flagged as * different, breaks them up and reports discrepancies. Note, discrepancies * are affected by the 'CHECK' and 'IGNORE' stanzas which may apply to * these entries. * * Returns the number of discrepancies reported. */ static int evaluate_differences(char *control_line, char *test_line, boolean_t prog_fmt, int flags) { char ctrl_fname[PATH_MAX], test_fname[PATH_MAX], ctrl_type[TYPE_SIZE], test_type[TYPE_SIZE], ctrl_size[MISC_SIZE], ctrl_mode[MISC_SIZE], ctrl_acl[ACL_SIZE], ctrl_mtime[MISC_SIZE], ctrl_uid[MISC_SIZE], ctrl_gid[MISC_SIZE], ctrl_dest[PATH_MAX], ctrl_contents[PATH_MAX], ctrl_devnode[PATH_MAX], test_size[MISC_SIZE], test_mode[MISC_SIZE], test_acl[ACL_SIZE], test_mtime[MISC_SIZE], test_uid[MISC_SIZE], test_gid[MISC_SIZE], test_dest[PATH_MAX], test_contents[PATH_MAX], test_devnode[PATH_MAX], *tag; int ret_val; struct rule *rule_ptr; ret_val = 0; parse_line(control_line, ctrl_fname, ctrl_type, ctrl_size, ctrl_mode, ctrl_acl, ctrl_mtime, ctrl_uid, ctrl_gid, ctrl_contents, ctrl_devnode, ctrl_dest); /* * Now we know the fname and type, let's get the rule that matches this * manifest entry. If there is a match, make sure to setup the * correct reporting flags. */ rule_ptr = check_rules(ctrl_fname, ctrl_type[0]); if (rule_ptr != NULL) flags = rule_ptr->attr_list; parse_line(test_line, test_fname, test_type, test_size, test_mode, test_acl, test_mtime, test_uid, test_gid, test_contents, test_devnode, test_dest); /* * Report the errors based upon which keywords have been set by * the user. */ if ((flags & ATTR_TYPE) && (ctrl_type[0] != test_type[0])) { report_error(ctrl_fname, TYPE_KEYWORD, ctrl_type, test_type, prog_fmt); ret_val++; } if ((flags & ATTR_SIZE) && (strcmp(ctrl_size, test_size) != 0)) { report_error(ctrl_fname, SIZE_KEYWORD, ctrl_size, test_size, prog_fmt); ret_val++; } if ((flags & ATTR_MODE) && (strcmp(ctrl_mode, test_mode) != 0)) { report_error(ctrl_fname, MODE_KEYWORD, ctrl_mode, test_mode, prog_fmt); ret_val++; } if ((flags & ATTR_ACL) && (strcmp(ctrl_acl, test_acl) != 0)) { report_error(ctrl_fname, ACL_KEYWORD, ctrl_acl, test_acl, prog_fmt); ret_val++; } if ((flags & ATTR_MTIME) && (ctrl_type[0] == test_type[0])) { if (strcmp(ctrl_mtime, test_mtime) != 0) { switch (ctrl_type[0]) { case 'D': tag = "dirmtime"; break; case 'L': tag = "lnmtime"; break; default: tag = "mtime"; break; } if (flags == 0) { report_error(ctrl_fname, tag, ctrl_mtime, test_mtime, prog_fmt); ret_val++; } } if ((ctrl_type[0] == 'F') && (flags & ATTR_MTIME) && (strcmp(ctrl_mtime, test_mtime) != 0)) { report_error(ctrl_fname, MTIME_KEYWORD, ctrl_mtime, test_mtime, prog_fmt); ret_val++; } if ((ctrl_type[0] == 'D') && (flags & ATTR_DIRMTIME) && (strcmp(ctrl_mtime, test_mtime) != 0)) { report_error(ctrl_fname, DIRMTIME_KEYWORD, ctrl_mtime, test_mtime, prog_fmt); ret_val++; } if ((ctrl_type[0] == 'L') && (flags & ATTR_LNMTIME) && (strcmp(ctrl_mtime, test_mtime) != 0)) { report_error(ctrl_fname, LNMTIME_KEYWORD, ctrl_mtime, test_mtime, prog_fmt); ret_val++; } } else if ((flags & ATTR_MTIME) && (strcmp(ctrl_mtime, test_mtime) != 0)) { report_error(ctrl_fname, MTIME_KEYWORD, ctrl_mtime, test_mtime, prog_fmt); ret_val++; } if ((flags & ATTR_UID) && (strcmp(ctrl_uid, test_uid) != 0)) { report_error(ctrl_fname, UID_KEYWORD, ctrl_uid, test_uid, prog_fmt); ret_val++; } if ((flags & ATTR_GID) && (strcmp(ctrl_gid, test_gid) != 0)) { report_error(ctrl_fname, GID_KEYWORD, ctrl_gid, test_gid, prog_fmt); ret_val++; } if ((flags & ATTR_DEVNODE) && (strcmp(ctrl_devnode, test_devnode) != 0)) { report_error(ctrl_fname, DEVNODE_KEYWORD, ctrl_devnode, test_devnode, prog_fmt); ret_val++; } if ((flags & ATTR_DEST) && (strcmp(ctrl_dest, test_dest) != 0)) { report_error(ctrl_fname, DEST_KEYWORD, ctrl_dest, test_dest, prog_fmt); ret_val++; } if ((flags & ATTR_CONTENTS) && (strcmp(ctrl_contents, test_contents)) != 0) { report_error(ctrl_fname, CONTENTS_KEYWORD, ctrl_contents, test_contents, prog_fmt); ret_val++; } return (ret_val); } /* * Function responsible for reporting errors. */ static void report_error(char *fname, char *type, char *ctrl_val, char *test_val, boolean_t prog_fmt) { static char last_fname[PATH_MAX] = ""; if (!prog_fmt) { /* Verbose mode */ if (strcmp(fname, last_fname) != 0) { (void) printf("%s:\n", fname); (void) strlcpy(last_fname, fname, sizeof (last_fname)); } if (strcmp(type, ADD_KEYWORD) == 0 || strcmp(type, DELETE_KEYWORD) == 0) (void) printf(" %s\n", type); else (void) printf(" %s control:%s test:%s\n", type, ctrl_val, test_val); } else { /* Programmatic mode */ if (strcmp(fname, last_fname) != 0) { /* Ensure a line is not printed for the initial case */ if (strlen(last_fname) != 0) (void) printf("\n"); (void) strlcpy(last_fname, fname, sizeof (last_fname)); (void) printf("%s ", fname); } (void) printf("%s ", type); if (strcmp(type, ADD_KEYWORD) != 0 && strcmp(type, DELETE_KEYWORD) != 0) { (void) printf("%s ", ctrl_val); (void) printf("%s ", test_val); } } } /* * Function responsible for reading in a line from the manifest. * Takes in the file ptr and a buffer, parses the buffer and sets the 'line' * ptr correctly. In the case when the buffer is fully parsed, this function * reads more data from the file ptr and refills the buffer. */ static int read_manifest_line(FILE *fd, char *buf, int buf_size, int start_pos, char **line, char *fname) { int end_pos, len, iscomment = 0, filepos; /* * Initialization case: make sure the manifest version is OK */ if (start_pos == 0) { end_pos = 0; buf[0] = '\0'; filepos = ftell(fd); (void) fread((void *) buf, (size_t)buf_size, (size_t)1, fd); *line = buf; if (filepos == 0) { if (strncmp(buf, MANIFEST_VER, strlen(MANIFEST_VER)) != 0) (void) fprintf(stderr, MISSING_VER, fname); if ((*line[0] == '!') || (*line[0] == '#')) iscomment++; while (iscomment) { while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') && (end_pos < buf_size)) end_pos++; if (end_pos >= buf_size) return (-1); end_pos++; *line = &(buf[end_pos]); iscomment = 0; if ((*line[0] == '!') || (*line[0] == '#')) iscomment++; } } while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') && (end_pos < buf_size)) end_pos++; if (end_pos < buf_size) { if (buf[end_pos] == '\n') { buf[end_pos] = '\0'; return (end_pos); } if (buf[end_pos] == '\0') return (-1); } (void) fprintf(stderr, MANIFEST_ERR); exit(FATAL_EXIT); } end_pos = (start_pos+1); *line = &(buf[end_pos]); /* Read the buffer until EOL or the buffer is empty */ while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') && (end_pos < buf_size)) end_pos++; if (end_pos < buf_size) { /* Found the end of the line, normal exit */ if (buf[end_pos] == '\n') { buf[end_pos] = '\0'; return (end_pos); } /* No more input to read */ if (buf[end_pos] == '\0') return (-1); } /* * The following code takes the remainder of the buffer and * puts it at the beginning. The space after the remainder, which * is now at the beginning, is blanked. * At this point, read in more data and continue to find the EOL.... */ len = end_pos - (start_pos + 1); (void) memcpy(buf, &(buf[start_pos+1]), (size_t)len); (void) memset(&buf[len], '\0', (buf_size - len)); (void) fread((void *) &buf[len], (size_t)(buf_size-len), (size_t)1, fd); *line = buf; end_pos = len; /* Read the buffer until EOL or the buffer is empty */ while ((buf[end_pos] != '\n') && (buf[end_pos] != '\0') && (end_pos < buf_size)) end_pos++; if (end_pos < buf_size) { /* Found the end of the line, normal exit */ if (buf[end_pos] == '\n') { buf[end_pos] = '\0'; return (end_pos); } /* No more input to read */ if (buf[end_pos] == '\0') return (-1); } (void) fprintf(stderr, MANIFEST_ERR); exit(FATAL_EXIT); /* NOTREACHED */ } static void init_default_flags(uint_t *flags) { /* Default behavior: everything is checked *except* dirmtime */ *flags = ATTR_ALL & ~(ATTR_DIRMTIME); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include "bart.h" #include static int sanitize_reloc_root(char *root, size_t bufsize); static int create_manifest_filelist(char **argv, char *reloc_root); static int create_manifest_rule(char *reloc_root, FILE *rule_fp); static void output_manifest(void); static int eval_file(const char *fname, const struct stat64 *statb, struct FTW *ftwx); static char *sanitized_fname(const char *, boolean_t); static char *get_acl_string(const char *fname, const struct stat64 *statb, int *err_code); static int generate_hash(int fdin, char *hash_str); static int read_filelist(char *reloc_root, char **argv, char *buf, size_t bufsize); static int walker(const char *name, const struct stat64 *sp, int type, struct FTW *ftwx); /* * The following globals are necessary due to the "walker" function * provided by nftw(). Since there is no way to pass them through to the * walker function, they must be global. */ static int compute_chksum = 1, eval_err = 0; static struct rule *subtree_root; static char reloc_root[PATH_MAX]; static struct statvfs64 parent_vfs; int bart_create(int argc, char **argv) { boolean_t filelist_input; int ret, c, output_pipe[2]; FILE *rules_fd = NULL; pid_t pid; filelist_input = B_FALSE; reloc_root[0] = '\0'; while ((c = getopt(argc, argv, "Inr:R:")) != EOF) { switch (c) { case 'I': if (rules_fd != NULL) { (void) fprintf(stderr, "%s", INPUT_ERR); usage(); } filelist_input = B_TRUE; break; case 'n': compute_chksum = 0; break; case 'r': if (strcmp(optarg, "-") == 0) rules_fd = stdin; else rules_fd = fopen(optarg, "r"); if (rules_fd == NULL) { perror(optarg); usage(); } break; case 'R': (void) strlcpy(reloc_root, optarg, sizeof (reloc_root)); ret = sanitize_reloc_root(reloc_root, sizeof (reloc_root)); if (ret == 0) usage(); break; case '?': default : usage(); } } argv += optind; if (pipe(output_pipe) < 0) { perror(""); exit(FATAL_EXIT); } pid = fork(); if (pid < 0) { perror(NULL); exit(FATAL_EXIT); } /* * Break the creation of a manifest into two parts: the parent process * generated the data whereas the child process sorts the data. * * The processes communicate through the pipe. */ if (pid > 0) { /* * Redirect the stdout of this process so it goes into * output_pipe[0]. The output of this process will be read * by the child, which will sort the output. */ if (dup2(output_pipe[0], STDOUT_FILENO) != STDOUT_FILENO) { perror(NULL); exit(FATAL_EXIT); } (void) close(output_pipe[0]); (void) close(output_pipe[1]); if (filelist_input == B_TRUE) { ret = create_manifest_filelist(argv, reloc_root); } else { ret = create_manifest_rule(reloc_root, rules_fd); } /* Close stdout so the sort in the child proc will complete */ (void) fclose(stdout); } else { /* * Redirect the stdin of this process so its read in from * the pipe, which is the parent process in this case. */ if (dup2(output_pipe[1], STDIN_FILENO) != STDIN_FILENO) { perror(NULL); exit(FATAL_EXIT); } (void) close(output_pipe[0]); output_manifest(); } /* Wait for the child proc (the sort) to complete */ (void) wait(0); return (ret); } /* * Handle the -R option and sets 'root' to be the absolute path of the * relocatable root. This is useful when the user specifies '-R ../../foo'. * * Return code is whether or not the location spec'd by the -R flag is a * directory or not. */ static int sanitize_reloc_root(char *root, size_t bufsize) { char pwd[PATH_MAX]; /* * First, save the current directory and go to the location * specified with the -R option. */ (void) getcwd(pwd, sizeof (pwd)); if (chdir(root) < 0) { /* Failed to change directory, something is wrong.... */ perror(root); return (0); } /* * Save the absolute path of the relocatable root directory. */ (void) getcwd(root, bufsize); /* * Now, go back to where we started, necessary for picking up a rules * file. */ if (chdir(pwd) < 0) { /* Failed to change directory, something is wrong.... */ perror(root); return (0); } /* * Make sure the path returned does not have a trailing /. This * can only happen when the entire pathname is "/". */ if (strcmp(root, "/") == 0) root[0] = '\0'; /* * Since the earlier chdir() succeeded, return success. */ return (1); } /* * This is the worker bee which creates the manifest based upon the command * line options supplied by the user. * * NOTE: create_manifest() eventually outputs data to a pipe, which is read in * by the child process. The child process is running output_manifest(), which * is responsible for generating sorted output. */ static int create_manifest_rule(char *reloc_root, FILE *rule_fp) { struct rule *root; int ret_status = EXIT; uint_t flags; if (compute_chksum) flags = ATTR_CONTENTS; else flags = 0; ret_status = read_rules(rule_fp, reloc_root, flags, 1); /* Loop through every single subtree */ for (root = get_first_subtree(); root != NULL; root = get_next_subtree(root)) { /* * Check to see if this subtree should have contents * checking turned on or off. * * NOTE: The 'compute_chksum' and 'parent_vfs' * are a necessary hack: the variables are used in * walker(), both directly and indirectly. Since * the parameters to walker() are defined by nftw(), * the globals are really a backdoor mechanism. */ ret_status = statvfs64(root->subtree, &parent_vfs); if (ret_status < 0) { perror(root->subtree); continue; } /* * Walk the subtree and invoke the callback function walker() * Use FTW_ANYERR to get FTW_NS and FTW_DNR entries *and* * to continue past those errors. */ subtree_root = root; (void) nftw64(root->subtree, &walker, 20, FTW_PHYS|FTW_ANYERR); /* * Ugly but necessary: * * walker() must return 0, or the tree walk will stop, * so warning flags must be set through a global. */ if (eval_err == WARNING_EXIT) ret_status = WARNING_EXIT; } return (ret_status); } static int create_manifest_filelist(char **argv, char *reloc_root) { int ret_status = EXIT; char input_fname[PATH_MAX]; while (read_filelist(reloc_root, argv, input_fname, sizeof (input_fname)) != -1) { struct stat64 stat_buf; int ret; ret = lstat64(input_fname, &stat_buf); if (ret < 0) { ret_status = WARNING_EXIT; perror(input_fname); } else { ret = eval_file(input_fname, &stat_buf, NULL); if (ret == WARNING_EXIT) ret_status = WARNING_EXIT; } } return (ret_status); } /* * output_manifest() the child process. It reads in the output from * create_manifest() and sorts it. */ static void output_manifest(void) { char *env[] = {"LC_CTYPE=C", "LC_COLLATE=C", "LC_NUMERIC=C", NULL}; time_t time_val; struct tm *tm; char time_buf[1024]; (void) printf("%s", MANIFEST_VER); time_val = time((time_t)0); tm = localtime(&time_val); (void) strftime(time_buf, sizeof (time_buf), "%A, %B %d, %Y (%T)", tm); (void) printf("! %s\n", time_buf); (void) printf("%s", FORMAT_STR); (void) fflush(stdout); /* * Simply run sort and read from the the current stdin, which is really * the output of create_manifest(). * Also, make sure the output is unique, since a given file may be * included by several stanzas. */ if (execle("/bin/sort", "sort", "-u", NULL, env) < 0) { perror(""); exit(FATAL_EXIT); } /*NOTREACHED*/ } /* * Callback function for nftw() */ static int walker(const char *name, const struct stat64 *sp, int type, struct FTW *ftwx) { int ret; struct statvfs64 path_vfs; boolean_t dir_flag = B_FALSE; struct rule *rule; switch (type) { case FTW_F: /* file */ rule = check_rules(name, 'F'); if (rule != NULL) { if (rule->attr_list & ATTR_CONTENTS) compute_chksum = 1; else compute_chksum = 0; } break; case FTW_SL: /* symbolic link, FTW_PHYS */ case FTW_SLN: /* symbolic link, ~FTW_PHYS */ break; case FTW_DP: /* end of directory, FTW_DEPTH */ case FTW_D: /* enter directory, ~FTW_DEPTH */ dir_flag = B_TRUE; ret = statvfs64(name, &path_vfs); if (ret < 0) eval_err = WARNING_EXIT; break; case FTW_NS: /* unstatable file */ (void) fprintf(stderr, UNKNOWN_FILE, name); eval_err = WARNING_EXIT; return (0); case FTW_DNR: /* unreadable directory */ (void) fprintf(stderr, CANTLIST_DIR, name); eval_err = WARNING_EXIT; return (0); default: (void) fprintf(stderr, INTERNAL_ERR, name); eval_err = WARNING_EXIT; return (0); } /* This is the function which really processes the file */ ret = eval_file(name, sp, ftwx); /* * Since the parameters to walker() are constrained by nftw(), * need to use a global to reflect a WARNING. Sigh. */ if (ret == WARNING_EXIT) eval_err = WARNING_EXIT; /* * This is a case of a directory which crosses into a mounted * filesystem of a different type, e.g., UFS -> NFS. * BART should not walk the new filesystem (by specification), so * set this consolidation-private flag so the rest of the subtree * under this directory is not waled. */ if (dir_flag && (strcmp(parent_vfs.f_basetype, path_vfs.f_basetype) != 0)) ftwx->quit = FTW_PRUNE; return (0); } /* * This file does the per-file evaluation and is run to generate every entry * in the manifest. * * All output is written to a pipe which is read by the child process, * which is running output_manifest(). */ static int eval_file(const char *fname, const struct stat64 *statb, struct FTW *ftwx) { int fd, ret, err_code, i, result; char last_field[PATH_MAX], ftype, *acl_str; char *quoted_name; err_code = EXIT; switch (statb->st_mode & S_IFMT) { /* Regular file */ case S_IFREG: ftype = 'F'; break; /* Directory */ case S_IFDIR: ftype = 'D'; break; /* Block Device */ case S_IFBLK: ftype = 'B'; break; /* Character Device */ case S_IFCHR: ftype = 'C'; break; /* Named Pipe */ case S_IFIFO: ftype = 'P'; break; /* Socket */ case S_IFSOCK: ftype = 'S'; break; /* Door */ case S_IFDOOR: ftype = 'O'; break; /* Symbolic link */ case S_IFLNK: ftype = 'L'; break; default: ftype = '-'; break; } /* First, make sure this file should be cataloged */ if ((subtree_root != NULL) && ((result = exclude_fname(fname, ftype, subtree_root)) != NO_EXCLUDE)) { if ((result == EXCLUDE_PRUNE) && (ftwx != (struct FTW *)NULL)) ftwx->quit = FTW_PRUNE; return (err_code); } for (i = 0; i < PATH_MAX; i++) last_field[i] = '\0'; /* * Regular files, compute the MD5 checksum and put it into 'last_field' * UNLESS instructed to ignore the checksums. */ if (ftype == 'F') { if (compute_chksum) { fd = open(fname, O_RDONLY|O_LARGEFILE); if (fd < 0) { err_code = WARNING_EXIT; perror(fname); /* default value since the computution failed */ (void) strcpy(last_field, "-"); } else { if (generate_hash(fd, last_field) != 0) { err_code = WARNING_EXIT; (void) fprintf(stderr, CONTENTS_WARN, fname); (void) strcpy(last_field, "-"); } } (void) close(fd); } /* Instructed to ignore checksums, just put in a '-' */ else (void) strcpy(last_field, "-"); } /* * For symbolic links, put the destination of the symbolic link into * 'last_field' */ if (ftype == 'L') { ret = readlink(fname, last_field, sizeof (last_field)); if (ret < 0) { err_code = WARNING_EXIT; perror(fname); /* default value since the computation failed */ (void) strcpy(last_field, "-"); } else (void) strlcpy(last_field, sanitized_fname(last_field, B_FALSE), sizeof (last_field)); /* * Boundary condition: possible for a symlink to point to * nothing [ ln -s '' link_name ]. For this case, set the * destination to "\000". */ if (strlen(last_field) == 0) (void) strcpy(last_field, "\\000"); } acl_str = get_acl_string(fname, statb, &err_code); /* Sanitize 'fname', so its in the proper format for the manifest */ quoted_name = sanitized_fname(fname, B_TRUE); /* Start to build the entry.... */ (void) printf("%s %c %d %o %s %x %d %d", quoted_name, ftype, (int)statb->st_size, (int)statb->st_mode, acl_str, (int)statb->st_mtime, (int)statb->st_uid, (int)statb->st_gid); /* Finish it off based upon whether or not it's a device node */ if ((ftype == 'B') || (ftype == 'C')) (void) printf(" %x\n", (int)statb->st_rdev); else if (strlen(last_field) > 0) (void) printf(" %s\n", last_field); else (void) printf("\n"); /* free the memory consumed */ free(acl_str); free(quoted_name); return (err_code); } /* * When creating a manifest, make sure all '?', tabs, space, newline, '/' * and '[' are all properly quoted. Convert them to a "\ooo" where the 'ooo' * represents their octal value. For filesystem objects, as opposed to symlink * targets, also canonicalize the pathname. */ static char * sanitized_fname(const char *fname, boolean_t canon_path) { const char *ip; unsigned char ch; char *op, *quoted_name; /* Initialize everything */ quoted_name = safe_calloc((4 * PATH_MAX) + 1); ip = fname; op = quoted_name; if (canon_path) { /* * In the case when a relocatable root was used, the relocatable * root should *not* be part of the manifest. */ ip += strlen(reloc_root); /* * In the case when the '-I' option was used, make sure * the quoted_name starts with a '/'. */ if (*ip != '/') *op++ = '/'; } /* Now walk through 'fname' and build the quoted string */ while ((ch = *ip++) != 0) { switch (ch) { /* Quote the following characters */ case ' ': case '*': case '\n': case '?': case '[': case '\\': case '\t': op += sprintf(op, "\\%.3o", (unsigned char)ch); break; /* Otherwise, simply append them */ default: *op++ = ch; break; } } *op = 0; return (quoted_name); } /* * Function responsible for generating the ACL information for a given * file. Note, the string is put into buffer malloc'd by this function. * It's the responsibility of the caller to free the buffer. This function * should never return a NULL pointer. */ static char * get_acl_string(const char *fname, const struct stat64 *statb, int *err_code) { acl_t *aclp; char *acltext; int error; if (S_ISLNK(statb->st_mode)) { return (safe_strdup("-")); } /* * Include trivial acl's */ error = acl_get(fname, 0, &aclp); if (error != 0) { *err_code = WARNING_EXIT; (void) fprintf(stderr, "%s: %s\n", fname, acl_strerror(error)); return (safe_strdup("-")); } else { acltext = acl_totext(aclp, 0); acl_free(aclp); if (acltext == NULL) return (safe_strdup("-")); else return (acltext); } } /* * * description: This routine reads stdin in BUF_SIZE chunks, uses the bits * to update the md5 hash buffer, and outputs the chunks * to stdout. When stdin is exhausted, the hash is computed, * converted to a hexadecimal string, and returned. * * returns: The md5 hash of stdin, or NULL if unsuccessful for any reason. */ static int generate_hash(int fdin, char *hash_str) { unsigned char buf[BUF_SIZE]; unsigned char hash[MD5_DIGEST_LENGTH]; int i, amtread; MD5_CTX ctx; MD5Init(&ctx); for (;;) { amtread = read(fdin, buf, sizeof (buf)); if (amtread == 0) break; if (amtread < 0) return (1); /* got some data. Now update hash */ MD5Update(&ctx, buf, amtread); } /* done passing through data, calculate hash */ MD5Final(hash, &ctx); for (i = 0; i < MD5_DIGEST_LENGTH; i++) (void) sprintf(hash_str + (i*2), "%2.2x", hash[i]); return (0); } /* * Used by 'bart create' with the '-I' option. Return each entry into a 'buf' * with the appropriate exit code: '0' for success and '-1' for failure. */ static int read_filelist(char *reloc_root, char **argv, char *buf, size_t bufsize) { static int argv_index = -1; static boolean_t read_stdinput = B_FALSE; char temp_buf[PATH_MAX]; char *cp; /* * INITIALIZATION: * Setup this code so it knows whether or not to read sdtin. * Also, if reading from argv, setup the index, "argv_index" */ if (argv_index == -1) { argv_index = 0; /* In this case, no args after '-I', so read stdin */ if (argv[0] == NULL) read_stdinput = B_TRUE; } buf[0] = '\0'; if (read_stdinput) { if (fgets(temp_buf, PATH_MAX, stdin) == NULL) return (-1); cp = strtok(temp_buf, "\n"); } else { cp = argv[argv_index++]; } if (cp == NULL) return (-1); /* * Unlike similar code elsewhere, avoid adding a leading * slash for relative pathnames. */ (void) snprintf(buf, bufsize, (reloc_root[0] == '\0' || cp[0] == '/') ? "%s%s" : "%s/%s", reloc_root, cp); return (0); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include "bart.h" struct cmd_keyword { char *ck_name; void (*ck_func)(void); }; static struct attr_keyword attr_keywords[] = { { ALL_KEYWORD, ~0 }, { CONTENTS_KEYWORD, ATTR_CONTENTS }, { TYPE_KEYWORD, ATTR_TYPE }, { SIZE_KEYWORD, ATTR_SIZE }, { MODE_KEYWORD, ATTR_MODE }, { ACL_KEYWORD, ATTR_ACL }, { UID_KEYWORD, ATTR_UID }, { GID_KEYWORD, ATTR_GID }, { MTIME_KEYWORD, ATTR_MTIME }, { LNMTIME_KEYWORD, ATTR_LNMTIME }, { DIRMTIME_KEYWORD, ATTR_DIRMTIME }, { DEST_KEYWORD, ATTR_DEST }, { DEVNODE_KEYWORD, ATTR_DEVNODE }, { ADD_KEYWORD, ATTR_ADD }, { DELETE_KEYWORD, ATTR_DELETE }, { NULL } }; struct attr_keyword * attr_keylookup(char *word) { struct attr_keyword *akp; for (akp = attr_keywords; ; akp++) { if (akp->ak_name == NULL) break; if (strcasecmp(word, akp->ak_name) == 0) return (akp); } return (NULL); } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include "bart.h" int main(int argc, char **argv) { /* Make sure we are in the correct locale */ (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); /* Superficial check of the arguments. Note usage() exits the pgm */ if (argc < 2) usage(); /* * OK, hand it off to bart_create() or bart_compare(). * * Since the command line was 'bart create ..', or 'bart compare ..', * those subcommands should start parsing options at &argv[1], and * (argc-1) to be consistent. */ if (strcmp(argv[1], "create") == 0) return (bart_create((argc-1), (argv+1))); else if (strcmp(argv[1], "compare") == 0) { return (bart_compare((argc-1), (argv+1))); } else usage(); return (FATAL_EXIT); } void usage() { (void) fprintf(stderr, USAGE_MSG); exit(FATAL_EXIT); } void * safe_calloc(size_t size) { char *ptr; ptr = calloc((size_t)1, size); if (ptr == NULL) exit(FATAL_EXIT); else return (ptr); /* NOTREACHED */ } /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _MSGS_H #define _MSGS_H #ifdef __cplusplus extern "C" { #endif #define SYNTAX_ERR gettext("ERROR: Ignoring rules syntax error: %s\n") #define SYNTAX_ABORT gettext("ABORTING: Syntax error(s) in the rules file\n") #define UNKNOWN_FILE gettext("WARNING: unknown directory entry: %s\n") #define CANTLIST_DIR gettext("WARNING: cannot list directory: %s\n") #define INTERNAL_ERR gettext("WARNING: internal error at %s\n") #define INVALID_SUBTREE gettext("WARNING: Ignoring invalid subtree: %s\n") #define RULES_ERR gettext("ERROR: -r option requires a filename\n") #define INPUT_ERR \ gettext("ERROR: Cannot use -I and -r options together\n") #define MISSING_VER \ gettext("WARNING: %s has missing/invalid version string\n") #define MANIFEST_ERR gettext("ERROR: Manifest corrupt, cannot continue\n") #define CONTENTS_WARN gettext("WARNING: Checksum failed: %s\n") #define USAGE_MSG gettext("Usage:\n"\ "\tbart create [-n] [-R root] [-r rules|-]\n"\ "\tbart create [-n] [-R root] [-I | -I filelist]\n"\ "\tbart compare [-r rules|-] [-i keywords] [-p] "\ "control-manifest test-manifest\n") #ifdef __cplusplus } #endif #endif /* _MSGS_H */ /* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include "bart.h" static int count_slashes(const char *); static struct rule *gen_rulestruct(void); static struct tree_modifier *gen_tree_modifier(void); static struct dir_component *gen_dir_component(void); static void init_rule(uint_t, struct rule *); static void add_modifier(struct rule *, char *); static struct rule *add_subtree_rule(char *, char *, int, int *); static struct rule *add_single_rule(char *); static void dirs_cleanup(struct dir_component *); static void add_dir(struct dir_component **, char *); static char *lex(FILE *); static int match_subtree(const char *, char *); static struct rule *get_last_entry(boolean_t); static int lex_linenum; /* line number in current input file */ static struct rule *first_rule = NULL, *current_rule = NULL; /* * This function is responsible for validating whether or not a given file * should be cataloged, based upon the modifiers for a subtree. * For example, a line in the rules file: '/home/nickiso *.c' should only * catalog the C files (based upon pattern matching) in the subtree * '/home/nickiso'. * * exclude_fname depends on having the modifiers be pre-sorted to put * negative directory modifiers first, so that the logic does * not need to save complex state information. This is valid because * we are only cataloging things that meet all modifiers (AND logic.) * * Returns: * NO_EXCLUDE * EXCLUDE_SKIP * EXCLUDE_PRUNE */ int exclude_fname(const char *fname, char fname_type, struct rule *rule_ptr) { char *pattern, *ptr, *fname_ptr, saved_char; char fname_cp[PATH_MAX], pattern_cp[PATH_MAX]; int num_pattern_slash, i, num_fname_slash, slashes_to_adv; struct tree_modifier *mod_ptr; /* * If this is create and there are no modifiers, bail. * This will have to change once create handles multiple rules * during walk. */ if (rule_ptr->modifiers == NULL) if (rule_ptr->attr_list == 0) return (EXCLUDE_PRUNE); else return (NO_EXCLUDE); /* * Walk through all the modifiers until its they are exhausted OR * until the file should definitely be excluded. */ for (mod_ptr = rule_ptr->modifiers; mod_ptr != NULL; mod_ptr = mod_ptr->next) { /* leading !'s were processed in add_modifier */ pattern = mod_ptr->mod_str; if (mod_ptr->is_dir == B_FALSE) { /* * Pattern is a file pattern. * * In the case when a user is trying to filter on * a file pattern and the entry is a directory, * this is not a match. * * If a match is required, skip this file. If * a match is forbidden, keep looking at modifiers. */ if (fname_type == 'D') { if (mod_ptr->include == B_TRUE) return (EXCLUDE_SKIP); else continue; } /* * Match patterns against filenames. * Need to be able to handle multi-level patterns, * eg. "SCCS/.c", which means * 'only match C files under SCCS directories. * * Determine the number of levels in the filename and * in the pattern. */ num_pattern_slash = count_slashes(pattern); num_fname_slash = count_slashes(fname); /* Check for trivial exclude condition */ if (num_pattern_slash > num_fname_slash) { if (mod_ptr->include == B_TRUE) return (EXCLUDE_SKIP); } /* * Do an apples to apples comparison, based upon the * number of levels: * * Assume fname is /A/B/C/D/E and the pattern is D/E. * In that case, 'ptr' will point to "D/E" and * 'slashes_to_adv' will be '4'. */ (void) strlcpy(fname_cp, fname, sizeof (fname_cp)); ptr = fname_cp; slashes_to_adv = num_fname_slash - num_pattern_slash; for (i = 0; i < slashes_to_adv; i++) { ptr = strchr(ptr, '/'); ptr++; } if ((pattern[0] == '.') && (pattern[1] == '.') && (pattern[2] == '/')) { pattern = strchr(pattern, '/'); ptr = strchr(ptr, '/'); } /* OK, now do the fnmatch() compare to the file */ if (fnmatch(pattern, ptr, FNM_PATHNAME) == 0) { /* matches, is it an exclude? */ if (mod_ptr->include == B_FALSE) return (EXCLUDE_SKIP); } else if (mod_ptr->include == B_TRUE) { /* failed a required filename match */ return (EXCLUDE_SKIP); } } else { /* * The rule requires directory matching. * * Unlike filename matching, directory matching can * prune. * * First, make copies, since both the pattern and * filename need to be modified. * * When copying 'fname', ignore the relocatable root * since pattern matching is done for the string AFTER * the relocatable root. For example, if the * relocatable root is "/dir1/dir2/dir3" and the * pattern is "dir3/", we do NOT want to include every * directory in the relocatable root. Instead, we * only want to include subtrees that look like: * "/dir1/dir2/dir3/....dir3/....." * * NOTE: the 'fname_cp' does NOT have a trailing '/': * necessary for fnmatch(). */ (void) strlcpy(fname_cp, (fname+strlen(rule_ptr->subtree)), sizeof (fname_cp)); (void) strlcpy(pattern_cp, pattern, sizeof (pattern_cp)); /* * For non-directory files, remove the trailing * name, e.g., for a file /A/B/C/D where 'D' is * the actual filename, remove the 'D' since it * should *not* be considered in the directory match. */ if (fname_type != 'D') { ptr = strrchr(fname_cp, '/'); if (ptr != NULL) *ptr = '\0'; /* * Trivial case: a simple filename does * not match a directory by definition, * so skip if match is required, * keep analyzing otherwise. */ if (strlen(fname_cp) == 0) if (mod_ptr->include == B_TRUE) return (EXCLUDE_SKIP); } /* Count the # of slashes in the pattern and fname */ num_pattern_slash = count_slashes(pattern_cp); num_fname_slash = count_slashes(fname_cp); /* * fname_cp is too short if this is not a dir */ if ((num_pattern_slash > num_fname_slash) && (fname_type != 'D')) { if (mod_ptr->include == B_TRUE) return (EXCLUDE_SKIP); } /* * Take the leading '/' from fname_cp before * decrementing the number of slashes. */ if (fname_cp[0] == '/') { (void) strlcpy(fname_cp, strchr(fname_cp, '/') + 1, sizeof (fname_cp)); num_fname_slash--; } /* * Begin the loop, walk through the file name until * it can be determined that there is no match. * For example: if pattern is C/D/, and fname_cp is * A/B/C/D/E then compare A/B/ with C/D/, if it doesn't * match, then walk further so that the next iteration * checks B/C/ against C/D/, continue until we have * exhausted options. * In the above case, the 3rd iteration will match * C/D/ with C/D/. */ while (num_pattern_slash <= num_fname_slash) { /* get a pointer to our filename */ fname_ptr = fname_cp; /* * Walk the filename through the slashes * so that we have a component of the same * number of slashes as the pattern. */ for (i = 0; i < num_pattern_slash; i++) { ptr = strchr(fname_ptr, '/'); fname_ptr = ptr + 1; } /* * Save the character after our target slash * before breaking the string for use with * fnmatch */ saved_char = *(++ptr); *ptr = '\0'; /* * Call compare function for the current * component with the pattern we are looking * for. */ if (fnmatch(pattern_cp, fname_cp, FNM_PATHNAME) == 0) { if (mod_ptr->include == B_TRUE) { break; } else if (fname_type == 'D') return (EXCLUDE_PRUNE); else return (EXCLUDE_SKIP); } else if (mod_ptr->include == B_TRUE) { if (fname_type == 'D') return (EXCLUDE_PRUNE); else return (EXCLUDE_SKIP); } /* * We didn't match, so restore the saved * character to the original position. */ *ptr = saved_char; /* * Break down fname_cp, if it was A/B/C * then after this operation it will be B/C * in preparation for the next iteration. */ (void) strlcpy(fname_cp, strchr(fname_cp, '/') + 1, sizeof (fname_cp)); /* * Decrement the number of slashes to * compensate for the one removed above. */ num_fname_slash--; } /* end while loop looking down the path */ /* * If we didn't get a match above then we may be on the * last component of our filename. * This is to handle the following cases * - filename is A/B/C/D/E and pattern may be D/E/ * - filename is D/E and pattern may be D/E/ */ if (num_pattern_slash == (num_fname_slash + 1)) { /* strip the trailing slash from the pattern */ ptr = strrchr(pattern_cp, '/'); *ptr = '\0'; /* fnmatch returns 0 for a match */ if (fnmatch(pattern_cp, fname_cp, FNM_PATHNAME) == 0) { if (mod_ptr->include == B_FALSE) { if (fname_type == 'D') return (EXCLUDE_PRUNE); else return (EXCLUDE_SKIP); } } else if (mod_ptr->include == B_TRUE) return (EXCLUDE_SKIP); } } } return (NO_EXCLUDE); } static int count_slashes(const char *in_path) { int num_fname_slash = 0; const char *p; for (p = in_path; *p != '\0'; p++) if (*p == '/') num_fname_slash++; return (num_fname_slash); } static struct rule * gen_rulestruct(void) { struct rule *new_rule; new_rule = (struct rule *)safe_calloc(sizeof (struct rule)); return (new_rule); } static struct tree_modifier * gen_tree_modifier(void) { struct tree_modifier *new_modifier; new_modifier = (struct tree_modifier *)safe_calloc (sizeof (struct tree_modifier)); return (new_modifier); } static struct dir_component * gen_dir_component(void) { struct dir_component *new_dir; new_dir = (struct dir_component *)safe_calloc (sizeof (struct dir_component)); return (new_dir); } /* * Set up a default rule when there is no rules file. */ static struct rule * setup_default_rule(char *reloc_root, uint_t flags) { struct rule *new_rule; new_rule = add_single_rule(reloc_root[0] == '\0' ? "/" : reloc_root); init_rule(flags, new_rule); add_modifier(new_rule, "*"); return (new_rule); } /* * Utility function, used to initialize the flag in a new rule structure. */ static void init_rule(uint_t flags, struct rule *new_rule) { if (new_rule == NULL) return; new_rule->attr_list = flags; } /* * Function to read the rulesfile. Used by both 'bart create' and * 'bart compare'. */ int read_rules(FILE *file, char *reloc_root, uint_t in_flags, int create) { char *s; struct rule *block_begin = NULL, *new_rule, *rp; struct attr_keyword *akp; int check_flag, ignore_flag, syntax_err, ret_code; int global_block; ret_code = EXIT; lex_linenum = 0; check_flag = 0; ignore_flag = 0; syntax_err = 0; global_block = 1; if (file == NULL) { (void) setup_default_rule(reloc_root, in_flags); return (ret_code); } else if (!create) { block_begin = setup_default_rule("/", in_flags); } while (!feof(file)) { /* Read a line from the file */ s = lex(file); /* skip blank lines and comments */ if (s == NULL || *s == 0 || *s == '#') continue; /* * Beginning of a subtree and possibly a new block. * * If this is a new block, keep track of the beginning of * the block. if there are directives later on, we need to * apply that directive to all members of the block. * * If the first stmt in the file was an 'IGNORE all' or * 'IGNORE contents', we need to keep track of it and * automatically switch off contents checking for new * subtrees. */ if (s[0] == '/') { /* subtree definition hence not a global block */ global_block = 0; new_rule = add_subtree_rule(s, reloc_root, create, &ret_code); s = lex(0); while ((s != NULL) && (*s != 0) && (*s != '#')) { add_modifier(new_rule, s); s = lex(0); } /* Found a new block, keep track of the beginning */ if (block_begin == NULL || (ignore_flag != 0) || (check_flag != 0)) { block_begin = new_rule; check_flag = 0; ignore_flag = 0; } /* Apply global settings to this block, if any */ init_rule(in_flags, new_rule); } else if (IGNORE_KEYWORD(s) || CHECK_KEYWORD(s)) { int check_kw; if (IGNORE_KEYWORD(s)) { ignore_flag++; check_kw = 0; } else { check_flag++; check_kw = 1; } /* Parse next token */ s = lex(0); while ((s != NULL) && (*s != 0) && (*s != '#')) { akp = attr_keylookup(s); if (akp == NULL) { (void) fprintf(stderr, SYNTAX_ERR, s); syntax_err++; exit(2); } /* * For all the flags, check if this is a global * IGNORE/CHECK. If so, set the global flags. * * NOTE: The only time you can have a * global ignore is when its the * stmt before any blocks have been * spec'd. */ if (global_block) { if (check_kw) in_flags |= akp->ak_flags; else in_flags &= ~(akp->ak_flags); } else { for (rp = block_begin; rp != NULL; rp = rp->next) { if (check_kw) rp->attr_list |= akp->ak_flags; else rp->attr_list &= ~(akp->ak_flags); } } /* Parse next token */ s = lex(0); } } else { (void) fprintf(stderr, SYNTAX_ERR, s); s = lex(0); while (s != NULL && *s != 0) { (void) fprintf(stderr, " %s", s); s = lex(0); } (void) fprintf(stderr, "\n"); syntax_err++; } } (void) fclose(file); if (syntax_err) { (void) fprintf(stderr, SYNTAX_ABORT); exit(2); } return (ret_code); } /* * Add a modifier to the mod_ptr list in each rule, putting negative * directory entries * first to guarantee walks will be appropriately pruned. */ static void add_modifier(struct rule *rule, char *modifier_str) { int include, is_dir; char *pattern; struct tree_modifier *new_mod_ptr, *curr_mod_ptr; struct rule *this_rule; include = B_TRUE; pattern = modifier_str; /* see if the pattern is an include or an exclude */ if (pattern[0] == '!') { include = B_FALSE; pattern++; } is_dir = (pattern[0] != '\0' && pattern[strlen(pattern) - 1] == '/'); for (this_rule = rule; this_rule != NULL; this_rule = this_rule->next) { new_mod_ptr = gen_tree_modifier(); new_mod_ptr->include = include; new_mod_ptr->is_dir = is_dir; new_mod_ptr->mod_str = safe_strdup(pattern); if (is_dir && !include) { new_mod_ptr->next = this_rule->modifiers; this_rule->modifiers = new_mod_ptr; } else if (this_rule->modifiers == NULL) this_rule->modifiers = new_mod_ptr; else { curr_mod_ptr = this_rule->modifiers; while (curr_mod_ptr->next != NULL) curr_mod_ptr = curr_mod_ptr->next; curr_mod_ptr->next = new_mod_ptr; } } } /* * This funtion is invoked when reading rulesfiles. A subtree may have * wildcards in it, e.g., '/home/n*', which is expected to match all home * dirs which start with an 'n'. * * This function needs to break down the subtree into its components. For * each component, see how many directories match. Take the subtree list just * generated and run it through again, this time looking at the next component. * At each iteration, keep a linked list of subtrees that currently match. * Once the final list is created, invoke add_single_rule() to create the * rule struct with the correct information. * * This function returns a ptr to the first element in the block of subtrees * which matched the subtree def'n in the rulesfile. */ static struct rule * add_subtree_rule(char *rule, char *reloc_root, int create, int *err_code) { char full_path[PATH_MAX], pattern[PATH_MAX]; char new_dirname[PATH_MAX]; char *beg_pattern, *end_pattern, *curr_dirname; struct dir_component *current_level = NULL, *next_level = NULL; struct dir_component *tmp_ptr; DIR *dir_ptr; struct dirent *dir_entry; struct rule *begin_rule = NULL; int ret; struct stat64 statb; (void) snprintf(full_path, sizeof (full_path), (rule[0] == '/') ? "%s%s" : "%s/%s", reloc_root, rule); /* * In the case of 'bart compare', don't validate * the subtrees, since the machine running the * comparison may not be the machine which generated * the manifest. */ if (create == 0) return (add_single_rule(full_path)); /* Insert 'current_level' into the linked list */ add_dir(¤t_level, NULL); /* Special case: occurs when -R is "/" and the subtree is "/" */ if (strcmp(full_path, "/") == 0) (void) strcpy(current_level->dirname, "/"); beg_pattern = full_path; while (beg_pattern != NULL) { /* * Extract the pathname component starting at 'beg_pattern'. * Take those chars and put them into 'pattern'. */ while (*beg_pattern == '/') beg_pattern++; if (*beg_pattern == '\0') /* end of pathname */ break; end_pattern = strchr(beg_pattern, '/'); if (end_pattern != NULL) (void) strlcpy(pattern, beg_pattern, end_pattern - beg_pattern + 1); else (void) strlcpy(pattern, beg_pattern, sizeof (pattern)); beg_pattern = end_pattern; /* * At this point, search for 'pattern' as a *subdirectory* of * the dirs in the linked list. */ while (current_level != NULL) { /* curr_dirname used to make the code more readable */ curr_dirname = current_level->dirname; /* Initialization case */ if (strlen(curr_dirname) == 0) (void) strcpy(curr_dirname, "/"); /* Open up the dir for this element in the list */ dir_ptr = opendir(curr_dirname); dir_entry = NULL; if (dir_ptr == NULL) { perror(curr_dirname); *err_code = WARNING_EXIT; } else dir_entry = readdir(dir_ptr); /* * Now iterate through the subdirs of 'curr_dirname' * In the case of a match against 'pattern', * add the path to the next linked list, which * will be matched on the next iteration. */ while (dir_entry != NULL) { /* Skip the dirs "." and ".." */ if ((strcmp(dir_entry->d_name, ".") == 0) || (strcmp(dir_entry->d_name, "..") == 0)) { dir_entry = readdir(dir_ptr); continue; } if (fnmatch(pattern, dir_entry->d_name, FNM_PATHNAME) == 0) { /* * Build 'new_dirname' which will be * examined on the next iteration. */ if (curr_dirname[strlen(curr_dirname)-1] != '/') (void) snprintf(new_dirname, sizeof (new_dirname), "%s/%s", curr_dirname, dir_entry->d_name); else (void) snprintf(new_dirname, sizeof (new_dirname), "%s%s", curr_dirname, dir_entry->d_name); /* Add to the next lined list */ add_dir(&next_level, new_dirname); } dir_entry = readdir(dir_ptr); } /* Close directory */ if (dir_ptr != NULL) (void) closedir(dir_ptr); /* Free this entry and move on.... */ tmp_ptr = current_level; current_level = current_level->next; free(tmp_ptr); } /* * OK, done with this level. Move to the next level and * advance the ptrs which indicate the component name. */ current_level = next_level; next_level = NULL; } tmp_ptr = current_level; /* Error case: the subtree doesn't exist! */ if (current_level == NULL) { (void) fprintf(stderr, INVALID_SUBTREE, full_path); *err_code = WARNING_EXIT; } /* * Iterate through all the dirnames which match the pattern and * add them to to global list of subtrees which must be examined. */ while (current_level != NULL) { /* * Sanity check for 'bart create', make sure the subtree * points to a valid object. */ ret = lstat64(current_level->dirname, &statb); if (ret < 0) { (void) fprintf(stderr, INVALID_SUBTREE, current_level->dirname); current_level = current_level->next; *err_code = WARNING_EXIT; continue; } if (begin_rule == NULL) { begin_rule = add_single_rule(current_level->dirname); } else (void) add_single_rule(current_level->dirname); current_level = current_level->next; } /* * Free up the memory and return a ptr to the first entry in the * subtree block. This is necessary for the parser, which may need * to add modifier strings to all the elements in this block. */ dirs_cleanup(tmp_ptr); return (begin_rule); } /* * Add a single entry to the linked list of rules to be read. Does not do * the wildcard expansion of 'add_subtree_rule', so is much simpler. */ static struct rule * add_single_rule(char *path) { /* * If the rules list does NOT exist, then create it. * If the rules list does exist, then traverse the next element. */ if (first_rule == NULL) { first_rule = gen_rulestruct(); current_rule = first_rule; } else { current_rule->next = gen_rulestruct(); current_rule->next->prev = current_rule; current_rule = current_rule->next; } /* Setup the rule struct, handle relocatable roots, i.e. '-R' option */ (void) strlcpy(current_rule->subtree, path, sizeof (current_rule->subtree)); return (current_rule); } /* * Code stolen from filesync utility, used by read_rules() to read in the * rulesfile. */ static char * lex(FILE *file) { char c, delim; char *p; char *s; static char *savep; static char namebuf[ BUF_SIZE ]; static char inbuf[ BUF_SIZE ]; if (file) { /* read a new line */ p = inbuf + sizeof (inbuf); s = inbuf; /* read the next input line, with all continuations */ while (savep = fgets(s, p - s, file)) { lex_linenum++; /* go find the last character of the input line */ while (*s && s[1]) s++; if (*s == '\n') s--; /* see whether or not we need a continuation */ if (s < inbuf || *s != '\\') break; continue; } if (savep == NULL) return (0); s = inbuf; } else { /* continue with old line */ if (savep == NULL) return (0); s = savep; } savep = NULL; /* skip over leading white space */ while (isspace(*s)) s++; if (*s == 0) return (0); /* see if this is a quoted string */ c = *s; if (c == '\'' || c == '"') { delim = c; s++; } else delim = 0; /* copy the token into the buffer */ for (p = namebuf; (c = *s) != 0; s++) { /* literal escape */ if (c == '\\') { s++; *p++ = *s; continue; } /* closing delimiter */ if (c == delim) { s++; break; } /* delimiting white space */ if (delim == 0 && isspace(c)) break; /* ordinary characters */ *p++ = *s; } /* remember where we left off */ savep = *s ? s : 0; /* null terminate and return the buffer */ *p = 0; return (namebuf); } /* * Iterate through the dir strcutures and free memory. */ static void dirs_cleanup(struct dir_component *dir) { struct dir_component *next; while (dir != NULL) { next = dir->next; free(dir); dir = next; } } /* * Create and initialize a new dir structure. Used by add_subtree_rule() when * doing expansion of directory names caused by wildcards. */ static void add_dir(struct dir_component **dir, char *dirname) { struct dir_component *new, *next_dir; new = gen_dir_component(); if (dirname != NULL) (void) strlcpy(new->dirname, dirname, sizeof (new->dirname)); if (*dir == NULL) *dir = new; else { next_dir = *dir; while (next_dir->next != NULL) next_dir = next_dir->next; next_dir->next = new; } } /* * Traverse the linked list of rules in a REVERSE order. */ static struct rule * get_last_entry(boolean_t reset) { static struct rule *curr_root = NULL; if (reset) { curr_root = first_rule; /* RESET: set cur_root to the end of the list */ while (curr_root != NULL) if (curr_root->next == NULL) break; else curr_root = curr_root->next; } else curr_root = (curr_root->prev); return (curr_root); } /* * Traverse the first entry, used by 'bart create' to iterate through * subtrees or individual filenames. */ struct rule * get_first_subtree() { return (first_rule); } /* * Traverse the next entry, used by 'bart create' to iterate through * subtrees or individual filenames. */ struct rule * get_next_subtree(struct rule *entry) { return (entry->next); } char * safe_strdup(char *s) { char *ret; size_t len; len = strlen(s) + 1; ret = safe_calloc(len); (void) strlcpy(ret, s, len); return (ret); } /* * Function to match a filename against the subtrees in the link list * of 'rule' strcutures. Upon finding a matching rule, see if it should * be excluded. Keep going until a match is found OR all rules have been * exhausted. * NOTES: Rules are parsed in reverse; * satisfies the spec that "Last rule wins". Also, the default rule should * always match, so this function should NEVER return NULL. */ struct rule * check_rules(const char *fname, char type) { struct rule *root; root = get_last_entry(B_TRUE); while (root != NULL) { if (match_subtree(fname, root->subtree)) { if (exclude_fname(fname, type, root) == NO_EXCLUDE) break; } root = get_last_entry(B_FALSE); } return (root); } /* * Function to determine if an entry in a rules file (see bart_rules(5)) applies * to a filename. We truncate "fname" such that it has the same number of * components as "rule" and let fnmatch(3C) do the rest. A "component" is one * part of an fname as delimited by slashes ('/'). So "/A/B/C/D" has four * components: "A", "B", "C" and "D". * * For example: * * 1. the rule "/home/nickiso" applies to fname "/home/nickiso/src/foo.c" so * should match. * * 2. the rule "/home/nickiso/temp/src" does not apply to fname * "/home/nickiso/foo.c" so should not match. */ static int match_subtree(const char *fname, char *rule) { int match, num_rule_slash; char *ptr, fname_cp[PATH_MAX]; /* If rule has more components than fname, it cannot match. */ if ((num_rule_slash = count_slashes(rule)) > count_slashes(fname)) return (0); /* Create a copy of fname that we can truncate. */ (void) strlcpy(fname_cp, fname, sizeof (fname_cp)); /* * Truncate fname_cp such that it has the same number of components * as rule. If rule ends with '/', so should fname_cp. ie: * * rule fname fname_cp matches * ---- ----- -------- ------- * /home/dir* /home/dir0/dir1/fileA /home/dir0 yes * /home/dir/ /home/dir0/dir1/fileA /home/dir0/ no */ for (ptr = fname_cp; num_rule_slash > 0; num_rule_slash--, ptr++) ptr = strchr(ptr, '/'); if (*(rule + strlen(rule) - 1) != '/') { while (*ptr != '\0') { if (*ptr == '/') break; ptr++; } } *ptr = '\0'; /* OK, now see if they match. */ match = fnmatch(rule, fname_cp, FNM_PATHNAME); /* No match, return failure */ if (match != 0) return (0); else return (1); } void process_glob_ignores(char *ignore_list, uint_t *flags) { char *cp; struct attr_keyword *akp; if (ignore_list == NULL) usage(); cp = strtok(ignore_list, ","); while (cp != NULL) { akp = attr_keylookup(cp); if (akp == NULL) (void) fprintf(stderr, "ERROR: Invalid keyword %s\n", cp); else *flags &= ~akp->ak_flags; cp = strtok(NULL, ","); } }