xref: /illumos-gate/usr/src/cmd/filesync/eval.c (revision 7c478bd9)
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 }
998