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 */
75static errmask_t eval_file(struct base *, struct file *);
76static errmask_t add_file_arg(struct base *, char *);
77static int walker(const char *, const struct stat *, int, struct FTW *);
78static errmask_t add_glob(struct base *, char *);
79static errmask_t add_run(struct base *, char *);
80static void check_inum(struct file *, int);
81static void fakedata(struct file *, int);
82
83/*
84 * globals
85 */
86static bool_t usingsrc;	/* this pass is on the source side		*/
87static int walk_errs;	/* errors found in tree walk			*/
88static struct file *cur_dir;	/* base directory for this pass		*/
89static 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 */
115errmask_t
116evaluate(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 */
245static errmask_t
246add_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 */
369static errmask_t
370eval_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 */
494static int
495walker(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 */
660void
661note_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 */
711static void
712do_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 */
748void
749update_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 */
777static void
778fakedata(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 */
814void
815check_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 */
875static errmask_t
876add_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 */
944static errmask_t
945add_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