/* * 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 2007 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 #include #include #include #include #include #include #include #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. */ int 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); return (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-existence, 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=%u, my_gid=%u, my_umask=%03o\n", my_uid, my_gid, my_umask); }