main.c revision f48205be61a214698b763ff550ab9e657525104c
/*
* 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 <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.
*/
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-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=%u, my_gid=%u, my_umask=%03o\n",
my_uid, my_gid, my_umask);
}