summaryrefslogtreecommitdiff
path: root/usr/src/cmd/filesync/eval.c
diff options
context:
space:
mode:
authorstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
committerstevel@tonic-gate <none@none>2005-06-14 00:00:00 -0700
commit7c478bd95313f5f23a4c958a745db2134aa03244 (patch)
treec871e58545497667cbb4b0a4f2daf204743e1fe7 /usr/src/cmd/filesync/eval.c
downloadillumos-joyent-7c478bd95313f5f23a4c958a745db2134aa03244.tar.gz
OpenSolaris Launch
Diffstat (limited to 'usr/src/cmd/filesync/eval.c')
-rw-r--r--usr/src/cmd/filesync/eval.c997
1 files changed, 997 insertions, 0 deletions
diff --git a/usr/src/cmd/filesync/eval.c b/usr/src/cmd/filesync/eval.c
new file mode 100644
index 0000000000..8c63f11949
--- /dev/null
+++ b/usr/src/cmd/filesync/eval.c
@@ -0,0 +1,997 @@
+/*
+ * 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:
+ * eval.c
+ *
+ * purpose:
+ * routines to ascertain the current status of all of the files
+ * described by a set of rules. Some of the routines that update
+ * file status information are also called later (during reconcilation)
+ * to reflect the changes that have been made to files.
+ *
+ * contents:
+ * evaluate top level - evaluate one side of one base
+ * add_file_arg (static) add a file to the list of files to evaluate
+ * eval_file (static) stat a specific file, recurse on directories
+ * walker (static) node visitor for recursive descent
+ * note_info update a file_info structure from a stat structure
+ * do_update (static) update one file_info structure from another
+ * update_info update the baseline file_info from the prevailng side
+ * fakedata (static) make it look like one side hasn't changed
+ * check_inum (static) sanity check to detect wrong-dir errors
+ * add_glob (static) expand a wildcard in an include rule
+ * add_run (static) run a program to generate an include list
+ *
+ * notes:
+ * pay careful attention to the use of the LISTED and EVALUATE
+ * flags in each file description structure.
+ */
+
+#pragma ident "%Z%%M% %I% %E% SMI"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <string.h>
+#include <glob.h>
+#include <ftw.h>
+#include <sys/mkdev.h>
+#include <errno.h>
+
+#include "filesync.h"
+#include "database.h"
+#include "messages.h"
+#include "debug.h"
+
+/*
+ * routines:
+ */
+static errmask_t eval_file(struct base *, struct file *);
+static errmask_t add_file_arg(struct base *, char *);
+static int walker(const char *, const struct stat *, int, struct FTW *);
+static errmask_t add_glob(struct base *, char *);
+static errmask_t add_run(struct base *, char *);
+static void check_inum(struct file *, int);
+static void fakedata(struct file *, int);
+
+/*
+ * globals
+ */
+static bool_t usingsrc; /* this pass is on the source side */
+static int walk_errs; /* errors found in tree walk */
+static struct file *cur_dir; /* base directory for this pass */
+static struct base *cur_base; /* base pointer for this pass */
+
+/*
+ * routine:
+ * evaluate
+ *
+ * purpose:
+ * to build up a baseline description for all of the files
+ * under one side of one base pair (as specified by the rules
+ * for that base pair).
+ *
+ * parameters:
+ * pointer to the base to be evaluated
+ * source/destination indication
+ * are we restricted to new rules
+ *
+ * returns:
+ * error mask
+ *
+ * notes:
+ * we evaluate source and destination separately, and
+ * reinterpret the include rules on each side (since there
+ * may be wild cards and programs that must be evaluated
+ * in a specific directory context). Similarly the ignore
+ * rules must be interpreted anew for each base.
+ */
+errmask_t
+evaluate(struct base *bp, side_t srcdst, bool_t newrules)
+{ errmask_t errs = 0;
+ char *dir;
+ struct rule *rp;
+ struct file *fp;
+
+ /* see if this base is still relevant */
+ if ((bp->b_flags & F_LISTED) == 0)
+ return (0);
+
+ /* figure out what this pass is all about */
+ usingsrc = (srcdst == OPT_SRC);
+
+ /*
+ * the ignore engine maintains considerable per-base-directory
+ * state, and so must be reset at the start of a new tree.
+ */
+ ignore_reset();
+
+ /* all evaluation must happen from the appropriate directory */
+ dir = usingsrc ? bp->b_src_name : bp->b_dst_name;
+ if (chdir(dir) < 0) {
+ fprintf(stderr, gettext(ERR_chdir), dir);
+
+ /*
+ * if we have -n -o we are actually willing to
+ * pretend that nothing has changed on the missing
+ * side. This is actually useful on a disconnected
+ * notebook to ask what has been changed so far.
+ */
+ if (opt_onesided == (usingsrc ? OPT_DST : OPT_SRC)) {
+ for (fp = bp->b_files; fp; fp = fp->f_next)
+ fakedata(fp, srcdst);
+
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr, "EVAL: FAKE DATA %s dir=%s\n",
+ usingsrc ? "SRC" : "DST", dir);
+ return (0);
+ } else
+ return (ERR_NOBASE);
+ }
+
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr, "EVAL: base=%d, %s dir=%s\n",
+ bp->b_ident, usingsrc ? "SRC" : "DST", dir);
+
+ /* assemble the include list */
+ for (rp = bp->b_includes; rp; rp = rp->r_next) {
+
+ /* see if we are skipping old rules */
+ if (newrules && ((rp->r_flags & R_NEW) == 0))
+ continue;
+
+ if (rp->r_flags & R_PROGRAM)
+ errs |= add_run(bp, rp->r_file);
+ else if (rp->r_flags & R_WILD)
+ errs |= add_glob(bp, rp->r_file);
+ else
+ errs |= add_file_arg(bp, rp->r_file);
+ }
+
+ /* assemble the base-specific exclude list */
+ for (rp = bp->b_excludes; rp; rp = rp->r_next)
+ if (rp->r_flags & R_PROGRAM)
+ ignore_pgm(rp->r_file);
+ else if (rp->r_flags & R_WILD)
+ ignore_expr(rp->r_file);
+ else
+ ignore_file(rp->r_file);
+
+ /* add in the global excludes */
+ for (rp = omnibase.b_excludes; rp; rp = rp->r_next)
+ if (rp->r_flags & R_WILD)
+ ignore_expr(rp->r_file);
+ else
+ ignore_file(rp->r_file);
+
+ /*
+ * because of restriction lists and new-rules, the baseline
+ * may contain many more files than we are actually supposed
+ * to look at during the impending evaluation/analysis phases
+ *
+ * when LIST arguments are encountered within a rule, we turn
+ * on the LISTED flag for the associated files. We only evaluate
+ * files that have the LISTED flag. We turn the LISTED flag off
+ * after evaluating them because just because a file was enumerated
+ * in the source doesn't mean that will necessarily be enumerated
+ * in the destination.
+ */
+ for (fp = bp->b_files; fp; fp = fp->f_next)
+ if (fp->f_flags & F_LISTED) {
+ errs |= eval_file(bp, fp);
+ fp->f_flags &= ~F_LISTED;
+ }
+
+ /* note that this base has been evaluated */
+ bp->b_flags |= F_EVALUATE;
+
+ return (errs);
+}
+
+/*
+ * routine:
+ * add_file_arg
+ *
+ * purpose:
+ * to create file node(s) under a specified base for an explictly
+ * included file.
+ *
+ * parameters:
+ * pointer to associated base
+ * name of the file
+ *
+ * returns:
+ * error mask
+ *
+ * notes:
+ * the trick is that an include LIST argument need not be a file
+ * in the base directory, but may be a path passing through
+ * several intermediate directories. If this is the case we
+ * need to ensure that all of those directories are added to
+ * the tree SPARSELY since it is not intended that they be
+ * expanded during the course of evaluation.
+ *
+ * we ignore arguments that end in .. because they have the
+ * potential to walk out of the base tree, because it can
+ * result in different names for a single file, and because
+ * should never be necessary to specify files that way.
+ */
+static errmask_t
+add_file_arg(struct base *bp, char *path)
+{ int i;
+ errmask_t errs = 0;
+ struct file *dp = 0;
+ struct file *fp;
+ char *s, *p;
+ char name[ MAX_NAME ];
+
+ /*
+ * see if someone is trying to feed us a ..
+ */
+ if (strcmp(path, "..") == 0 || prefix(path, "../") ||
+ suffix(path, "/..") || contains(path, "/../")) {
+ fprintf(stderr, gettext(WARN_ignore), path);
+ return (ERR_MISSING);
+ }
+
+ /*
+ * strip off any trailing "/." or "/"
+ * since noone will miss these, it is safe to actually
+ * take them off the name. When we fall out of this
+ * loop, s will point where the null belongs. We don't
+ * actually null the end of string yet because we want
+ * to leave it pristine for error messages.
+ */
+ for (s = path; *s; s++);
+ while (s > path) {
+ if (s[-1] == '/') {
+ s--;
+ continue;
+ }
+ if (s[-1] == '.' && s > &path[1] && s[-2] == '/') {
+ s -= 2;
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * skip over leading "/" and "./" (but not over a lone ".")
+ */
+ for (p = path; p < s; ) {
+ if (*p == '/') {
+ p++;
+ continue;
+ }
+ if (*p == '.' && s > &p[1] && p[1] == '/') {
+ p += 2;
+ continue;
+ }
+ break;
+ }
+
+ /*
+ * if there is nothing left, we're miffed, but done
+ */
+ if (p >= s) {
+ fprintf(stderr, gettext(WARN_ignore), path);
+ return (ERR_MISSING);
+ } else {
+ /*
+ * this is actually storing a null into the argument,
+ * but it is OK to do this because the stuff we are
+ * truncating really is garbage that noone will ever
+ * want to see.
+ */
+ *s = 0;
+ path = p;
+ }
+
+ /*
+ * see if there are any restrictions that would force
+ * us to ignore this argument
+ */
+ if (check_restr(bp, path) == 0)
+ return (0);
+
+ while (*path) {
+ /* lex off the next name component */
+ for (i = 0; path[i] && path[i] != '/'; i++)
+ name[i] = path[i];
+ name[i] = 0;
+
+ /* add it into the database */
+ fp = (dp == 0) ? add_file_to_base(bp, name)
+ : add_file_to_dir(dp, name);
+
+ /* see if this was an intermediate directory */
+ if (path[i] == '/') {
+ fp->f_flags |= F_LISTED | F_SPARSE;
+ path += i+1;
+ } else {
+ fp->f_flags |= F_LISTED;
+ path += i;
+ }
+
+ dp = fp;
+ }
+
+ return (errs);
+}
+
+/*
+ * routine:
+ * eval_file
+ *
+ * purpose:
+ * to evaluate one named file under a particular directory
+ *
+ * parameters:
+ * pointer to base structure
+ * pointer to file structure
+ *
+ * returns:
+ * error mask
+ * filled in evaluations in the baseline
+ *
+ * note:
+ * due to new rules and other restrictions we may not be expected
+ * to evaluate the entire tree. We should only be called on files
+ * that are LISTed, and we should only invoke ourselves recursively
+ * on such files.
+ */
+static errmask_t
+eval_file(struct base *bp, struct file *fp)
+{ errmask_t errs = 0;
+ int rc;
+ char *name;
+ struct file *cp;
+ struct stat statb;
+
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr, "EVAL: FILE, flags=%s, name=%s\n",
+ showflags(fileflags, fp->f_flags), fp->f_name);
+
+ /* stat the file and fill in the file structure information */
+ name = get_name(fp);
+
+#ifdef DBG_ERRORS
+ /* see if we should simulated a stat error on this file */
+ if (opt_errors && (errno = dbg_chk_error(name, usingsrc ? 's' : 'S')))
+ rc = -1;
+ else
+#endif
+ rc = lstat(name, &statb);
+
+ if (rc < 0) {
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr, "EVAL: FAIL lstat, errno=%d\n", errno);
+ switch (errno) {
+ case EACCES:
+ fp->f_flags |= F_STAT_ERROR;
+ return (ERR_PERM);
+ case EOVERFLOW:
+ fp->f_flags |= F_STAT_ERROR;
+ return (ERR_UNRESOLVED);
+ default:
+ return (ERR_MISSING);
+ }
+ }
+
+ /* record the information we've just gained */
+ note_info(fp, &statb, usingsrc ? OPT_SRC : OPT_DST);
+
+ /*
+ * checking for ACLs is expensive, so we only do it if we
+ * have been asked to, or if we have reason to believe that
+ * the file has an ACL
+ */
+ if (opt_acls || fp->f_info[OPT_BASE].f_numacls)
+ (void) get_acls(name,
+ &fp->f_info[usingsrc ? OPT_SRC : OPT_DST]);
+
+
+ /* note that this file has been evaluated */
+ fp->f_flags |= F_EVALUATE;
+
+ /* if it is not a directory, a simple stat will suffice */
+ if ((statb.st_mode & S_IFMT) != S_IFDIR)
+ return (0);
+
+ /*
+ * as a sanity check, we look for changes in the I-node
+ * numbers associated with LISTed directories ... on the
+ * assumption that these are high-enough up on the tree
+ * that they aren't likely to change, and so a change
+ * might indicate trouble.
+ */
+ if (fp->f_flags & F_LISTED)
+ check_inum(fp, usingsrc);
+
+ /*
+ * sparse directories are on the path between a base and
+ * a listed directory. As such, we don't walk these
+ * directories. Rather, we just enumerate the LISTed
+ * files.
+ */
+ if (fp->f_flags & F_SPARSE) {
+ push_name(fp->f_name);
+
+ /* this directory isn't supposed to be fully walked */
+ for (cp = fp->f_files; cp; cp = cp->f_next)
+ if (cp->f_flags & F_LISTED) {
+ errs |= eval_file(bp, cp);
+ cp->f_flags &= ~F_LISTED;
+ }
+ pop_name();
+ } else {
+ /* fully walk the tree beneath this directory */
+ walk_errs = 0;
+ cur_base = bp;
+ cur_dir = fp;
+ nftw(get_name(fp), &walker, MAX_DEPTH, FTW_PHYS|FTW_MOUNT);
+ errs |= walk_errs;
+ }
+
+ return (errs);
+}
+
+/*
+ * routine:
+ * walker
+ *
+ * purpose:
+ * node visitor for recursive directory enumeration
+ *
+ * parameters:
+ * name of file
+ * pointer to stat buffer for file
+ * file type
+ * FTW structure (base name offset, walk-depth)
+ *
+ * returns:
+ * 0 continue
+ * -1 stop
+ *
+ * notes:
+ * Ignoring files is easy, but ignoring directories is harder.
+ * Ideally we would just decline to walk the trees beneath
+ * ignored directories, but ftw doesn't allow the walker to
+ * tell it to "don't enter this directory, but continue".
+ *
+ * Instead, we have to set a global to tell us to ignore
+ * everything under that tree. The variable ignore_level
+ * is set to a level, below which, everything should be
+ * ignored. Once the enumeration rises above that level
+ * again, we clear it.
+ */
+static int
+walker(const char *name, const struct stat *sp, int type,
+ struct FTW *ftwx)
+{ const char *path;
+ struct file *fp;
+ int level;
+ int which;
+ bool_t restr;
+ static struct file *dirstack[ MAX_DEPTH + 1 ];
+ static int ignore_level = 0;
+
+ path = &name[ftwx->base];
+ level = ftwx->level;
+ which = usingsrc ? OPT_SRC : OPT_DST;
+
+ /*
+ * see if we are ignoring all files in this sub-tree
+ */
+ if (ignore_level > 0 && level >= ignore_level) {
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr, "EVAL: SKIP file=%s\n", name);
+ return (0);
+ } else
+ ignore_level = 0; /* we're through ignoring */
+
+#ifdef DBG_ERRORS
+ /* see if we should simulated a stat error on this file */
+ if (opt_errors && dbg_chk_error(name, usingsrc ? 'n' : 'N'))
+ type = FTW_NS;
+#endif
+
+ switch (type) {
+ case FTW_F: /* file */
+ case FTW_SL: /* symbolic link */
+ /*
+ * filter out files of inappropriate types
+ */
+ switch (sp->st_mode & S_IFMT) {
+ default: /* anything else we ignore */
+ return (0);
+
+ case S_IFCHR:
+ case S_IFBLK:
+ case S_IFREG:
+ case S_IFLNK:
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr,
+ "EVAL: WALK lvl=%d, file=%s\n",
+ level, path);
+
+ /* see if we were told to ignore this one */
+ if (ignore_check(path))
+ return (0);
+
+ fp = add_file_to_dir(dirstack[level-1], path);
+ note_info(fp, sp, which);
+
+ /* note that this file has been evaluated */
+ fp->f_flags |= F_EVALUATE;
+
+ /* see if we should check ACLs */
+ if ((sp->st_mode & S_IFMT) == S_IFLNK)
+ return (0);
+
+ if (fp->f_info[OPT_BASE].f_numacls || opt_acls)
+ (void) get_acls(name,
+ &fp->f_info[which]);
+
+ return (0);
+ }
+
+ case FTW_D: /* enter directory */
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr, "EVAL: WALK lvl=%d, dir=%s\n",
+ level, name);
+
+ /*
+ * if we have been told to ignore this directory, we should
+ * ignore all files under it. Similarly, if we are outside
+ * of our restrictions, we should ignore the entire subtree
+ */
+ restr = check_restr(cur_base, name);
+ if (restr == FALSE || ignore_check(path)) {
+ ignore_level = level + 1;
+ return (0);
+ }
+
+ fp = (level == 0) ? cur_dir :
+ add_file_to_dir(dirstack[level-1], path);
+
+ note_info(fp, sp, which);
+
+ /* see if we should be checking ACLs */
+ if (opt_acls || fp->f_info[OPT_BASE].f_numacls)
+ (void) get_acls(name, &fp->f_info[which]);
+
+ /* note that this file has been evaluated */
+ fp->f_flags |= F_EVALUATE;
+
+ /* note the parent of the children to come */
+ dirstack[ level ] = fp;
+
+ /*
+ * PROBLEM: given the information that nftw provides us with,
+ * how do we know that we have confirmed the fact
+ * that a file no longer exists. Or to rephrase
+ * this in filesync terms, how do we know when to
+ * set the EVALUATE flag for a file we didn't find.
+ *
+ * if we are going to fully scan this directory (we
+ * are completely within our restrictions) then we
+ * will be confirming the non-existance of files that
+ * used to be here. Thus any file that was in the
+ * base line under this directory should be considered
+ * to have been evaluated (whether we found it or not).
+ *
+ * if, however, we are only willing to scan selected
+ * files (due to restrictions), or the file was not
+ * in the baseline, then we should not assume that this
+ * pass will evaluate it.
+ */
+ if (restr == TRUE)
+ for (fp = fp->f_files; fp; fp = fp->f_next) {
+ if ((fp->f_flags & F_IN_BASELINE) == 0)
+ continue;
+ fp->f_flags |= F_EVALUATE;
+ }
+
+ return (0);
+
+ case FTW_DP: /* end of directory */
+ dirstack[ level ] = 0;
+ break;
+
+ case FTW_DNR: /* unreadable directory */
+ walk_errs |= ERR_PERM;
+ /* FALLTHROUGH */
+ case FTW_NS: /* unstatable file */
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr, "EVAL: walker can't stat/read %s\n",
+ name);
+ fp = (level == 0) ? cur_dir :
+ add_file_to_dir(dirstack[level-1], path);
+ fp->f_flags |= F_STAT_ERROR;
+ walk_errs |= ERR_UNRESOLVED;
+ break;
+ }
+
+ return (0);
+}
+
+/*
+ * routine:
+ * note_info
+ *
+ * purpose:
+ * to record information about a file in its file node
+ *
+ * parameters
+ * file node pointer
+ * stat buffer
+ * which file info structure to fill in (0-2)
+ *
+ * returns
+ * void
+ */
+void
+note_info(struct file *fp, const struct stat *sp, side_t which)
+{ struct fileinfo *ip;
+ static int flags[3] = { F_IN_BASELINE, F_IN_SOURCE, F_IN_DEST };
+
+ ip = &fp->f_info[ which ];
+
+ ip->f_ino = sp->st_ino;
+ ip->f_d_maj = major(sp->st_dev);
+ ip->f_d_min = minor(sp->st_dev);
+ ip->f_type = sp->st_mode & S_IFMT;
+ ip->f_size = sp->st_size;
+ ip->f_mode = sp->st_mode & S_IAMB;
+ ip->f_uid = sp->st_uid;
+ ip->f_gid = sp->st_gid;
+ ip->f_modtime = sp->st_mtim.tv_sec;
+ ip->f_modns = sp->st_mtim.tv_nsec;
+ ip->f_nlink = sp->st_nlink;
+ ip->f_rd_maj = major(sp->st_rdev);
+ ip->f_rd_min = minor(sp->st_rdev);
+
+ /* indicate where this file has been found */
+ fp->f_flags |= flags[which];
+
+ if (opt_debug & DBG_STAT)
+ fprintf(stderr,
+ "STAT: list=%d, file=%s, mod=%08lx.%08lx, nacl=%d\n",
+ which, fp->f_name, ip->f_modtime, ip->f_modns,
+ ip->f_numacls);
+}
+
+/*
+ * routine:
+ * do_update
+ *
+ * purpose:
+ * to copy information from one side into the baseline in order
+ * to reflect the effects of recent reconciliation actions
+ *
+ * parameters
+ * fileinfo structure to be updated
+ * fileinfo structure to be updated from
+ *
+ * returns
+ * void
+ *
+ * note:
+ * we play fast and loose with the copying of acl chains
+ * here, but noone is going to free or reuse any of this
+ * memory anyway. None the less, I do feel embarassed.
+ */
+static void
+do_update(struct fileinfo *np, struct fileinfo *ip)
+{
+ /* get most of the fields from the designated "right" copy */
+ np->f_type = ip->f_type;
+ np->f_size = ip->f_size;
+ np->f_mode = ip->f_mode;
+ np->f_uid = ip->f_uid;
+ np->f_gid = ip->f_gid;
+ np->f_rd_maj = ip->f_rd_maj;
+ np->f_rd_min = ip->f_rd_min;
+
+ /* see if facls have to be propagated */
+ np->f_numacls = ip->f_numacls;
+ np->f_acls = ip->f_acls;
+}
+
+/*
+ * routine:
+ * update_info
+ *
+ * purpose:
+ * to update the baseline to reflect recent reconcliations
+ *
+ * parameters
+ * file node pointer
+ * which file info structure to trust (1/2)
+ *
+ * returns
+ * void
+ *
+ * note:
+ * after we update this I-node we run down the entire
+ * change list looking for links and update them too.
+ * This is to ensure that when subsequent links get
+ * reconciled, they are already found to be up-to-date.
+ */
+void
+update_info(struct file *fp, side_t which)
+{
+ /* first update the specified fileinfo structure */
+ do_update(&fp->f_info[ OPT_BASE ], &fp->f_info[ which ]);
+
+ if (opt_debug & DBG_STAT)
+ fprintf(stderr,
+ "STAT: UPDATE from=%d, file=%s, mod=%08lx.%08lx\n",
+ which, fp->f_name, fp->f_info[ which ].f_modtime,
+ fp->f_info[ which ].f_modns);
+}
+
+/*
+ * routine:
+ * fakedata
+ *
+ * purpose:
+ * to populate a tree we cannot analyze with information from the baseline
+ *
+ * parameters:
+ * file to be faked
+ * which side to fake
+ *
+ * notes:
+ * We would never use this for real reconciliation, but it is useful
+ * if a disconnected notebook user wants to find out what has been
+ * changed so far. We only do this if we are notouch and oneway.
+ */
+static void
+fakedata(struct file *fp, int which)
+{ struct file *lp;
+
+ /* pretend we actually found the file */
+ fp->f_flags |= (which == OPT_SRC) ? F_IN_SOURCE : F_IN_DEST;
+
+ /* update the specified side from the baseline */
+ do_update(&fp->f_info[ which ], &fp->f_info[ OPT_BASE ]);
+ fp->f_info[which].f_nlink = (which == OPT_SRC) ? fp->f_s_nlink :
+ fp->f_d_nlink;
+ fp->f_info[which].f_modtime = (which == OPT_SRC) ? fp->f_s_modtime :
+ fp->f_d_modtime;
+
+ for (lp = fp->f_files; lp; lp = lp->f_next)
+ fakedata(lp, which);
+}
+
+/*
+ * routine:
+ * check_inum
+ *
+ * purpose:
+ * sanity check inode #s on directories that are unlikely to change
+ *
+ * parameters:
+ * pointer to file node
+ * are we using the source
+ *
+ * note:
+ * the purpose of this sanity check is to catch a case where we
+ * have somehow been pointed at a directory that is not the one
+ * we expected to be reconciling against. It could happen if a
+ * variable wasn't properly set, or if we were in a new domain
+ * where an old path no longer worked. This could result in
+ * bazillions of inappropriate propagations and deletions.
+ */
+void
+check_inum(struct file *fp, int src)
+{ struct fileinfo *ip;
+
+ /*
+ * we validate the inode number and the major device numbers ... minor
+ * device numbers for NFS devices are arbitrary
+ */
+ if (src) {
+ ip = &fp->f_info[ OPT_SRC ];
+ if (ip->f_ino == fp->f_s_inum && ip->f_d_maj == fp->f_s_maj)
+ return;
+
+ /* if file was newly created/deleted, this isn't warnable */
+ if (fp->f_s_inum == 0 || ip->f_ino == 0)
+ return;
+
+ if (opt_verbose)
+ fprintf(stdout, V_change, fp->f_name, TXT_src,
+ fp->f_s_maj, fp->f_s_min, fp->f_s_inum,
+ ip->f_d_maj, ip->f_d_min, ip->f_ino);
+ } else {
+ ip = &fp->f_info[ OPT_DST ];
+ if (ip->f_ino == fp->f_d_inum && ip->f_d_maj == fp->f_d_maj)
+ return;
+
+ /* if file was newly created/deleted, this isn't warnable */
+ if (fp->f_d_inum == 0 || ip->f_ino == 0)
+ return;
+
+ if (opt_verbose)
+ fprintf(stdout, V_change, fp->f_name, TXT_dst,
+ fp->f_d_maj, fp->f_d_min, fp->f_d_inum,
+ ip->f_d_maj, ip->f_d_min, ip->f_ino);
+ }
+
+ /* note that something has changed */
+ inum_changes++;
+}
+
+/*
+ * routine:
+ * add_glob
+ *
+ * purpose:
+ * to evaluate a wild-carded expression into names, and add them
+ * to the evaluation list.
+ *
+ * parameters:
+ * base
+ * expression
+ *
+ * returns:
+ * error mask
+ *
+ * notes:
+ * we don't want to allow any patterns to expand to a . because
+ * that could result in re-evaluation of a tree under a different
+ * name. The real thing we are worried about here is ".*" which
+ * is meant to pick up . files, but shouldn't pick up . and ..
+ */
+static errmask_t
+add_glob(struct base *bp, char *expr)
+{ int i;
+ errmask_t errs = 0;
+#ifndef BROKEN_GLOB
+ glob_t gt;
+ char *s;
+
+ /* expand the regular expression */
+ i = glob(expr, GLOB_NOSORT, 0, &gt);
+ if (i == GLOB_NOMATCH)
+ return (ERR_MISSING);
+ if (i) {
+ /* this shouldn't happen, so it's cryptic message time */
+ fprintf(stderr, "EVAL: add_glob globfail expr=%s, ret=%d\n",
+ expr, i);
+ return (ERR_OTHER);
+ }
+
+ for (i = 0; i < gt.gl_pathc; i++) {
+ /* make sure we don't let anything expand to a . */
+ s = basename(gt.gl_pathv[i]);
+ if (strcmp(s, ".") == 0) {
+ fprintf(stderr, gettext(WARN_ignore), gt.gl_pathv[i]);
+ errs |= ERR_MISSING;
+ continue;
+ }
+
+ errs |= add_file_arg(bp, gt.gl_pathv[i]);
+ }
+
+ globfree(&gt);
+#else
+ /*
+ * in 2.4 the glob function was completely broken. The
+ * easiest way to get around this problem is to just ask
+ * the shell to do the work for us. This is much slower
+ * but produces virtually identical results. Given that
+ * the 2.4 version is internal use only, I probably won't
+ * worry about the performance difference (less than 2
+ * seconds for a typical filesync command, and no hit
+ * at all if they don't use regular expressions in
+ * their LIST rules).
+ */
+ char cmdbuf[MAX_LINE];
+
+ sprintf(cmdbuf, "ls -d %s 2> /dev/null", expr);
+ errs |= add_run(bp, cmdbuf);
+#endif
+
+ return (errs);
+}
+
+
+/*
+ * routine:
+ * add_run
+ *
+ * purpose:
+ * to run a command and capture the names it outputs in the
+ * evaluation list.
+ *
+ * parameters
+ * base
+ * command
+ *
+ * returns:
+ * error mask
+ */
+static errmask_t
+add_run(struct base *bp, char *cmd)
+{ char *s, *p;
+ FILE *fp;
+ char inbuf[ MAX_LINE ];
+ errmask_t errs = 0;
+ int added = 0;
+
+ if (opt_debug & DBG_EVAL)
+ fprintf(stderr, "EVAL: RUN %s\n", cmd);
+
+ /* run the command and collect its ouput */
+ fp = popen(cmd, "r");
+ if (fp == NULL) {
+ fprintf(stderr, gettext(ERR_badrun), cmd);
+ return (ERR_OTHER);
+ }
+
+ while (fgets(inbuf, sizeof (inbuf), fp) != 0) {
+ /* strip off any trailing newline */
+ for (s = inbuf; *s && *s != '\n'; s++);
+ *s = 0;
+
+ /* skip any leading white space */
+ for (s = inbuf; *s == ' ' || *s == '\t'; s++);
+
+ /* make sure we don't let anything expand to a . */
+ p = basename(s);
+ if (strcmp(p, ".") == 0) {
+ fprintf(stderr, gettext(WARN_ignore), s);
+ errs |= ERR_MISSING;
+ continue;
+ }
+
+ /* add this file to the list */
+ if (*s) {
+ errs |= add_file_arg(bp, s);
+ added++;
+ }
+ }
+
+ pclose(fp);
+
+#ifdef BROKEN_GLOB
+ /*
+ * if we are being used to simulate libc glob, and we didn't
+ * return anything, we should probably assume that the regex
+ * was unable to match anything
+ */
+ if (added == 0)
+ errs |= ERR_MISSING;
+#endif
+ return (errs);
+}