OpenSolaris_b135/cmd/filesync/rules.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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
 */
#pragma ident	"%Z%%M%	%I%	%E% SMI"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>

#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);
}