1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright 1995-2003 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 /*
  28  * module:
  29  *      eval.c
  30  *
  31  * purpose:
  32  *      routines to ascertain the current status of all of the files
  33  *      described by a set of rules.  Some of the routines that update
  34  *      file status information are also called later (during reconcilation)
  35  *      to reflect the changes that have been made to files.
  36  *
  37  * contents:
  38  *      evaluate        top level - evaluate one side of one base
  39  *      add_file_arg    (static) add a file to the list of files to evaluate
  40  *      eval_file       (static) stat a specific file, recurse on directories
  41  *      walker          (static) node visitor for recursive descent
  42  *      note_info       update a file_info structure from a stat structure
  43  *      do_update       (static) update one file_info structure from another
  44  *      update_info     update the baseline file_info from the prevailng side
  45  *      fakedata        (static) make it look like one side hasn't changed
  46  *      check_inum      (static) sanity check to detect wrong-dir errors
  47  *      add_glob        (static) expand a wildcard in an include rule
  48  *      add_run         (static) run a program to generate an include list
  49  *
  50  * notes:
  51  *      pay careful attention to the use of the LISTED and EVALUATE
  52  *      flags in each file description structure.
  53  */
  54 
  55 #pragma ident   "%Z%%M% %I%     %E% SMI"
  56 
  57 #include <stdio.h>
  58 #include <stdlib.h>
  59 #include <libgen.h>
  60 #include <unistd.h>
  61 #include <string.h>
  62 #include <glob.h>
  63 #include <ftw.h>
  64 #include <sys/mkdev.h>
  65 #include <errno.h>
  66 
  67 #include "filesync.h"
  68 #include "database.h"
  69 #include "messages.h"
  70 #include "debug.h"
  71 
  72 /*
  73  * routines:
  74  */
  75 static errmask_t eval_file(struct base *, struct file *);
  76 static errmask_t add_file_arg(struct base *, char *);
  77 static int walker(const char *, const struct stat *, int, struct FTW *);
  78 static errmask_t add_glob(struct base *, char *);
  79 static errmask_t add_run(struct base *, char *);
  80 static void check_inum(struct file *, int);
  81 static void fakedata(struct file *, int);
  82 
  83 /*
  84  * globals
  85  */
  86 static bool_t usingsrc; /* this pass is on the source side              */
  87 static int walk_errs;   /* errors found in tree walk                    */
  88 static struct file *cur_dir;    /* base directory for this pass         */
  89 static struct base *cur_base;   /* base pointer for this pass           */
  90 
  91 /*
  92  * routine:
  93  *      evaluate
  94  *
  95  * purpose:
  96  *      to build up a baseline description for all of the files
  97  *      under one side of one base pair (as specified by the rules
  98  *      for that base pair).
  99  *
 100  * parameters:
 101  *      pointer to the base to be evaluated
 102  *      source/destination indication
 103  *      are we restricted to new rules
 104  *
 105  * returns:
 106  *      error mask
 107  *
 108  * notes:
 109  *      we evaluate source and destination separately, and
 110  *      reinterpret the include rules on each side (since there
 111  *      may be wild cards and programs that must be evaluated
 112  *      in a specific directory context).  Similarly the ignore
 113  *      rules must be interpreted anew for each base.
 114  */
 115 errmask_t
 116 evaluate(struct base *bp, side_t srcdst, bool_t newrules)
 117 {       errmask_t errs = 0;
 118         char *dir;
 119         struct rule *rp;
 120         struct file *fp;
 121 
 122         /* see if this base is still relevant           */
 123         if ((bp->b_flags & F_LISTED) == 0)
 124                 return (0);
 125 
 126         /* figure out what this pass is all about       */
 127         usingsrc = (srcdst == OPT_SRC);
 128 
 129         /*
 130          * the ignore engine maintains considerable per-base-directory
 131          * state, and so must be reset at the start of a new tree.
 132          */
 133         ignore_reset();
 134 
 135         /* all evaluation must happen from the appropriate directory */
 136         dir = usingsrc ? bp->b_src_name : bp->b_dst_name;
 137         if (chdir(dir) < 0) {
 138                 fprintf(stderr, gettext(ERR_chdir), dir);
 139 
 140                 /*
 141                  * if we have -n -o we are actually willing to
 142                  * pretend that nothing has changed on the missing
 143                  * side.  This is actually useful on a disconnected
 144                  * notebook to ask what has been changed so far.
 145                  */
 146                 if (opt_onesided == (usingsrc ? OPT_DST : OPT_SRC)) {
 147                         for (fp = bp->b_files; fp; fp = fp->f_next)
 148                                 fakedata(fp, srcdst);
 149 
 150                         if (opt_debug & DBG_EVAL)
 151                                 fprintf(stderr, "EVAL: FAKE DATA %s dir=%s\n",
 152                                         usingsrc ? "SRC" : "DST", dir);
 153                         return (0);
 154                 } else
 155                         return (ERR_NOBASE);
 156         }
 157 
 158         if (opt_debug & DBG_EVAL)
 159                 fprintf(stderr, "EVAL: base=%d, %s dir=%s\n",
 160                         bp->b_ident, usingsrc ? "SRC" : "DST", dir);
 161 
 162         /* assemble the include list                    */
 163         for (rp = bp->b_includes; rp; rp = rp->r_next) {
 164 
 165                 /* see if we are skipping old rules     */
 166                 if (newrules && ((rp->r_flags & R_NEW) == 0))
 167                         continue;
 168 
 169                 if (rp->r_flags & R_PROGRAM)
 170                         errs |= add_run(bp, rp->r_file);
 171                 else if (rp->r_flags & R_WILD)
 172                         errs |= add_glob(bp, rp->r_file);
 173                 else
 174                         errs |= add_file_arg(bp, rp->r_file);
 175         }
 176 
 177         /* assemble the base-specific exclude list              */
 178         for (rp = bp->b_excludes; rp; rp = rp->r_next)
 179                 if (rp->r_flags & R_PROGRAM)
 180                         ignore_pgm(rp->r_file);
 181                 else if (rp->r_flags & R_WILD)
 182                         ignore_expr(rp->r_file);
 183                 else
 184                         ignore_file(rp->r_file);
 185 
 186         /* add in the global excludes                           */
 187         for (rp = omnibase.b_excludes; rp; rp = rp->r_next)
 188                 if (rp->r_flags & R_WILD)
 189                         ignore_expr(rp->r_file);
 190                 else
 191                         ignore_file(rp->r_file);
 192 
 193         /*
 194          * because of restriction lists and new-rules, the baseline
 195          * may contain many more files than we are actually supposed
 196          * to look at during the impending evaluation/analysis phases
 197          *
 198          * when LIST arguments are encountered within a rule, we turn
 199          * on the LISTED flag for the associated files.  We only evaluate
 200          * files that have the LISTED flag.  We turn the LISTED flag off
 201          * after evaluating them because just because a file was enumerated
 202          * in the source doesn't mean that will necessarily be enumerated
 203          * in the destination.
 204          */
 205         for (fp = bp->b_files; fp; fp = fp->f_next)
 206                 if (fp->f_flags & F_LISTED) {
 207                         errs |= eval_file(bp, fp);
 208                         fp->f_flags &= ~F_LISTED;
 209                 }
 210 
 211         /* note that this base has been evaluated       */
 212         bp->b_flags |= F_EVALUATE;
 213 
 214         return (errs);
 215 }
 216 
 217 /*
 218  * routine:
 219  *      add_file_arg
 220  *
 221  * purpose:
 222  *      to create file node(s) under a specified base for an explictly
 223  *      included file.
 224  *
 225  * parameters:
 226  *      pointer to associated base
 227  *      name of the file
 228  *
 229  * returns:
 230  *      error mask
 231  *
 232  * notes:
 233  *      the trick is that an include LIST argument need not be a file
 234  *      in the base directory, but may be a path passing through
 235  *      several intermediate directories.  If this is the case we
 236  *      need to ensure that all of those directories are added to
 237  *      the tree SPARSELY since it is not intended that they be
 238  *      expanded during the course of evaluation.
 239  *
 240  *      we ignore arguments that end in .. because they have the
 241  *      potential to walk out of the base tree, because it can
 242  *      result in different names for a single file, and because
 243  *      should never be necessary to specify files that way.
 244  */
 245 static errmask_t
 246 add_file_arg(struct base *bp, char *path)
 247 {       int i;
 248         errmask_t errs = 0;
 249         struct file *dp = 0;
 250         struct file *fp;
 251         char *s, *p;
 252         char name[ MAX_NAME ];
 253 
 254         /*
 255          * see if someone is trying to feed us a ..
 256          */
 257         if (strcmp(path, "..") == 0 || prefix(path, "../") ||
 258             suffix(path, "/..") || contains(path, "/../")) {
 259                 fprintf(stderr, gettext(WARN_ignore), path);
 260                 return (ERR_MISSING);
 261         }
 262 
 263         /*
 264          * strip off any trailing "/." or "/"
 265          *      since noone will miss these, it is safe to actually
 266          *      take them off the name.  When we fall out of this
 267          *      loop, s will point where the null belongs.  We don't
 268          *      actually null the end of string yet because we want
 269          *      to leave it pristine for error messages.
 270          */
 271         for (s = path; *s; s++);
 272         while (s > path) {
 273                 if (s[-1] == '/') {
 274                         s--;
 275                         continue;
 276                 }
 277                 if (s[-1] == '.' && s > &path[1] && s[-2] == '/') {
 278                         s -= 2;
 279                         continue;
 280                 }
 281                 break;
 282         }
 283 
 284         /*
 285          * skip over leading "/" and "./" (but not over a lone ".")
 286          */
 287         for (p = path; p < s; ) {
 288                 if (*p == '/') {
 289                         p++;
 290                         continue;
 291                 }
 292                 if (*p == '.' && s > &p[1] && p[1] == '/') {
 293                         p += 2;
 294                         continue;
 295                 }
 296                 break;
 297         }
 298 
 299         /*
 300          * if there is nothing left, we're miffed, but done
 301          */
 302         if (p >= s) {
 303                 fprintf(stderr, gettext(WARN_ignore), path);
 304                 return (ERR_MISSING);
 305         } else {
 306                 /*
 307                  * this is actually storing a null into the argument,
 308                  * but it is OK to do this because the stuff we are
 309                  * truncating really is garbage that noone will ever
 310                  * want to see.
 311                  */
 312                 *s = 0;
 313                 path = p;
 314         }
 315 
 316         /*
 317          * see if there are any restrictions that would force
 318          * us to ignore this argument
 319          */
 320         if (check_restr(bp, path) == 0)
 321                 return (0);
 322 
 323         while (*path) {
 324                 /* lex off the next name component      */
 325                 for (i = 0; path[i] && path[i] != '/'; i++)
 326                         name[i] = path[i];
 327                 name[i] = 0;
 328 
 329                 /* add it into the database             */
 330                 fp = (dp == 0)  ? add_file_to_base(bp, name)
 331                                 : add_file_to_dir(dp, name);
 332 
 333                 /* see if this was an intermediate directory    */
 334                 if (path[i] == '/') {
 335                         fp->f_flags |= F_LISTED | F_SPARSE;
 336                         path += i+1;
 337                 } else {
 338                         fp->f_flags |= F_LISTED;
 339                         path += i;
 340                 }
 341 
 342                 dp = fp;
 343         }
 344 
 345         return (errs);
 346 }
 347 
 348 /*
 349  * routine:
 350  *      eval_file
 351  *
 352  * purpose:
 353  *      to evaluate one named file under a particular directory
 354  *
 355  * parameters:
 356  *      pointer to base structure
 357  *      pointer to file structure
 358  *
 359  * returns:
 360  *      error mask
 361  *      filled in evaluations in the baseline
 362  *
 363  * note:
 364  *      due to new rules and other restrictions we may not be expected
 365  *      to evaluate the entire tree.  We should only be called on files
 366  *      that are LISTed, and we should only invoke ourselves recursively
 367  *      on such files.
 368  */
 369 static errmask_t
 370 eval_file(struct base *bp, struct file *fp)
 371 {       errmask_t errs = 0;
 372         int rc;
 373         char *name;
 374         struct file *cp;
 375         struct stat statb;
 376 
 377         if (opt_debug & DBG_EVAL)
 378                 fprintf(stderr, "EVAL: FILE, flags=%s, name=%s\n",
 379                         showflags(fileflags, fp->f_flags), fp->f_name);
 380 
 381         /* stat the file and fill in the file structure information     */
 382         name = get_name(fp);
 383 
 384 #ifdef  DBG_ERRORS
 385         /* see if we should simulated a stat error on this file */
 386         if (opt_errors && (errno = dbg_chk_error(name, usingsrc ? 's' : 'S')))
 387                 rc = -1;
 388         else
 389 #endif
 390         rc = lstat(name, &statb);
 391 
 392         if (rc < 0) {
 393                 if (opt_debug & DBG_EVAL)
 394                         fprintf(stderr, "EVAL: FAIL lstat, errno=%d\n", errno);
 395                 switch (errno) {
 396                     case EACCES:
 397                         fp->f_flags |= F_STAT_ERROR;
 398                         return (ERR_PERM);
 399                     case EOVERFLOW:
 400                         fp->f_flags |= F_STAT_ERROR;
 401                         return (ERR_UNRESOLVED);
 402                     default:
 403                         return (ERR_MISSING);
 404                 }
 405         }
 406 
 407         /* record the information we've just gained                     */
 408         note_info(fp, &statb, usingsrc ? OPT_SRC : OPT_DST);
 409 
 410         /*
 411          * checking for ACLs is expensive, so we only do it if we
 412          * have been asked to, or if we have reason to believe that
 413          * the file has an ACL
 414          */
 415         if (opt_acls || fp->f_info[OPT_BASE].f_numacls)
 416                 (void) get_acls(name,
 417                                 &fp->f_info[usingsrc ? OPT_SRC : OPT_DST]);
 418 
 419 
 420         /* note that this file has been evaluated                       */
 421         fp->f_flags |= F_EVALUATE;
 422 
 423         /* if it is not a directory, a simple stat will suffice */
 424         if ((statb.st_mode & S_IFMT) != S_IFDIR)
 425                 return (0);
 426 
 427         /*
 428          * as a sanity check, we look for changes in the I-node
 429          * numbers associated with LISTed directories ... on the
 430          * assumption that these are high-enough up on the tree
 431          * that they aren't likely to change, and so a change
 432          * might indicate trouble.
 433          */
 434         if (fp->f_flags & F_LISTED)
 435                 check_inum(fp, usingsrc);
 436 
 437         /*
 438          * sparse directories are on the path between a base and
 439          * a listed directory.  As such, we don't walk these
 440          * directories.  Rather, we just enumerate the LISTed
 441          * files.
 442          */
 443         if (fp->f_flags & F_SPARSE) {
 444                 push_name(fp->f_name);
 445 
 446                 /* this directory isn't supposed to be fully walked     */
 447                 for (cp = fp->f_files; cp; cp = cp->f_next)
 448                         if (cp->f_flags & F_LISTED) {
 449                                 errs |= eval_file(bp, cp);
 450                                 cp->f_flags &= ~F_LISTED;
 451                         }
 452                 pop_name();
 453         } else {
 454                 /* fully walk the tree beneath this directory           */
 455                 walk_errs = 0;
 456                 cur_base = bp;
 457                 cur_dir = fp;
 458                 nftw(get_name(fp), &walker, MAX_DEPTH, FTW_PHYS|FTW_MOUNT);
 459                 errs |= walk_errs;
 460         }
 461 
 462         return (errs);
 463 }
 464 
 465 /*
 466  * routine:
 467  *      walker
 468  *
 469  * purpose:
 470  *      node visitor for recursive directory enumeration
 471  *
 472  * parameters:
 473  *      name of file
 474  *      pointer to stat buffer for file
 475  *      file type
 476  *      FTW structure (base name offset, walk-depth)
 477  *
 478  * returns:
 479  *      0       continue
 480  *      -1      stop
 481  *
 482  * notes:
 483  *      Ignoring files is easy, but ignoring directories is harder.
 484  *      Ideally we would just decline to walk the trees beneath
 485  *      ignored directories, but ftw doesn't allow the walker to
 486  *      tell it to "don't enter this directory, but continue".
 487  *
 488  *      Instead, we have to set a global to tell us to ignore
 489  *      everything under that tree.  The variable ignore_level
 490  *      is set to a level, below which, everything should be
 491  *      ignored.  Once the enumeration rises above that level
 492  *      again, we clear it.
 493  */
 494 static int
 495 walker(const char *name, const struct stat *sp, int type,
 496                 struct FTW *ftwx)
 497 {       const char *path;
 498         struct file *fp;
 499         int level;
 500         int which;
 501         bool_t restr;
 502         static struct file *dirstack[ MAX_DEPTH + 1 ];
 503         static int ignore_level = 0;
 504 
 505         path = &name[ftwx->base];
 506         level = ftwx->level;
 507         which = usingsrc ? OPT_SRC : OPT_DST;
 508 
 509         /*
 510          * see if we are ignoring all files in this sub-tree
 511          */
 512         if (ignore_level > 0 && level >= ignore_level) {
 513                 if (opt_debug & DBG_EVAL)
 514                         fprintf(stderr, "EVAL: SKIP file=%s\n", name);
 515                 return (0);
 516         } else
 517                 ignore_level = 0;       /* we're through ignoring       */
 518 
 519 #ifdef  DBG_ERRORS
 520         /* see if we should simulated a stat error on this file */
 521         if (opt_errors && dbg_chk_error(name, usingsrc ? 'n' : 'N'))
 522                 type = FTW_NS;
 523 #endif
 524 
 525         switch (type) {
 526         case FTW_F:     /* file                 */
 527         case FTW_SL:    /* symbolic link        */
 528                 /*
 529                  * filter out files of inappropriate types
 530                  */
 531                 switch (sp->st_mode & S_IFMT) {
 532                         default:        /* anything else we ignore      */
 533                                 return (0);
 534 
 535                         case S_IFCHR:
 536                         case S_IFBLK:
 537                         case S_IFREG:
 538                         case S_IFLNK:
 539                                 if (opt_debug & DBG_EVAL)
 540                                         fprintf(stderr,
 541                                                 "EVAL: WALK lvl=%d, file=%s\n",
 542                                                 level, path);
 543 
 544                                 /* see if we were told to ignore this one */
 545                                 if (ignore_check(path))
 546                                         return (0);
 547 
 548                                 fp = add_file_to_dir(dirstack[level-1], path);
 549                                 note_info(fp, sp, which);
 550 
 551                                 /* note that this file has been evaluated */
 552                                 fp->f_flags |= F_EVALUATE;
 553 
 554                                 /* see if we should check ACLs          */
 555                                 if ((sp->st_mode & S_IFMT) == S_IFLNK)
 556                                         return (0);
 557 
 558                                 if (fp->f_info[OPT_BASE].f_numacls || opt_acls)
 559                                         (void) get_acls(name,
 560                                                         &fp->f_info[which]);
 561 
 562                                 return (0);
 563                 }
 564 
 565         case FTW_D:     /* enter directory              */
 566                 if (opt_debug & DBG_EVAL)
 567                         fprintf(stderr, "EVAL: WALK lvl=%d, dir=%s\n",
 568                                 level, name);
 569 
 570                 /*
 571                  * if we have been told to ignore this directory, we should
 572                  * ignore all files under it.  Similarly, if we are outside
 573                  * of our restrictions, we should ignore the entire subtree
 574                  */
 575                 restr = check_restr(cur_base, name);
 576                 if (restr == FALSE || ignore_check(path)) {
 577                         ignore_level = level + 1;
 578                         return (0);
 579                 }
 580 
 581                 fp = (level == 0) ?  cur_dir :
 582                     add_file_to_dir(dirstack[level-1], path);
 583 
 584                 note_info(fp, sp, which);
 585 
 586                 /* see if we should be checking ACLs    */
 587                 if (opt_acls || fp->f_info[OPT_BASE].f_numacls)
 588                         (void) get_acls(name, &fp->f_info[which]);
 589 
 590                 /* note that this file has been evaluated */
 591                 fp->f_flags |= F_EVALUATE;
 592 
 593                 /* note the parent of the children to come      */
 594                 dirstack[ level ] = fp;
 595 
 596                 /*
 597                  * PROBLEM: given the information that nftw provides us with,
 598                  *          how do we know that we have confirmed the fact
 599                  *          that a file no longer exists.  Or to rephrase
 600                  *          this in filesync terms, how do we know when to
 601                  *          set the EVALUATE flag for a file we didn't find.
 602                  *
 603                  * if we are going to fully scan this directory (we
 604                  * are completely within our restrictions) then we
 605                  * will be confirming the non-existance of files that
 606                  * used to be here.  Thus any file that was in the
 607                  * base line under this directory should be considered
 608                  * to have been evaluated (whether we found it or not).
 609                  *
 610                  * if, however, we are only willing to scan selected
 611                  * files (due to restrictions), or the file was not
 612                  * in the baseline, then we should not assume that this
 613                  * pass will evaluate it.
 614                  */
 615                 if (restr == TRUE)
 616                         for (fp = fp->f_files; fp; fp = fp->f_next) {
 617                                 if ((fp->f_flags & F_IN_BASELINE) == 0)
 618                                         continue;
 619                                 fp->f_flags |= F_EVALUATE;
 620                         }
 621 
 622                 return (0);
 623 
 624         case FTW_DP:    /* end of directory     */
 625                 dirstack[ level ] = 0;
 626                 break;
 627 
 628         case FTW_DNR:   /* unreadable directory */
 629                 walk_errs |= ERR_PERM;
 630                 /* FALLTHROUGH  */
 631         case FTW_NS:    /* unstatable file      */
 632                 if (opt_debug & DBG_EVAL)
 633                         fprintf(stderr, "EVAL: walker can't stat/read %s\n",
 634                                 name);
 635                 fp = (level == 0) ?  cur_dir :
 636                         add_file_to_dir(dirstack[level-1], path);
 637                 fp->f_flags |= F_STAT_ERROR;
 638                 walk_errs |= ERR_UNRESOLVED;
 639                 break;
 640         }
 641 
 642         return (0);
 643 }
 644 
 645 /*
 646  * routine:
 647  *      note_info
 648  *
 649  * purpose:
 650  *      to record information about a file in its file node
 651  *
 652  * parameters
 653  *      file node pointer
 654  *      stat buffer
 655  *      which file info structure to fill in (0-2)
 656  *
 657  * returns
 658  *      void
 659  */
 660 void
 661 note_info(struct file *fp, const struct stat *sp, side_t which)
 662 {       struct fileinfo *ip;
 663         static int flags[3] = { F_IN_BASELINE, F_IN_SOURCE, F_IN_DEST };
 664 
 665         ip = &fp->f_info[ which ];
 666 
 667         ip->f_ino    = sp->st_ino;
 668         ip->f_d_maj  = major(sp->st_dev);
 669         ip->f_d_min  = minor(sp->st_dev);
 670         ip->f_type   = sp->st_mode & S_IFMT;
 671         ip->f_size   = sp->st_size;
 672         ip->f_mode   = sp->st_mode & S_IAMB;
 673         ip->f_uid    = sp->st_uid;
 674         ip->f_gid    = sp->st_gid;
 675         ip->f_modtime        = sp->st_mtim.tv_sec;
 676         ip->f_modns  = sp->st_mtim.tv_nsec;
 677         ip->f_nlink  = sp->st_nlink;
 678         ip->f_rd_maj = major(sp->st_rdev);
 679         ip->f_rd_min = minor(sp->st_rdev);
 680 
 681         /* indicate where this file has been found      */
 682         fp->f_flags |= flags[which];
 683 
 684         if (opt_debug & DBG_STAT)
 685                 fprintf(stderr,
 686                         "STAT: list=%d, file=%s, mod=%08lx.%08lx, nacl=%d\n",
 687                         which, fp->f_name, ip->f_modtime, ip->f_modns,
 688                         ip->f_numacls);
 689 }
 690 
 691 /*
 692  * routine:
 693  *      do_update
 694  *
 695  * purpose:
 696  *      to copy information from one side into the baseline in order
 697  *      to reflect the effects of recent reconciliation actions
 698  *
 699  * parameters
 700  *      fileinfo structure to be updated
 701  *      fileinfo structure to be updated from
 702  *
 703  * returns
 704  *      void
 705  *
 706  * note:
 707  *      we play fast and loose with the copying of acl chains
 708  *      here, but noone is going to free or reuse any of this
 709  *      memory anyway.  None the less, I do feel embarassed.
 710  */
 711 static void
 712 do_update(struct fileinfo *np, struct fileinfo *ip)
 713 {
 714         /* get most of the fields from the designated "right" copy */
 715         np->f_type   = ip->f_type;
 716         np->f_size   = ip->f_size;
 717         np->f_mode   = ip->f_mode;
 718         np->f_uid    = ip->f_uid;
 719         np->f_gid    = ip->f_gid;
 720         np->f_rd_maj = ip->f_rd_maj;
 721         np->f_rd_min = ip->f_rd_min;
 722 
 723         /* see if facls have to be propagated   */
 724         np->f_numacls = ip->f_numacls;
 725         np->f_acls = ip->f_acls;
 726 }
 727 
 728 /*
 729  * routine:
 730  *      update_info
 731  *
 732  * purpose:
 733  *      to update the baseline to reflect recent reconcliations
 734  *
 735  * parameters
 736  *      file node pointer
 737  *      which file info structure to trust (1/2)
 738  *
 739  * returns
 740  *      void
 741  *
 742  * note:
 743  *      after we update this I-node we run down the entire
 744  *      change list looking for links and update them too.
 745  *      This is to ensure that when subsequent links get
 746  *      reconciled, they are already found to be up-to-date.
 747  */
 748 void
 749 update_info(struct file *fp, side_t which)
 750 {
 751         /* first update the specified fileinfo structure        */
 752         do_update(&fp->f_info[ OPT_BASE ], &fp->f_info[ which ]);
 753 
 754         if (opt_debug & DBG_STAT)
 755                 fprintf(stderr,
 756                         "STAT: UPDATE from=%d, file=%s, mod=%08lx.%08lx\n",
 757                         which, fp->f_name, fp->f_info[ which ].f_modtime,
 758                         fp->f_info[ which ].f_modns);
 759 }
 760 
 761 /*
 762  * routine:
 763  *      fakedata
 764  *
 765  * purpose:
 766  *      to populate a tree we cannot analyze with information from the baseline
 767  *
 768  * parameters:
 769  *      file to be faked
 770  *      which side to fake
 771  *
 772  * notes:
 773  *      We would never use this for real reconciliation, but it is useful
 774  *      if a disconnected notebook user wants to find out what has been
 775  *      changed so far.  We only do this if we are notouch and oneway.
 776  */
 777 static void
 778 fakedata(struct file *fp, int which)
 779 {       struct file *lp;
 780 
 781         /* pretend we actually found the file                   */
 782         fp->f_flags |= (which == OPT_SRC) ? F_IN_SOURCE : F_IN_DEST;
 783 
 784         /* update the specified side from the baseline          */
 785         do_update(&fp->f_info[ which ], &fp->f_info[ OPT_BASE ]);
 786         fp->f_info[which].f_nlink = (which == OPT_SRC) ? fp->f_s_nlink :
 787                                                         fp->f_d_nlink;
 788         fp->f_info[which].f_modtime = (which == OPT_SRC) ? fp->f_s_modtime :
 789                                                         fp->f_d_modtime;
 790 
 791         for (lp = fp->f_files; lp; lp = lp->f_next)
 792                 fakedata(lp, which);
 793 }
 794 
 795 /*
 796  * routine:
 797  *      check_inum
 798  *
 799  * purpose:
 800  *      sanity check inode #s on directories that are unlikely to change
 801  *
 802  * parameters:
 803  *      pointer to file node
 804  *      are we using the source
 805  *
 806  * note:
 807  *      the purpose of this sanity check is to catch a case where we
 808  *      have somehow been pointed at a directory that is not the one
 809  *      we expected to be reconciling against.  It could happen if a
 810  *      variable wasn't properly set, or if we were in a new domain
 811  *      where an old path no longer worked.  This could result in
 812  *      bazillions of inappropriate propagations and deletions.
 813  */
 814 void
 815 check_inum(struct file *fp, int src)
 816 {       struct fileinfo *ip;
 817 
 818         /*
 819          * we validate the inode number and the major device numbers ... minor
 820          * device numbers for NFS devices are arbitrary
 821          */
 822         if (src) {
 823                 ip = &fp->f_info[ OPT_SRC ];
 824                 if (ip->f_ino == fp->f_s_inum && ip->f_d_maj == fp->f_s_maj)
 825                         return;
 826 
 827                 /* if file was newly created/deleted, this isn't warnable */
 828                 if (fp->f_s_inum == 0 || ip->f_ino == 0)
 829                         return;
 830 
 831                 if (opt_verbose)
 832                         fprintf(stdout, V_change, fp->f_name, TXT_src,
 833                                 fp->f_s_maj, fp->f_s_min, fp->f_s_inum,
 834                                 ip->f_d_maj, ip->f_d_min, ip->f_ino);
 835         } else {
 836                 ip = &fp->f_info[ OPT_DST ];
 837                 if (ip->f_ino == fp->f_d_inum && ip->f_d_maj == fp->f_d_maj)
 838                         return;
 839 
 840                 /* if file was newly created/deleted, this isn't warnable */
 841                 if (fp->f_d_inum == 0 || ip->f_ino == 0)
 842                         return;
 843 
 844                 if (opt_verbose)
 845                         fprintf(stdout, V_change, fp->f_name, TXT_dst,
 846                                 fp->f_d_maj, fp->f_d_min, fp->f_d_inum,
 847                                 ip->f_d_maj, ip->f_d_min, ip->f_ino);
 848         }
 849 
 850         /* note that something has changed      */
 851         inum_changes++;
 852 }
 853 
 854 /*
 855  * routine:
 856  *      add_glob
 857  *
 858  * purpose:
 859  *      to evaluate a wild-carded expression into names, and add them
 860  *      to the evaluation list.
 861  *
 862  * parameters:
 863  *      base
 864  *      expression
 865  *
 866  * returns:
 867  *      error mask
 868  *
 869  * notes:
 870  *      we don't want to allow any patterns to expand to a . because
 871  *      that could result in re-evaluation of a tree under a different
 872  *      name.  The real thing we are worried about here is ".*" which
 873  *      is meant to pick up . files, but shouldn't pick up . and ..
 874  */
 875 static errmask_t
 876 add_glob(struct base *bp, char *expr)
 877 {       int i;
 878         errmask_t errs = 0;
 879 #ifndef BROKEN_GLOB
 880         glob_t gt;
 881         char *s;
 882 
 883         /* expand the regular expression        */
 884         i = glob(expr, GLOB_NOSORT, 0, &gt);
 885         if (i == GLOB_NOMATCH)
 886                 return (ERR_MISSING);
 887         if (i) {
 888                 /* this shouldn't happen, so it's cryptic message time  */
 889                 fprintf(stderr, "EVAL: add_glob globfail expr=%s, ret=%d\n",
 890                                 expr, i);
 891                 return (ERR_OTHER);
 892         }
 893 
 894         for (i = 0; i < gt.gl_pathc; i++) {
 895                 /* make sure we don't let anything expand to a . */
 896                 s = basename(gt.gl_pathv[i]);
 897                 if (strcmp(s, ".") == 0) {
 898                         fprintf(stderr, gettext(WARN_ignore), gt.gl_pathv[i]);
 899                         errs |= ERR_MISSING;
 900                         continue;
 901                 }
 902 
 903                 errs |= add_file_arg(bp, gt.gl_pathv[i]);
 904         }
 905 
 906         globfree(&gt);
 907 #else
 908         /*
 909          * in 2.4 the glob function was completely broken.  The
 910          * easiest way to get around this problem is to just ask
 911          * the shell to do the work for us.  This is much slower
 912          * but produces virtually identical results.  Given that
 913          * the 2.4 version is internal use only, I probably won't
 914          * worry about the performance difference (less than 2
 915          * seconds for a typical filesync command, and no hit
 916          * at all if they don't use regular expressions in
 917          * their LIST rules).
 918          */
 919         char cmdbuf[MAX_LINE];
 920 
 921         sprintf(cmdbuf, "ls -d %s 2> /dev/null", expr);
 922         errs |= add_run(bp, cmdbuf);
 923 #endif
 924 
 925         return (errs);
 926 }
 927 
 928 
 929 /*
 930  * routine:
 931  *      add_run
 932  *
 933  * purpose:
 934  *      to run a command and capture the names it outputs in the
 935  *      evaluation list.
 936  *
 937  * parameters
 938  *      base
 939  *      command
 940  *
 941  * returns:
 942  *      error mask
 943  */
 944 static errmask_t
 945 add_run(struct base *bp, char *cmd)
 946 {       char *s, *p;
 947         FILE *fp;
 948         char inbuf[ MAX_LINE ];
 949         errmask_t errs = 0;
 950         int added = 0;
 951 
 952         if (opt_debug & DBG_EVAL)
 953                 fprintf(stderr, "EVAL: RUN %s\n", cmd);
 954 
 955         /* run the command and collect its ouput        */
 956         fp = popen(cmd, "r");
 957         if (fp == NULL) {
 958                 fprintf(stderr, gettext(ERR_badrun), cmd);
 959                 return (ERR_OTHER);
 960         }
 961 
 962         while (fgets(inbuf, sizeof (inbuf), fp) != 0) {
 963                 /* strip off any trailing newline       */
 964                 for (s = inbuf; *s && *s != '\n'; s++);
 965                 *s = 0;
 966 
 967                 /* skip any leading white space         */
 968                 for (s = inbuf; *s == ' ' || *s == '\t'; s++);
 969 
 970                 /* make sure we don't let anything expand to a . */
 971                 p = basename(s);
 972                 if (strcmp(p, ".") == 0) {
 973                         fprintf(stderr, gettext(WARN_ignore), s);
 974                         errs |= ERR_MISSING;
 975                         continue;
 976                 }
 977 
 978                 /* add this file to the list            */
 979                 if (*s) {
 980                         errs |= add_file_arg(bp, s);
 981                         added++;
 982                 }
 983         }
 984 
 985         pclose(fp);
 986 
 987 #ifdef  BROKEN_GLOB
 988         /*
 989          * if we are being used to simulate libc glob, and we didn't
 990          * return anything, we should probably assume that the regex
 991          * was unable to match anything
 992          */
 993         if (added == 0)
 994                 errs |= ERR_MISSING;
 995 #endif
 996         return (errs);
 997 }