/*
* 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);
}