diff options
Diffstat (limited to 'usr/src/cmd/filesync/main.c')
-rw-r--r-- | usr/src/cmd/filesync/main.c | 688 |
1 files changed, 688 insertions, 0 deletions
diff --git a/usr/src/cmd/filesync/main.c b/usr/src/cmd/filesync/main.c new file mode 100644 index 0000000000..b12e7e47fc --- /dev/null +++ b/usr/src/cmd/filesync/main.c @@ -0,0 +1,688 @@ +/* + * 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 1995-2003 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +/* + * module: + * main.c + * + * purpose: + * argument handling and top level dispatch + * + * contents: + * main argument handling and main loop + * usage (static) print out usage message + * confirm prompt the user for a confirmation and get it + * nomem fatal error handler for malloc failures + * findfiles (static) locate our baseline and rules files + * cleanup (static) unlock baseline and delete temp file + * check_access (static) do we have adequate access to a file/directory + * whoami (static) get uid/gid/umask + */ + +#pragma ident "%Z%%M% %I% %E% SMI" + +#include <unistd.h> +#include <stdlib.h> +#include <fcntl.h> +#include <stdio.h> +#include <string.h> +#include <ctype.h> +#include <errno.h> +#include <sys/stat.h> + +#include "filesync.h" +#include "database.h" +#include "messages.h" +#include "debug.h" + +/* + * local routines in this module: + */ +static errmask_t findfiles(); /* find rule and baseline files */ +static void cleanup(int); /* cleanup locks and temps */ +static errmask_t check_access(char *, int *); /* check access to file */ +static void whoami(); /* gather information about me */ +static void usage(void); /* general usage */ + + +/* + * globals exported to the rest of the program + */ +bool_t opt_mtime; /* preserve modification times on propagations */ +bool_t opt_notouch; /* don't actually make any changes */ +bool_t opt_quiet; /* disable reconciliation command output */ +bool_t opt_verbose; /* enable analysis descriptions */ +side_t opt_force; /* designated winner for conflicts */ +side_t opt_oneway; /* one way only propagation */ +side_t opt_onesided; /* permit one-sided evaluation */ +bool_t opt_everything; /* everything must agree (modes/uid/gid) */ +bool_t opt_yes; /* pre-confirm massive deletions are OK */ +bool_t opt_acls; /* always scan for acls on all files */ +bool_t opt_errors; /* simulate errors on specified files */ +bool_t opt_halt; /* halt on propagation errors */ +dbgmask_t opt_debug; /* debug mask */ + +uid_t my_uid; /* default UID for files I create */ +gid_t my_gid; /* default GID for files I create */ + +static char *file_rules; /* name of rules file */ +static char *file_base; /* name of baseline file */ + +static int new_baseline; /* are we creating a new baseline */ +static int new_rules; /* are we creating a new rules file */ +static int my_umask; /* default UMASK for files I create */ +static int lockfd; /* file descriptor for locking baseline */ + +static char *rlist[MAX_RLIST]; +static int num_restrs = 0; + +/* + * routine: + * main + * + * purpose: + * argument processing and primary dispatch + * + * returns: + * error codes per filesync.1 (ERR_* in filesync.h) + * + * notes: + * read filesync.1 in order to understand the argument processing + * + * most of the command line options just set some opt_ global + * variable that is later looked at by the code that actually + * implements the features. Only file names are really processed + * in this routine. + */ +void +main(int argc, char **argv) +{ int i; + int c; + errmask_t errs = ERR_OK; + int do_prune = 0; + char *srcname = 0; + char *dstname = 0; + struct base *bp; + + /* keep the error messages simple */ + argv[0] = "filesync"; + + /* gather together all of the options */ + while ((c = getopt(argc, argv, "AaehmnqvyD:E:r:s:d:f:o:")) != EOF) + switch (c) { + case 'a': /* always scan for acls */ + opt_acls = TRUE; + break; + case 'e': /* everything agrees */ + opt_everything = TRUE; + break; + case 'h': /* halt on error */ + opt_halt = TRUE; + break; + case 'm': /* preserve modtimes */ + opt_mtime = TRUE; + break; + case 'n': /* notouch */ + opt_notouch = TRUE; + break; + case 'q': /* quiet */ + opt_quiet = TRUE; + break; + case 'v': /* verbose */ + opt_verbose = TRUE; + break; + case 'y': /* yes */ + opt_yes = TRUE; + break; + case 'D': /* debug options */ + if (!isdigit(optarg[0])) { + dbg_usage(); + exit(ERR_INVAL); + } + opt_debug |= strtol(optarg, (char **) NULL, 0); + break; + + case 'E': /* error simulation */ + if (dbg_set_error(optarg)) { + err_usage(); + exit(ERR_INVAL); + } + opt_errors = TRUE; + break; + + case 'f': /* force conflict resolution */ + switch (optarg[0]) { + case 's': + opt_force = OPT_SRC; + break; + case 'd': + opt_force = OPT_DST; + break; + case 'o': + opt_force = OPT_OLD; + break; + case 'n': + opt_force = OPT_NEW; + break; + default: + fprintf(stderr, + gettext(ERR_badopt), + c, optarg); + errs |= ERR_INVAL; + break; + } + break; + + case 'o': /* one way propagation */ + switch (optarg[0]) { + case 's': + opt_oneway = OPT_SRC; + break; + case 'd': + opt_oneway = OPT_DST; + break; + default: + fprintf(stderr, + gettext(ERR_badopt), + c, optarg); + errs |= ERR_INVAL; + break; + } + break; + + case 'r': /* restricted reconciliation */ + if (num_restrs < MAX_RLIST) + rlist[ num_restrs++ ] = optarg; + else { + fprintf(stderr, gettext(ERR_tomany), + MAX_RLIST); + errs |= ERR_INVAL; + } + break; + + case 's': + if ((srcname = qualify(optarg)) == 0) + errs |= ERR_MISSING; + break; + + case 'd': + if ((dstname = qualify(optarg)) == 0) + errs |= ERR_MISSING; + break; + + default: + case '?': + errs |= ERR_INVAL; + break; + } + + if (opt_debug & DBG_MISC) + fprintf(stderr, "MISC: DBG=%s\n", showflags(dbgmap, opt_debug)); + + /* if we have file names, we need a source and destination */ + if (optind < argc) { + if (srcname == 0) { + fprintf(stderr, gettext(ERR_nosrc)); + errs |= ERR_INVAL; + } + if (dstname == 0) { + fprintf(stderr, gettext(ERR_nodst)); + errs |= ERR_INVAL; + } + } + + /* check for simple usage errors */ + if (errs & ERR_INVAL) { + usage(); + exit(errs); + } + + /* locate our baseline and rules files */ + if (c = findfiles()) + exit(c); + + /* figure out file creation defaults */ + whoami(); + + /* read in our initial baseline */ + if (!new_baseline && (c = read_baseline(file_base))) + errs |= c; + + /* read in the rules file if we need or have rules */ + if (optind >= argc && new_rules) { + fprintf(stderr, ERR_nonames); + errs |= ERR_INVAL; + } else if (!new_rules) + errs |= read_rules(file_rules); + + /* if anything has failed with our setup, go no further */ + if (errs) { + cleanup(errs); + exit(errs); + } + + /* + * figure out whether or not we are willing to do a one-sided + * analysis (where we don't even look at the other side. This + * is an "I'm just curious what has changed" query, and we are + * only willing to do it if: + * we aren't actually going to do anything + * we have a baseline we can compare against + * otherwise, we are going to insist on being able to access + * both the source and destination. + */ + if (opt_notouch && !new_baseline) + opt_onesided = opt_oneway; + + /* + * there are two interested usage scenarios: + * file names specified + * create new rules for the specified files + * evaulate and reconcile only the specified files + * no file names specified + * use already existing rules + * consider restricting them to specified subdirs/files + */ + if (optind < argc) { + /* figure out what base pair we're working on */ + bp = add_base(srcname, dstname); + + /* perverse default rules to avoid trouble */ + if (new_rules) { + errs |= add_ignore(0, SUFX_RULES); + errs |= add_ignore(0, SUFX_BASE); + } + + /* create include rules for each file/dir arg */ + while (optind < argc) + errs |= add_include(bp, argv[ optind++ ]); + + /* + * evaluate the specified base on each side, + * being careful to limit evaulation to new rules + */ + errs |= evaluate(bp, OPT_SRC, TRUE); + errs |= evaluate(bp, OPT_DST, TRUE); + } else { + /* note any possible evaluation restrictions */ + for (i = 0; i < num_restrs; i++) + errs |= add_restr(rlist[i]); + + /* + * we can only prune the baseline file if we have done + * a complete (unrestricted) analysis. + */ + if (i == 0) + do_prune = 1; + + /* evaulate each base on each side */ + for (bp = bases; bp; bp = bp->b_next) { + errs |= evaluate(bp, OPT_SRC, FALSE); + errs |= evaluate(bp, OPT_DST, FALSE); + } + } + + /* if anything serious happened, skip reconciliation */ + if (errs & ERR_FATAL) { + cleanup(errs); + exit(errs); + } + + /* analyze and deal with the differenecs */ + errs |= analyze(); + + /* see if there is any dead-wood in the baseline */ + if (do_prune) { + c = prune(); + + if (c > 0 && opt_verbose) + fprintf(stdout, V_prunes, c); + } + + /* print out a final summary */ + summary(); + + /* update the rules and baseline files (if needed) */ + (void) umask(my_umask); + errs |= write_baseline(file_base); + errs |= write_rules(file_rules); + + if (opt_debug & DBG_MISC) + fprintf(stderr, "MISC: EXIT=%s\n", showflags(errmap, errs)); + + /* just returning ERR_RESOLVABLE upsets some people */ + if (errs == ERR_RESOLVABLE && !opt_notouch) + errs = 0; + + /* all done */ + cleanup(0); + exit(errs); +} + + +/* + * routine: + * usage + * + * purpose: + * print out a usage message + * + * parameters: + * none + * + * returns: + * none + * + * note: + * the -D and -E switches are for development/test/support + * use only and do not show up in the general usage message. + */ +static void +usage(void) +{ + fprintf(stderr, "%s\t%s %s\n", gettext(ERR_usage), "filesync", + gettext(USE_simple)); + fprintf(stderr, "\t%s %s\n", "filesync", gettext(USE_all)); + fprintf(stderr, "\t-a .......... %s\n", gettext(USE_a)); + fprintf(stderr, "\t-e .......... %s\n", gettext(USE_e)); + fprintf(stderr, "\t-h .......... %s\n", gettext(USE_h)); + fprintf(stderr, "\t-m .......... %s\n", gettext(USE_m)); + fprintf(stderr, "\t-n .......... %s\n", gettext(USE_n)); + fprintf(stderr, "\t-q .......... %s\n", gettext(USE_q)); + fprintf(stderr, "\t-v .......... %s\n", gettext(USE_v)); + fprintf(stderr, "\t-y .......... %s\n", gettext(USE_y)); + fprintf(stderr, "\t-s dir ...... %s\n", gettext(USE_s)); + fprintf(stderr, "\t-d dir ...... %s\n", gettext(USE_d)); + fprintf(stderr, "\t-r dir ...... %s\n", gettext(USE_r)); + fprintf(stderr, "\t-f [sdon].... %s\n", gettext(USE_f)); + fprintf(stderr, "\t-o src/dst... %s\n", gettext(USE_o)); +} + +/* + * routine: + * confirm + * + * purpose: + * to confirm that the user is willing to do something dangerous + * + * parameters: + * warning message to be printed + * + * returns: + * void + * + * notes: + * if this is a "notouch" or if the user has pre-confirmed, + * we should not obtain the confirmation and just return that + * the user has confirmed. + */ +void +confirm(char *message) +{ FILE *ttyi, *ttyo; + char ansbuf[ MAX_LINE ]; + + /* if user pre-confirmed, we don't have to ask */ + if (opt_yes || opt_notouch) + return; + + ttyo = fopen("/dev/tty", "w"); + ttyi = fopen("/dev/tty", "r"); + if (ttyi == NULL || ttyo == NULL) + exit(ERR_OTHER); + + /* explain the problem and prompt for confirmation */ + fprintf(ttyo, message); + fprintf(ttyo, gettext(WARN_proceed)); + + /* if the user doesn't kill us, we can continue */ + (void) fgets(ansbuf, sizeof (ansbuf), ttyi); + + /* close the files and return */ + (void) fclose(ttyi); + (void) fclose(ttyo); +} + +void +nomem(char *reason) +{ + fprintf(stderr, gettext(ERR_nomem), reason); + exit(ERR_OTHER); +} + +/* + * routine: + * findfiles + * + * purpose: + * to locate our baseline and rules files + * + * parameters: + * none + * + * returns: + * error mask + * settings of file_base and file_rules + * + * side-effects: + * in order to keep multiple filesyncs from running in parallel + * we put an advisory lock on the baseline file. If the baseline + * file does not exist we create one. The unlocking (and deletion + * of extraneous baselines) is handled in cleanup. + */ +static errmask_t +findfiles(void) /* find rule and baseline files */ +{ char *s, *where; + char namebuf[MAX_PATH]; + int ret; + errmask_t errs = 0; + + /* figure out where the files should be located */ + s = getenv("FILESYNC"); + where = (s && *s) ? expand(s) : expand(DFLT_PRFX); + + /* see if we got a viable name */ + if (where == 0) { + fprintf(stderr, gettext(ERR_nofsync)); + return (ERR_FILES); + } + + /* try to form the name of the rules file */ + strcpy(namebuf, where); + strcat(namebuf, SUFX_RULES); + s = strdup(namebuf); + errs = check_access(namebuf, &new_rules); + + /* if we cannot find a proper rules file, look in the old place */ + if (new_rules && errs == 0) { + strcpy(namebuf, where); + strcat(namebuf, SUFX_OLD); + file_rules = strdup(namebuf); + errs = check_access(namebuf, &new_rules); + + /* if we couldn't find that either, go with new name */ + if (new_rules && errs == 0) + file_rules = s; + } else + file_rules = s; + + /* try to form the name of the baseline file */ + strcpy(namebuf, where); + strcat(namebuf, SUFX_BASE); + file_base = strdup(namebuf); + errs |= check_access(namebuf, &new_baseline); + + if (opt_debug & DBG_FILES) { + fprintf(stderr, "FILE: %s rules file: %s\n", + new_rules ? "new" : "existing", file_rules); + + fprintf(stderr, "FILE: %s base file: %s\n", + new_baseline ? "new" : "existing", file_base); + } + + /* + * in order to lock out other filesync programs we need some + * file we can lock. We do an advisory lock on the baseline + * file. If no baseline file exists, we create an empty one. + */ + if (new_baseline) + lockfd = creat(file_base, 0666); + else + lockfd = open(file_base, O_RDWR); + + if (lockfd < 0) { + fprintf(stderr, new_baseline ? ERR_creat : ERR_open, + TXT_base, file_base); + errs |= ERR_FILES; + } else { + ret = lockf(lockfd, F_TLOCK, 0L); + if (ret < 0) { + fprintf(stderr, ERR_lock, TXT_base, file_base); + errs |= ERR_FILES; + } else if (opt_debug & DBG_FILES) + fprintf(stderr, "FILE: locking baseline file %s\n", + file_base); + } + + return (errs); +} + +/* + * routine: + * cleanup + * + * purpose: + * to clean up temporary files and locking prior to exit + * + * paremeters: + * error mask + * + * returns: + * void + * + * notes: + * if there are no errors, the baseline file is assumed to be good. + * Otherwise, if we created a temporary baseline file (just for + * locking) we will delete it. + */ +static void +cleanup(errmask_t errmask) +{ + /* unlock the baseline file */ + if (opt_debug & DBG_FILES) + fprintf(stderr, "FILE: unlock baseline file %s\n", file_base); + (void) lockf(lockfd, F_ULOCK, 0); + + /* see if we need to delete a temporary copy */ + if (errmask && new_baseline) { + if (opt_debug & DBG_FILES) + fprintf(stderr, "FILE: unlink temp baseline file %s\n", + file_base); + (void) unlink(file_base); + } +} + +/* + * routine: + * check_access + * + * purpose: + * to determine whether or not we can access an existing file + * or create a new one + * + * parameters: + * name of file (in a clobberable buffer) + * pointer to new file flag + * + * returns: + * error mask + * setting of the new file flag + * + * note: + * it is kind of a kluge that this routine clobbers the name, + * but it is only called from one place, it needs a modified + * copy of the name, and the one caller doesn't mind. + */ +static errmask_t +check_access(char *name, int *newflag) +{ char *s; + + /* start out by asking for what we want */ + if (access(name, R_OK|W_OK) == 0) { + *newflag = 0; + return (0); + } + + /* if the problem is isn't non-existance, lose */ + if (errno != ENOENT) { + *newflag = 0; + fprintf(stderr, gettext(ERR_rdwri), name); + return (ERR_FILES); + } + + /* + * the file doesn't exist, so there is still hope if we can + * write in the directory that should contain the file + */ + *newflag = 1; + + /* truncate the file name to its containing directory */ + for (s = name; s[1]; s++); + while (s > name && *s != '/') + s--; + if (s > name) + *s = 0; + else if (*s == '/') + s[1] = 0; + else + name = "."; + + /* then see if we have write access to the directory */ + if (access(name, W_OK) == 0) + return (0); + + fprintf(stderr, gettext(ERR_dirwac), name); + return (ERR_FILES); +} + +/* + * routine: + * whoami + * + * purpose: + * to figure out who I am and what the default modes/ownership + * is on files that I create. + */ +static void +whoami() +{ + my_uid = geteuid(); + my_gid = getegid(); + my_umask = umask(0); + + if (opt_debug & DBG_MISC) + fprintf(stderr, "MISC: my_uid=%ld, my_gid=%ld, my_umask=%03o\n", + my_uid, my_gid, my_umask); +} |