/*
* 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
* 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 <errno.h>
#include "filesync.h"
#include "database.h"
#include "messages.h"
#include "debug.h"
/*
* routines:
*/
static void check_inum(struct file *, int);
/*
* globals
*/
/*
* 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.
*/
char *dir;
/* see if this base is still relevant */
return (0);
/* figure out what this pass is all about */
/*
* 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 */
/*
* 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.
*/
return (0);
} else
return (ERR_NOBASE);
}
/* assemble the include list */
/* see if we are skipping old rules */
continue;
else
}
/* assemble the base-specific exclude list */
else
/* add in the global excludes */
else
/*
* 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.
*/
}
/* note that this base has been evaluated */
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
{ int i;
char *s, *p;
/*
* see if someone is trying to feed us a ..
*/
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;
}
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) {
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
*/
return (0);
while (*path) {
/* lex off the next name component */
name[i] = 0;
/* add it into the database */
/* see if this was an intermediate directory */
if (path[i] == '/') {
path += i+1;
} else {
path += i;
}
}
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
int rc;
char *name;
/* stat the file and fill in the file structure information */
#ifdef DBG_ERRORS
/* see if we should simulated a stat error on this file */
rc = -1;
else
#endif
if (rc < 0) {
switch (errno) {
case EACCES:
return (ERR_PERM);
case EOVERFLOW:
return (ERR_UNRESOLVED);
default:
return (ERR_MISSING);
}
}
/* record the information we've just gained */
/*
* 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
*/
/* note that this file has been evaluated */
/* if it is not a directory, a simple stat will suffice */
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.
*/
/*
* 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.
*/
/* this directory isn't supposed to be fully walked */
}
pop_name();
} else {
/* fully walk the tree beneath this directory */
walk_errs = 0;
}
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
{ const char *path;
int level;
int which;
static int ignore_level = 0;
/*
* see if we are ignoring all files in this sub-tree
*/
return (0);
} else
ignore_level = 0; /* we're through ignoring */
#ifdef DBG_ERRORS
/* see if we should simulated a stat error on this file */
#endif
switch (type) {
case FTW_F: /* file */
case FTW_SL: /* symbolic link */
/*
* filter out files of inappropriate types
*/
default: /* anything else we ignore */
return (0);
case S_IFCHR:
case S_IFBLK:
case S_IFREG:
case S_IFLNK:
"EVAL: WALK lvl=%d, file=%s\n",
/* see if we were told to ignore this one */
if (ignore_check(path))
return (0);
/* note that this file has been evaluated */
/* see if we should check ACLs */
return (0);
return (0);
}
case FTW_D: /* enter directory */
/*
* 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
*/
return (0);
}
/* see if we should be checking ACLs */
/* note that this file has been evaluated */
/* note the parent of the children to come */
/*
* 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.
*/
continue;
}
return (0);
case FTW_DP: /* end of directory */
break;
case FTW_DNR: /* unreadable directory */
/* FALLTHROUGH */
case FTW_NS: /* unstatable file */
name);
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
/* indicate where this file has been found */
"STAT: list=%d, file=%s, mod=%08lx.%08lx, nacl=%d\n",
}
/*
* 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
{
/* get most of the fields from the designated "right" copy */
/* see if facls have to be propagated */
}
/*
* 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
{
/* first update the specified fileinfo structure */
"STAT: UPDATE from=%d, file=%s, mod=%08lx.%08lx\n",
}
/*
* 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
/* pretend we actually found the file */
/* update the specified side from the baseline */
}
/*
* 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
/*
* we validate the inode number and the major device numbers ... minor
* device numbers for NFS devices are arbitrary
*/
if (src) {
return;
return;
if (opt_verbose)
} else {
return;
return;
if (opt_verbose)
}
/* 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
{ int i;
#ifndef BROKEN_GLOB
char *s;
/* expand the regular expression */
if (i == GLOB_NOMATCH)
return (ERR_MISSING);
if (i) {
/* this shouldn't happen, so it's cryptic message time */
expr, i);
return (ERR_OTHER);
}
/* make sure we don't let anything expand to a . */
if (strcmp(s, ".") == 0) {
errs |= ERR_MISSING;
continue;
}
}
#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).
*/
#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
{ char *s, *p;
int added = 0;
/* run the command and collect its ouput */
return (ERR_OTHER);
}
/* strip off any trailing newline */
for (s = inbuf; *s && *s != '\n'; s++);
*s = 0;
/* skip any leading white space */
/* make sure we don't let anything expand to a . */
p = basename(s);
if (strcmp(p, ".") == 0) {
errs |= ERR_MISSING;
continue;
}
/* add this file to the list */
if (*s) {
added++;
}
}
#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);
}