/* * 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 (c) 1995 Sun Microsystems, Inc. All Rights Reserved * * module: * rules.c * * purpose: * to read and write the rules file and manage rules lists * * contents: * reading rules file * read_rules * (static) read_command * writing rules file * write_rules * (static) rw_header, rw_base * adding rules * add_ignore, add_include * (static) add_rule * adding/checking restrictions * add_restr, check_restr */ #include #include #include #include #include #include "filesync.h" #include "database.h" #include "messages.h" #include "debug.h" /* * routines: */ static errmask_t rw_base(FILE *file, struct base *bp); static errmask_t rw_header(FILE *file); static errmask_t add_rule(struct base *, int, const char *); static char *read_cmd(char *); /* * globals */ static int rules_added; static int restr_added; /* * locals */ #define RULE_MAJOR 1 /* rules file format major rev */ #define RULE_MINOR 1 /* rules file format minor rev */ #define RULE_TAG "PACKINGRULES" /* magic string for rules files */ /* * routine: * read_rules * * purpose: * to read in the rules file * * parameters: * name of rules file * * returns: * error mask * * notes: * later when I implement a proper (comment preserving) update * function I'm going to wish I had figured out how to build the * input functions for this function in a way that would make * the more usable for that too. */ errmask_t read_rules(char *name) { FILE *file; errmask_t errs = 0; int flags; int major, minor; char *s, *s1, *s2; struct base *bp; char *errstr = "???"; file = fopen(name, "r"); if (file == NULL) { fprintf(stderr, gettext(ERR_open), gettext(TXT_rules), name); return (ERR_FILES); } lex_linenum = 0; if (opt_debug & DBG_FILES) fprintf(stderr, "FILE: READ RULES %s\n", name); bp = &omnibase; /* default base before any others */ while (!feof(file)) { /* find the first token on the line */ s = lex(file); /* skip blank lines and comments */ if (s == 0 || *s == 0 || *s == '#' || *s == '*') continue; /* see if the first token is a known keyword */ if (strcmp(s, "BASE") == 0) { /* get the source & destination tokens */ errstr = gettext(TXT_srcdst); s1 = lex(0); if (s1 == 0) goto bad; s1 = strdup(s1); s2 = lex(0); if (s2 == 0) goto bad; s2 = strdup(s2); /* creat the new base pair */ bp = add_base(s1, s2); bp->b_flags |= F_LISTED; free(s1); free(s2); continue; } if (strcmp(s, "LIST") == 0) { /* make sure we are associated with a real base */ if (bp == &omnibase) { errstr = gettext(TXT_nobase); goto bad; } /* skip to the next token */ s = lex(0); errstr = gettext(TXT_noargs); if (s == 0) goto bad; /* see if it is a program or a name */ if (*s == '!') { errs |= add_rule(bp, R_PROGRAM, read_cmd(&s[1])); } else { do { flags = wildcards(s) ? R_WILD : 0; errs |= add_rule(bp, flags, s); s = lex(0); } while (s != 0); } continue; } if (strcmp(s, "IGNORE") == 0) { /* skip to the next token */ s = lex(0); errstr = gettext(TXT_noargs); if (s == 0) goto bad; flags = R_IGNORE; /* see if it is a program or a name */ if (*s == '!') { errs |= add_rule(bp, R_PROGRAM|flags, read_cmd(&s[1])); } else { do { if (wildcards(s)) flags |= R_WILD; errs |= add_rule(bp, flags, s); s = lex(0); } while (s != 0); } continue; } if (strcmp(s, "VERSION") == 0 || strcmp(s, RULE_TAG) == 0) { s = lex(0); errstr = gettext(TXT_noargs); if (s == 0) goto bad; major = strtol(s, &s1, 10); errstr = gettext(TXT_badver); if (*s1 != '.') goto bad; minor = strtol(&s1[1], 0, 10); if (major != RULE_MAJOR || minor > RULE_MINOR) { fprintf(stderr, gettext(ERR_badver), major, minor, gettext(TXT_rules), name); errs |= ERR_FILES; } continue; } bad: /* log the error and continue processing to find others */ fprintf(stderr, gettext(ERR_badinput), lex_linenum, errstr, name); errs |= ERR_FILES; } (void) fclose(file); return (errs); } /* * routine: * read_cmd * * purpose: * to lex a runnable command (! lines) into a buffer * * parameters: * first token * * returns: * pointer to a command line in a static buffer * (it is assumed the caller will copy it promptly) * * notes: * this is necessary because lex has already choped off * the first token for us */ static char *read_cmd(char * s) { static char cmdbuf[ MAX_LINE ]; cmdbuf[0] = 0; do { if (*s) { strcat(cmdbuf, s); strcat(cmdbuf, " "); } } while ((s = lex(0)) != 0); return (cmdbuf); } /* * routine: * write_rules * * purpose: * to rewrite the rules file, appending the new rules * * parameters: * name of output file * * returns: * error mask * */ errmask_t write_rules(char *name) { FILE *newfile; errmask_t errs = 0; struct base *bp; char tmpname[ MAX_PATH ]; /* if no-touch is specified, we don't update files */ if (opt_notouch || rules_added == 0) return (0); /* create a temporary output file */ sprintf(tmpname, "%s-TMP", name); /* create our output file */ newfile = fopen(tmpname, "w+"); if (newfile == NULL) { fprintf(stderr, gettext(ERR_creat), gettext(TXT_rules), name); return (ERR_FILES); } if (opt_debug & DBG_FILES) fprintf(stderr, "FILE: UPDATE RULES %s\n", name); errs |= rw_header(newfile); errs |= rw_base(newfile, &omnibase); for (bp = bases; bp; bp = bp->b_next) errs |= rw_base(newfile, bp); if (ferror(newfile)) { fprintf(stderr, gettext(ERR_write), gettext(TXT_rules), tmpname); errs |= ERR_FILES; } if (fclose(newfile)) { fprintf(stderr, gettext(ERR_fclose), gettext(TXT_rules), tmpname); errs |= ERR_FILES; } /* now switch the new file for the old one */ if (errs == 0) if (rename(tmpname, name) != 0) { fprintf(stderr, gettext(ERR_rename), gettext(TXT_rules), tmpname, name); errs |= ERR_FILES; } return (errs); } /* * routine: * rw_header * * purpose: * to write out a rules header * * parameters: * FILE* for the output file * * returns: * error mask * * notes: */ static errmask_t rw_header(FILE *file) { time_t now; struct tm *local; /* figure out what time it is */ (void) time(&now); local = localtime(&now); fprintf(file, "%s %d.%d\n", RULE_TAG, RULE_MAJOR, RULE_MINOR); fprintf(file, "#\n"); fprintf(file, "# filesync rules, last written by %s, %s", cuserid((char *) 0), asctime(local)); fprintf(file, "#\n"); return (0); } /* * routine: * rw_base * * purpose: * to write out the summary for one base-pair * * parameters: * FILE * for the output file * * returns: * error mask * * notes: */ static errmask_t rw_base(FILE *file, struct base *bp) { struct rule *rp; fprintf(file, "\n"); /* global rules don't appear within a base */ if (bp->b_ident) fprintf(file, "BASE %s %s\n", noblanks(bp->b_src_spec), noblanks(bp->b_dst_spec)); for (rp = bp->b_includes; rp; rp = rp->r_next) if (rp->r_flags & R_PROGRAM) fprintf(file, "LIST !%s\n", rp->r_file); else fprintf(file, "LIST %s\n", noblanks(rp->r_file)); for (rp = bp->b_excludes; rp; rp = rp->r_next) if (rp->r_flags & R_PROGRAM) fprintf(file, "IGNORE !%s\n", rp->r_file); else fprintf(file, "IGNORE %s\n", noblanks(rp->r_file)); return (0); } /* * routine: * add_rule * * purpose: * to add a new rule * * parameters: * pointer to list base * rule flags * associated name/arguments * * returns: * error flags * * notes: * we always copy the argument string because most of them * were read from a file and are just in a transient buffer */ static errmask_t add_rule(struct base *bp, int flags, const char *args) { struct rule *rp; struct rule **list; rp = malloc(sizeof (struct rule)); if (rp == 0) nomem("rule struture"); /* initialize the new base */ memset((void *) rp, 0, sizeof (struct rule)); rp->r_flags = flags; rp->r_file = strdup(args); /* figure out which list to put it on */ if (flags&R_IGNORE) list = &bp->b_excludes; else if (flags&R_RESTRICT) list = &bp->b_restrictions; else list = &bp->b_includes; while (*list) list = &((*list)->r_next); *list = rp; if (flags & R_NEW) rules_added++; if (opt_debug & DBG_RULE) { fprintf(stderr, "RULE: base=%d, ", bp->b_ident); fprintf(stderr, "flags=%s, ", showflags(rflags, rp->r_flags)); fprintf(stderr, "arg=%s\n", rp->r_file); } return (0); } /* * routine: * add_ignore, add_include * * purpose: * wrappers for add_rule that permit outsiders (like main.c) * not to know what is inside of a base, file, or list entry * * parameters: * base under which rules should be added * argument associated with rule * * returns: * error flags * * notes: * basically these routines figure out what the right * flags are for a rule, and what list to put it on, * and then call a common handler. */ errmask_t add_ignore(struct base *bp, char *name) { int flags = R_IGNORE | R_NEW; if (bp == 0) bp = &omnibase; if (wildcards(name)) flags |= R_WILD; return (add_rule(bp, flags, name)); } errmask_t add_include(struct base *bp, char *name) { int flags = R_NEW; if (bp == 0) bp = &omnibase; if (wildcards(name)) flags |= R_WILD; bp->b_flags |= F_LISTED; return (add_rule(bp, flags, name)); } /* * routine: * add_restr * * purpose: * to add a restriction to a base * * parameters: * address of base * restriction string * * returns: * error mask * * notes: * a restriction is specified on the command line and * tells us to limit our analysis/reconcilation to * specified files and/or directories. We deal with * these by adding a restriction rule to any base that * looks like it might fit the restriction. We need to * treat this as a rule because the restriction string * may extend beyond the base directory and part-way into * its tree ... meaning that individual file names under * the base will have to be checked against the restriction. */ errmask_t add_restr(char *restr) { const char *s; errmask_t errs = 0; struct base *bp; for (bp = bases; bp; bp = bp->b_next) { /* * see if this restriction could apply to this base. * It could match either the source or destination * directory name for this base. If it matches neither * then the restriction does not apply to this base. */ s = prefix(restr, bp->b_src_name); if (s == 0) s = prefix(restr, bp->b_dst_name); if (s == 0) continue; /* * if there is more restriction string after the * base, we will need to note the remainder of the * string so that we can match individual files * against it. */ if (*s == '/') s++; errs |= add_rule(bp, R_RESTRICT, s); restr_added++; } return (errs); } /* * routine: * check_restr * * purpose: * to see if an argument falls within restrictions * * parameters: * pointer to relevant base * file name * * returns: * TRUE name is within restrictions * FALSE name is outside of restrictions * MAYBE name is on the path to a restriction * * notes: * if no restrictions have been specified, we evaluate * everything. If any restrictions have been specified, * we process only files that match one of the restrictions. * * add_restr has ensured that if the restriction includes * a portion that must be matched by individual files under * the base, that the restriction rule will contain that * portion of the restriction which must be matched against * individual file names. */ bool_t check_restr(struct base *bp, const char *name) { struct rule *rp; /* if there are no restrictions, everything is OK */ if (restr_added == 0) return (TRUE); /* now we have to run through the list */ for (rp = bp->b_restrictions; rp; rp = rp->r_next) { /* see if current path is under the restriction */ if (prefix(name, rp->r_file)) return (TRUE); /* see if current path is on the way to restr */ if (prefix(rp->r_file, name)) /* * this is kinky, but walker really needs * to know the difference between a directory * that we are unreservedly scanning, and one * that we are scanning only to find something * beneath it. */ return (MAYBE); } /* * there are restrictions in effect and this file doesn't seem * to meet any of them */ if (opt_debug & DBG_RULE) fprintf(stderr, "RULE: FAIL RESTRICTION base=%d, file=%s\n", bp->b_ident, name); return (FALSE); }