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