xref: /onnv-gate/usr/src/cmd/filesync/anal.c (revision 3517:79d66aa80b8b)
10Sstevel@tonic-gate /*
20Sstevel@tonic-gate  * CDDL HEADER START
30Sstevel@tonic-gate  *
40Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
50Sstevel@tonic-gate  * Common Development and Distribution License, Version 1.0 only
60Sstevel@tonic-gate  * (the "License").  You may not use this file except in compliance
70Sstevel@tonic-gate  * with the License.
80Sstevel@tonic-gate  *
90Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
100Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
110Sstevel@tonic-gate  * See the License for the specific language governing permissions
120Sstevel@tonic-gate  * and limitations under the License.
130Sstevel@tonic-gate  *
140Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
150Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
160Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
170Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
180Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
190Sstevel@tonic-gate  *
200Sstevel@tonic-gate  * CDDL HEADER END
210Sstevel@tonic-gate  */
220Sstevel@tonic-gate /*
230Sstevel@tonic-gate  * Copyright (c) 1995 Sun Microsystems, Inc.  All Rights Reserved
240Sstevel@tonic-gate  *
250Sstevel@tonic-gate  * module:
260Sstevel@tonic-gate  *	anal.c
270Sstevel@tonic-gate  *
280Sstevel@tonic-gate  * purpose:
290Sstevel@tonic-gate  *	routines to analyze the file trees and figure out what has changed
300Sstevel@tonic-gate  *	and queue files for reconciliation.  It also contains tree enumeration
310Sstevel@tonic-gate  *	routines to for other purposes (pruning and link location).
320Sstevel@tonic-gate  *
330Sstevel@tonic-gate  * contents:
340Sstevel@tonic-gate  *
350Sstevel@tonic-gate  *  change analysis:
360Sstevel@tonic-gate  *	analyze .... (top level) analyze all files in the tree for changes
370Sstevel@tonic-gate  *	summary .... print out change/reconciliation statistics for each base
380Sstevel@tonic-gate  *	check_file . (static) look for changes and queue file for reconciliation
390Sstevel@tonic-gate  *	check_changes (static) figure out if a particular file has changed
400Sstevel@tonic-gate  *	queue_file . (static) add a file to the reconciliation list
410Sstevel@tonic-gate  *
420Sstevel@tonic-gate  *  other tree enumeration functions:
430Sstevel@tonic-gate  *	prune_file . (static) recursive descent and actual pruning
440Sstevel@tonic-gate  *	prune ...... (top level) initiate pruning analysis for nonexistant files
450Sstevel@tonic-gate  *	find_link .. look for other files to which a file may be a link
460Sstevel@tonic-gate  *	link_update. propagate changed stat info to all other links
470Sstevel@tonic-gate  *	same_name .. (static) figure out if two nodes describe same file
480Sstevel@tonic-gate  *
490Sstevel@tonic-gate  *  misc:
500Sstevel@tonic-gate  *	push_name .. maintain a running full pathname as we descend
510Sstevel@tonic-gate  *	pop_name ... maintain a running full pathname as we pop back
520Sstevel@tonic-gate  *	get_name ... return full pathname for the current file
530Sstevel@tonic-gate  *
540Sstevel@tonic-gate  * notes:
550Sstevel@tonic-gate  *	analysis is limited to files that were evaluated in the previous
560Sstevel@tonic-gate  *	pass ... since we don't have complete information about files that
570Sstevel@tonic-gate  *	were not evaluated in the previous pass.
580Sstevel@tonic-gate  */
59*3517Smp204432 #pragma ident	"%Z%%M%	%I%	%E% SMI"
600Sstevel@tonic-gate 
610Sstevel@tonic-gate #include <stdio.h>
620Sstevel@tonic-gate #include <stdlib.h>
630Sstevel@tonic-gate #include <strings.h>
640Sstevel@tonic-gate 
650Sstevel@tonic-gate #include "messages.h"
660Sstevel@tonic-gate #include "filesync.h"
670Sstevel@tonic-gate #include "database.h"
680Sstevel@tonic-gate #include "debug.h"
690Sstevel@tonic-gate 
700Sstevel@tonic-gate /*
710Sstevel@tonic-gate  * routines:
720Sstevel@tonic-gate  */
730Sstevel@tonic-gate void push_name(const char *);
740Sstevel@tonic-gate void pop_name();
750Sstevel@tonic-gate char *get_name(struct file *);
760Sstevel@tonic-gate static errmask_t check_file(struct file *fp);
770Sstevel@tonic-gate static diffmask_t check_changes(struct file *fp, int first, int second);
780Sstevel@tonic-gate static int prune_file(struct file *fp);
790Sstevel@tonic-gate static void queue_file(struct file *fp);
800Sstevel@tonic-gate 
810Sstevel@tonic-gate /*
820Sstevel@tonic-gate  * globals
830Sstevel@tonic-gate  */
840Sstevel@tonic-gate static struct file *changes;	/* list of files to be reconciled	*/
850Sstevel@tonic-gate 
860Sstevel@tonic-gate static long total_files;	/* total number of files being considered  */
870Sstevel@tonic-gate static long est_deletes;	/* estimated number of files to be deleted */
880Sstevel@tonic-gate static long est_rmdirs;		/* est rmdirs of non-empty directories	   */
890Sstevel@tonic-gate 
900Sstevel@tonic-gate int inum_changes;		/* LISTed directories whose I#s changed	   */
910Sstevel@tonic-gate 
920Sstevel@tonic-gate /*
930Sstevel@tonic-gate  * routine:
940Sstevel@tonic-gate  *	analyze
950Sstevel@tonic-gate  *
960Sstevel@tonic-gate  * purpose:
970Sstevel@tonic-gate  *	top level routine for the analysis/reconciliation process
980Sstevel@tonic-gate  *
990Sstevel@tonic-gate  * parameters:
1000Sstevel@tonic-gate  *	none
1010Sstevel@tonic-gate  *
1020Sstevel@tonic-gate  * returns:
1030Sstevel@tonic-gate  *	error mask
1040Sstevel@tonic-gate  *
1050Sstevel@tonic-gate  * notes:
1060Sstevel@tonic-gate  *	a critical side effect of this routine is the creation of
1070Sstevel@tonic-gate  *	the reconciliation list, an ordered list of files that
1080Sstevel@tonic-gate  *	needed to be processed in the subsequent reconciliation pass
1090Sstevel@tonic-gate  */
1100Sstevel@tonic-gate errmask_t
analyze()1110Sstevel@tonic-gate analyze()
1120Sstevel@tonic-gate {	struct base *bp;
1130Sstevel@tonic-gate 	struct file *fp;
1140Sstevel@tonic-gate 	int errs = 0;
1150Sstevel@tonic-gate 	int err;
1160Sstevel@tonic-gate 	int percentage;
1170Sstevel@tonic-gate 	bool_t aborted = FALSE;
1180Sstevel@tonic-gate 	char msgbuf[MAX_LINE];
1190Sstevel@tonic-gate 
1200Sstevel@tonic-gate 	/*
1210Sstevel@tonic-gate 	 * run through all bases and directories looking for files
1220Sstevel@tonic-gate 	 * that have been renamed.  This must be done before the
1230Sstevel@tonic-gate 	 * difference analysis because a directory rename can introduce
1240Sstevel@tonic-gate 	 * radical restructuring into a name-based tree.
1250Sstevel@tonic-gate 	 */
1260Sstevel@tonic-gate 	for (bp = bases; bp; bp = bp->b_next) {
1270Sstevel@tonic-gate 		for (fp = bp->b_files; fp; fp = fp->f_next)
1280Sstevel@tonic-gate 			if (fp->f_flags & F_EVALUATE)
1290Sstevel@tonic-gate 				errs |= find_renames(fp);
1300Sstevel@tonic-gate 	}
1310Sstevel@tonic-gate 
1320Sstevel@tonic-gate 	/*
1330Sstevel@tonic-gate 	 * run through all bases and files looking for candidates
1340Sstevel@tonic-gate 	 * note, however that we only descend into trees that have
1350Sstevel@tonic-gate 	 * the evaluate flag turned on.  As a result of new rules or
1360Sstevel@tonic-gate 	 * restriction arguments, we may be deliberatly ignoring
1370Sstevel@tonic-gate 	 * large amounts of the baseline.   This means we won't do
1380Sstevel@tonic-gate 	 * any stats to update the information in those nodes, and
1390Sstevel@tonic-gate 	 * they will be written back just as they were.
1400Sstevel@tonic-gate 	 *
1410Sstevel@tonic-gate 	 * note that there is code to prune out baseline nodes for
1420Sstevel@tonic-gate 	 * files that no longer exist, but that code is in reconcile
1430Sstevel@tonic-gate 	 * and will never get a chance to run on nodes that aren't
1440Sstevel@tonic-gate 	 * analyzed.
1450Sstevel@tonic-gate 	 *
1460Sstevel@tonic-gate 	 * we also want to run though all nodes with STAT errors
1470Sstevel@tonic-gate 	 * so that we can put them on the reconciliation list.
1480Sstevel@tonic-gate 	 */
1490Sstevel@tonic-gate 	for (bp = bases; bp; bp = bp->b_next) {
1500Sstevel@tonic-gate 		for (fp = bp->b_files; fp; fp = fp->f_next)
1510Sstevel@tonic-gate 			if (fp->f_flags & (F_EVALUATE|F_STAT_ERROR))
1520Sstevel@tonic-gate 				errs |= check_file(fp);
1530Sstevel@tonic-gate 	}
1540Sstevel@tonic-gate 
1550Sstevel@tonic-gate 	/*
1560Sstevel@tonic-gate 	 * my greatest fear is that someday, somehow, by messing with
1570Sstevel@tonic-gate 	 * variables or baselines or who-knows-what, that someone will
1580Sstevel@tonic-gate 	 * run a reconciliation against a large tree that doesn't correspond
1590Sstevel@tonic-gate 	 * to the baseline, and I will infer that a bazillion files have
1600Sstevel@tonic-gate 	 * been deleted and will propagate the slaughter before anyone
1610Sstevel@tonic-gate 	 * can say somebody stop that maniac.
1620Sstevel@tonic-gate 	 *
1630Sstevel@tonic-gate 	 * in order to prevent such a possibility, we have a few different
1640Sstevel@tonic-gate 	 * sanity checks.  There is, of course, a tradeoff here between
1650Sstevel@tonic-gate 	 * danger and irritation.  The current set of heuristics for whether
1660Sstevel@tonic-gate 	 * or not to generate a warning are (any of)
1670Sstevel@tonic-gate 	 *
1680Sstevel@tonic-gate 	 *	at least CONFIRM_MIN files have been deleted AND
1690Sstevel@tonic-gate 	 *	CONFIRM_PCT of all files have been deleted
1700Sstevel@tonic-gate 	 *
1710Sstevel@tonic-gate 	 *	the inode number on a LISTed directory has changed
1720Sstevel@tonic-gate 	 *
1730Sstevel@tonic-gate 	 *	a non-empty directory has been deleted.
1740Sstevel@tonic-gate 	 */
1750Sstevel@tonic-gate 	msgbuf[0] = 0;
1760Sstevel@tonic-gate 
1770Sstevel@tonic-gate 	percentage = (est_deletes * 100) / (total_files ? total_files : 1);
1780Sstevel@tonic-gate 	if (est_deletes >= CONFIRM_MIN && percentage >= CONFIRM_PCT)
1790Sstevel@tonic-gate 		sprintf(msgbuf, gettext(WARN_deletes), est_deletes);
1800Sstevel@tonic-gate 	else if (inum_changes > 0)
1810Sstevel@tonic-gate 		sprintf(msgbuf, gettext(WARN_ichange), inum_changes);
1820Sstevel@tonic-gate 	else if (est_rmdirs)
1830Sstevel@tonic-gate 		sprintf(msgbuf, gettext(WARN_rmdirs), est_rmdirs);
1840Sstevel@tonic-gate 
1850Sstevel@tonic-gate 	if (msgbuf[0])
1860Sstevel@tonic-gate 		confirm(msgbuf);
1870Sstevel@tonic-gate 
1880Sstevel@tonic-gate 	/*
1890Sstevel@tonic-gate 	 * TRICK:
1900Sstevel@tonic-gate 	 *	the change list contains both files that have changed
1910Sstevel@tonic-gate 	 *	(and probably warrant reconciliation) and files that
1920Sstevel@tonic-gate 	 *	we couldn't get up-to-date stat information on.  The
1930Sstevel@tonic-gate 	 *	latter files should just be flagged as being in conflict
1940Sstevel@tonic-gate 	 *	so they can be reported in the summary.  The same is
1950Sstevel@tonic-gate 	 *	true of all subsequent files if we abort reconciliation.
1960Sstevel@tonic-gate 	 */
1970Sstevel@tonic-gate 	for (fp = changes; fp; fp = fp->f_rnext)
1980Sstevel@tonic-gate 		if (aborted || (fp->f_flags & F_STAT_ERROR)) {
1990Sstevel@tonic-gate 			fp->f_flags |= F_CONFLICT;
2000Sstevel@tonic-gate 			/* if it isn't in the baseline yet, don't add it */
2010Sstevel@tonic-gate 			if ((fp->f_flags & F_IN_BASELINE) == 0)
2020Sstevel@tonic-gate 				fp->f_flags |= F_REMOVE;
2030Sstevel@tonic-gate 			fp->f_problem = aborted ? PROB_aborted : PROB_restat;
2040Sstevel@tonic-gate 			(fp->f_base)->b_unresolved++;
2050Sstevel@tonic-gate 			errs |= ERR_UNRESOLVED;
2060Sstevel@tonic-gate 			if (opt_verbose)
2070Sstevel@tonic-gate 				fprintf(stdout,
2080Sstevel@tonic-gate 					gettext(aborted ? V_suppressed
2090Sstevel@tonic-gate 							: V_nostat),
2100Sstevel@tonic-gate 					fp->f_fullname);
2110Sstevel@tonic-gate 		} else {
2120Sstevel@tonic-gate 			err = reconcile(fp);
2130Sstevel@tonic-gate 			errs |= err;
2140Sstevel@tonic-gate 			if (opt_halt && (err & ERR_ABORT)) {
2150Sstevel@tonic-gate 				fprintf(stderr, gettext(ERR_abort_h));
2160Sstevel@tonic-gate 				aborted = TRUE;
2170Sstevel@tonic-gate 			}
2180Sstevel@tonic-gate 		}
2190Sstevel@tonic-gate 
2200Sstevel@tonic-gate 	return (errs);
2210Sstevel@tonic-gate }
2220Sstevel@tonic-gate 
2230Sstevel@tonic-gate /*
2240Sstevel@tonic-gate  * routine:
2250Sstevel@tonic-gate  *	prune_file
2260Sstevel@tonic-gate  *
2270Sstevel@tonic-gate  * purpose:
2280Sstevel@tonic-gate  *	to look for file entries that should be pruned from baseline
2290Sstevel@tonic-gate  *	prune the current file if it needs pruning, and recursively
2300Sstevel@tonic-gate  *	descend if it is a directory.
2310Sstevel@tonic-gate  *
2320Sstevel@tonic-gate  * parameters:
2330Sstevel@tonic-gate  *	pointer to file node
2340Sstevel@tonic-gate  */
2350Sstevel@tonic-gate static int
prune_file(struct file * fp)2360Sstevel@tonic-gate prune_file(struct file *fp)
2370Sstevel@tonic-gate {	struct file *cp;
2380Sstevel@tonic-gate 	int prunes = 0;
2390Sstevel@tonic-gate 
2400Sstevel@tonic-gate 	/* if node hasn't been evaluated, mark it for removal	*/
2410Sstevel@tonic-gate 	if ((fp->f_flags & (F_EVALUATE|F_STAT_ERROR)) == 0) {
2420Sstevel@tonic-gate 		fp->f_flags |= F_REMOVE;
2430Sstevel@tonic-gate 		prunes++;
2440Sstevel@tonic-gate 		if (opt_debug & DBG_ANAL)
2450Sstevel@tonic-gate 			fprintf(stderr, "ANAL: PRUNE %s\n", fp->f_name);
2460Sstevel@tonic-gate 	}
2470Sstevel@tonic-gate 
2480Sstevel@tonic-gate 	/* now check our children				*/
2490Sstevel@tonic-gate 	for (cp = fp->f_files; cp; cp = cp->f_next)
2500Sstevel@tonic-gate 		prunes += prune_file(cp);
2510Sstevel@tonic-gate 
2520Sstevel@tonic-gate 	return (prunes);
2530Sstevel@tonic-gate }
2540Sstevel@tonic-gate 
2550Sstevel@tonic-gate /*
2560Sstevel@tonic-gate  * routine:
2570Sstevel@tonic-gate  *	prune
2580Sstevel@tonic-gate  *
2590Sstevel@tonic-gate  * purpose:
2600Sstevel@tonic-gate  *	to prune the baseline of entries that no longer correspond to
2610Sstevel@tonic-gate  *	existing rules.
2620Sstevel@tonic-gate  *
2630Sstevel@tonic-gate  * notes:
2640Sstevel@tonic-gate  *	This routine just calls prune_file on the top of each base tree.
2650Sstevel@tonic-gate  */
2660Sstevel@tonic-gate int
prune()2670Sstevel@tonic-gate prune()
2680Sstevel@tonic-gate {	struct base *bp;
2690Sstevel@tonic-gate 	struct file *fp;
2700Sstevel@tonic-gate 	int prunes = 0;
2710Sstevel@tonic-gate 
2720Sstevel@tonic-gate 	for (bp = bases; bp; bp = bp->b_next) {
2730Sstevel@tonic-gate 		for (fp = bp->b_files; fp; fp = fp->f_next)
2740Sstevel@tonic-gate 			prunes += prune_file(fp);
2750Sstevel@tonic-gate 
2760Sstevel@tonic-gate 		if ((bp->b_flags & F_EVALUATE) == 0)
2770Sstevel@tonic-gate 			bp->b_flags |= F_REMOVE;
2780Sstevel@tonic-gate 	}
2790Sstevel@tonic-gate 
2800Sstevel@tonic-gate 	return (prunes);
2810Sstevel@tonic-gate }
2820Sstevel@tonic-gate 
2830Sstevel@tonic-gate /*
2840Sstevel@tonic-gate  * routine:
2850Sstevel@tonic-gate  *	summary
2860Sstevel@tonic-gate  *
2870Sstevel@tonic-gate  * purpose:
2880Sstevel@tonic-gate  *	to print out statics and conflict lists
2890Sstevel@tonic-gate  */
2900Sstevel@tonic-gate void
summary()2910Sstevel@tonic-gate summary()
2920Sstevel@tonic-gate {	struct base *bp;
2930Sstevel@tonic-gate 	struct file *fp;
2940Sstevel@tonic-gate 	extern bool_t need_super;
2950Sstevel@tonic-gate 
2960Sstevel@tonic-gate 	(void) fflush(stdout);
2970Sstevel@tonic-gate 
2980Sstevel@tonic-gate 	for (bp = bases; bp; bp = bp->b_next) {
2990Sstevel@tonic-gate 
300*3517Smp204432 		/* see if this base was irrelevant	*/
3010Sstevel@tonic-gate 		if ((bp->b_flags & F_EVALUATE) == 0)
3020Sstevel@tonic-gate 			continue;
3030Sstevel@tonic-gate 
3040Sstevel@tonic-gate 		/* print out a summary for this base	*/
3050Sstevel@tonic-gate 		fprintf(stderr, gettext(SUM_hd),
3060Sstevel@tonic-gate 			bp->b_src_spec, bp->b_dst_spec, bp->b_totfiles);
3070Sstevel@tonic-gate 		fprintf(stderr, gettext(SUM_dst),
3080Sstevel@tonic-gate 			bp->b_dst_copies, bp->b_dst_deletes, bp->b_dst_misc);
3090Sstevel@tonic-gate 		fprintf(stderr, gettext(SUM_src),
3100Sstevel@tonic-gate 			bp->b_src_copies, bp->b_src_deletes, bp->b_src_misc);
3110Sstevel@tonic-gate 		if (bp->b_unresolved)
3120Sstevel@tonic-gate 			fprintf(stderr, gettext(SUM_unresolved),
3130Sstevel@tonic-gate 				bp->b_unresolved);
3140Sstevel@tonic-gate 
3150Sstevel@tonic-gate 
3160Sstevel@tonic-gate 		/* print out a list of unreconciled files for this base	*/
3170Sstevel@tonic-gate 		for (fp = changes; fp; fp = fp->f_rnext) {
3180Sstevel@tonic-gate 			if (fp->f_base != bp)
3190Sstevel@tonic-gate 				continue;
3200Sstevel@tonic-gate 			if ((fp->f_flags & F_CONFLICT) == 0)
3210Sstevel@tonic-gate 				continue;
3220Sstevel@tonic-gate 			fprintf(stderr, "\t\t%s (%s)\n", fp->f_fullname,
3230Sstevel@tonic-gate 				fp->f_problem ? fp->f_problem : "???");
3240Sstevel@tonic-gate 		}
3250Sstevel@tonic-gate 
3260Sstevel@tonic-gate 		fprintf(stderr, "\n");
3270Sstevel@tonic-gate 	}
3280Sstevel@tonic-gate 
3290Sstevel@tonic-gate 	if (need_super)
3300Sstevel@tonic-gate 		fprintf(stderr, gettext(WARN_super));
3310Sstevel@tonic-gate }
3320Sstevel@tonic-gate 
3330Sstevel@tonic-gate /*
3340Sstevel@tonic-gate  * routine:
3350Sstevel@tonic-gate  *	check_file
3360Sstevel@tonic-gate  *
3370Sstevel@tonic-gate  * purpose:
3380Sstevel@tonic-gate  *	figure out if a file requires reconciliation and recursively
3390Sstevel@tonic-gate  *	descend into all sub-files and directories
3400Sstevel@tonic-gate  *
3410Sstevel@tonic-gate  * parameters:
3420Sstevel@tonic-gate  *	base pointer
3430Sstevel@tonic-gate  *	file pointer
3440Sstevel@tonic-gate  *
3450Sstevel@tonic-gate  * returns:
3460Sstevel@tonic-gate  *	error mask
3470Sstevel@tonic-gate  *	built up changes needed list
3480Sstevel@tonic-gate  *	updated statistics
3490Sstevel@tonic-gate  *
3500Sstevel@tonic-gate  * notes:
3510Sstevel@tonic-gate  *	this routine builds up a path name as it descends through
3520Sstevel@tonic-gate  *	the tree (see push_name, pop_name, get_name).
3530Sstevel@tonic-gate  */
3540Sstevel@tonic-gate static errmask_t
check_file(struct file * fp)3550Sstevel@tonic-gate check_file(struct file *fp)
3560Sstevel@tonic-gate {	struct file *cp;
3570Sstevel@tonic-gate 	int errs = 0;
3580Sstevel@tonic-gate 
3590Sstevel@tonic-gate 	if ((fp->f_flags & F_STAT_ERROR) == 0) {
3600Sstevel@tonic-gate 		/* see if the source has changed	*/
3610Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_modtime	= fp->f_s_modtime;
3620Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_ino	= fp->f_s_inum;
3630Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_d_maj	= fp->f_s_maj;
3640Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_d_min	= fp->f_s_min;
3650Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_nlink	= fp->f_s_nlink;
3660Sstevel@tonic-gate 		fp->f_srcdiffs |= check_changes(fp, OPT_BASE, OPT_SRC);
3670Sstevel@tonic-gate 
3680Sstevel@tonic-gate 		/* see if the destination has changed	*/
3690Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_modtime	= fp->f_d_modtime;
3700Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_ino    	= fp->f_d_inum;
3710Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_d_maj    = fp->f_d_maj;
3720Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_d_min    = fp->f_d_min;
3730Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_nlink	= fp->f_d_nlink;
3740Sstevel@tonic-gate 		fp->f_dstdiffs |= check_changes(fp, OPT_BASE, OPT_DST);
3750Sstevel@tonic-gate 
3760Sstevel@tonic-gate 		/* if nobody thinks the file exists, baseline needs pruning */
3770Sstevel@tonic-gate 		if ((fp->f_flags & (F_IN_SOURCE|F_IN_DEST)) == 0) {
3780Sstevel@tonic-gate 			fp->f_srcdiffs |= D_DELETE;
3790Sstevel@tonic-gate 			fp->f_dstdiffs |= D_DELETE;
3800Sstevel@tonic-gate 		}
3810Sstevel@tonic-gate 
3820Sstevel@tonic-gate 		/* keep track of possible deletions to look for trouble	*/
3830Sstevel@tonic-gate 		if ((fp->f_dstdiffs | fp->f_srcdiffs) & D_DELETE) {
3840Sstevel@tonic-gate 			est_deletes++;
3850Sstevel@tonic-gate 
3860Sstevel@tonic-gate 			/* see if file is (or has been) a non-empty directory */
3870Sstevel@tonic-gate 			if (fp->f_files)
3880Sstevel@tonic-gate 				est_rmdirs++;
3890Sstevel@tonic-gate 		}
3900Sstevel@tonic-gate 	}
3910Sstevel@tonic-gate 
3920Sstevel@tonic-gate 	/* if we found differences, queue the file for reconciliation 	*/
3930Sstevel@tonic-gate 	if (fp->f_srcdiffs || fp->f_dstdiffs || fp->f_flags & F_STAT_ERROR) {
3940Sstevel@tonic-gate 		queue_file(fp);
3950Sstevel@tonic-gate 
3960Sstevel@tonic-gate 		if (opt_debug & DBG_ANAL) {
3970Sstevel@tonic-gate 			fprintf(stderr, "ANAL: src=%s",
3980Sstevel@tonic-gate 				showflags(diffmap, fp->f_srcdiffs));
3990Sstevel@tonic-gate 			fprintf(stderr, " dst=%s",
4000Sstevel@tonic-gate 				showflags(diffmap, fp->f_dstdiffs));
4010Sstevel@tonic-gate 			fprintf(stderr, " flgs=%s",
4020Sstevel@tonic-gate 				showflags(fileflags, fp->f_flags));
4030Sstevel@tonic-gate 			fprintf(stderr, " name=%s\n", fp->f_fullname);
4040Sstevel@tonic-gate 		}
4050Sstevel@tonic-gate 	}
4060Sstevel@tonic-gate 
4070Sstevel@tonic-gate 	/* bump the total file count	*/
4080Sstevel@tonic-gate 	fp->f_base->b_totfiles++;
4090Sstevel@tonic-gate 	total_files++;
4100Sstevel@tonic-gate 
4110Sstevel@tonic-gate 	/* if this is not a directory, we're done	*/
4120Sstevel@tonic-gate 	if (fp->f_files == 0)
4130Sstevel@tonic-gate 		return (errs);
4140Sstevel@tonic-gate 
4150Sstevel@tonic-gate 	/*
4160Sstevel@tonic-gate 	 * If this is a directory, we need to recursively analyze
4170Sstevel@tonic-gate 	 * our children, but only children who have been evaluated.
4180Sstevel@tonic-gate 	 * If a node has not been evaluated, then we don't have
4190Sstevel@tonic-gate 	 * updated stat information and there is nothing to analyze.
4200Sstevel@tonic-gate 	 *
4210Sstevel@tonic-gate 	 * we also want to run though all nodes with STAT errors
4220Sstevel@tonic-gate 	 * so that we can put them on the reconciliation list.
4230Sstevel@tonic-gate 	 * If a directory is unreadable on one side, all files
4240Sstevel@tonic-gate 	 * under that directory (ON BOTH SIDES) must be marked as
4250Sstevel@tonic-gate 	 * blocked by stat errors.
4260Sstevel@tonic-gate 	 */
4270Sstevel@tonic-gate 	push_name(fp->f_name);
4280Sstevel@tonic-gate 
4290Sstevel@tonic-gate 	for (cp = fp->f_files; cp; cp = cp->f_next) {
4300Sstevel@tonic-gate 		if (fp->f_flags & F_STAT_ERROR)
4310Sstevel@tonic-gate 			cp->f_flags |= F_STAT_ERROR;
4320Sstevel@tonic-gate 		if (cp->f_flags & (F_EVALUATE|F_STAT_ERROR))
4330Sstevel@tonic-gate 			errs |= check_file(cp);
4340Sstevel@tonic-gate 	}
4350Sstevel@tonic-gate 
4360Sstevel@tonic-gate 	pop_name();
4370Sstevel@tonic-gate 
4380Sstevel@tonic-gate 	return (errs);
4390Sstevel@tonic-gate }
4400Sstevel@tonic-gate 
4410Sstevel@tonic-gate /*
4420Sstevel@tonic-gate  * routine:
4430Sstevel@tonic-gate  *	check_changes
4440Sstevel@tonic-gate  *
4450Sstevel@tonic-gate  * purpose:
4460Sstevel@tonic-gate  *	to figure out what has changed for a specific file
4470Sstevel@tonic-gate  *
4480Sstevel@tonic-gate  * parameters:
4490Sstevel@tonic-gate  *	file pointer
4500Sstevel@tonic-gate  *	the reference info
4510Sstevel@tonic-gate  *	the info to be checked for changes
4520Sstevel@tonic-gate  *
4530Sstevel@tonic-gate  * returns:
4540Sstevel@tonic-gate  *	diff mask
4550Sstevel@tonic-gate  *
4560Sstevel@tonic-gate  * notes:
4570Sstevel@tonic-gate  *	this routine doesn't pretend to understand what happened.
4580Sstevel@tonic-gate  *	it merely enumerates the ways in which the files differ.
4590Sstevel@tonic-gate  */
4600Sstevel@tonic-gate static diffmask_t
check_changes(struct file * fp,int ref,int new)4610Sstevel@tonic-gate check_changes(struct file *fp, int ref, int new)
4620Sstevel@tonic-gate {	struct fileinfo *rp, *np;
4630Sstevel@tonic-gate 	int mask = 0;
4640Sstevel@tonic-gate 	int type;
4650Sstevel@tonic-gate 
4660Sstevel@tonic-gate 	rp = &fp->f_info[ref];
4670Sstevel@tonic-gate 	np = &fp->f_info[new];
4680Sstevel@tonic-gate 
4690Sstevel@tonic-gate 	if (np->f_uid != rp->f_uid)
4700Sstevel@tonic-gate 		mask |= D_UID;
4710Sstevel@tonic-gate 
4720Sstevel@tonic-gate 	if (np->f_gid != rp->f_gid)
4730Sstevel@tonic-gate 		mask |= D_GID;
4740Sstevel@tonic-gate 
4750Sstevel@tonic-gate 	if (np->f_mode != rp->f_mode)
4760Sstevel@tonic-gate 		mask |= D_PROT;
4770Sstevel@tonic-gate 
4780Sstevel@tonic-gate 	type = np->f_type;
4790Sstevel@tonic-gate 	if (type != rp->f_type) {
4800Sstevel@tonic-gate 		if (type == 0)
4810Sstevel@tonic-gate 			mask |= D_DELETE;
4820Sstevel@tonic-gate 		else if (rp->f_type == 0)
4830Sstevel@tonic-gate 			mask |= D_CREATE;
4840Sstevel@tonic-gate 		else
4850Sstevel@tonic-gate 			mask |= D_TYPE;
4860Sstevel@tonic-gate 	} else if (type == S_IFBLK || type == S_IFCHR) {
4870Sstevel@tonic-gate 		/*
4880Sstevel@tonic-gate 		 * for special files, we only look at the maj/min
4890Sstevel@tonic-gate 		 */
4900Sstevel@tonic-gate 		if (np->f_rd_maj != rp->f_rd_maj)
4910Sstevel@tonic-gate 			mask |= D_SIZE;
4920Sstevel@tonic-gate 		if (np->f_rd_min != rp->f_rd_min)
4930Sstevel@tonic-gate 			mask |= D_SIZE;
4940Sstevel@tonic-gate 	} else if (type != S_IFDIR) {
4950Sstevel@tonic-gate 		/*
4960Sstevel@tonic-gate 		 * for directories, we don't look directly at
4970Sstevel@tonic-gate 		 * the contents, so these fields don't mean
4980Sstevel@tonic-gate 		 * anything.  If the directories have changed
4990Sstevel@tonic-gate 		 * in any interesting way, we'll find it by
5000Sstevel@tonic-gate 		 * walking the tree.
5010Sstevel@tonic-gate 		 */
5020Sstevel@tonic-gate 		if (np->f_modtime > rp->f_modtime)
5030Sstevel@tonic-gate 			mask |= D_MTIME;
5040Sstevel@tonic-gate 
5050Sstevel@tonic-gate 		if (np->f_size != rp->f_size)
5060Sstevel@tonic-gate 			mask |= D_SIZE;
5070Sstevel@tonic-gate 
5080Sstevel@tonic-gate 		if (np->f_nlink != rp->f_nlink)
5090Sstevel@tonic-gate 			mask |= D_LINKS;
5100Sstevel@tonic-gate 	}
5110Sstevel@tonic-gate 
5120Sstevel@tonic-gate 	if (cmp_acls(rp, np) == 0)
5130Sstevel@tonic-gate 		mask |= D_FACLS;
5140Sstevel@tonic-gate 
5150Sstevel@tonic-gate 	return (mask);
5160Sstevel@tonic-gate }
5170Sstevel@tonic-gate 
5180Sstevel@tonic-gate /*
5190Sstevel@tonic-gate  * routine:
5200Sstevel@tonic-gate  *	same_name
5210Sstevel@tonic-gate  *
5220Sstevel@tonic-gate  * purpose:
5230Sstevel@tonic-gate  *	to figure out whether or not two databsae nodes actually refer to
5240Sstevel@tonic-gate  *	the same file.
5250Sstevel@tonic-gate  *
5260Sstevel@tonic-gate  * parameters:
5270Sstevel@tonic-gate  *	pointers to two file description nodes
5280Sstevel@tonic-gate  *	which side we should check
5290Sstevel@tonic-gate  *
5300Sstevel@tonic-gate  * returns:
5310Sstevel@tonic-gate  *	TRUE/FALSE
5320Sstevel@tonic-gate  *
5330Sstevel@tonic-gate  * notes:
5340Sstevel@tonic-gate  *	if a single directory is specified in multiple base pairs, it
5350Sstevel@tonic-gate  *	is possible to have multiple nodes in the database describing
5360Sstevel@tonic-gate  *	the same file.  This routine is supposed to detect those cases.
5370Sstevel@tonic-gate  *
5380Sstevel@tonic-gate  *	what should be a trivial string comparison is complicated by
5390Sstevel@tonic-gate  *	the possibility that the two nodes might describe the same file
5400Sstevel@tonic-gate  *	from base directories at different depths.  Thus, rather than
5410Sstevel@tonic-gate  *	comparing two strings, we really want to compare the concatenation
5420Sstevel@tonic-gate  *	of two pairs of strings.  Unfortunately calling full_name would
5430Sstevel@tonic-gate  *	be awkward right now, so instead we have our own comparison
5440Sstevel@tonic-gate  *	routine that automatically skips from the first string to
5450Sstevel@tonic-gate  *	the second.
5460Sstevel@tonic-gate  */
5470Sstevel@tonic-gate static bool_t
same_name(struct file * f1,struct file * f2,side_t srcdst)5480Sstevel@tonic-gate same_name(struct file *f1, struct file *f2, side_t srcdst)
5490Sstevel@tonic-gate {
5500Sstevel@tonic-gate 	char *s1, *s2, *x1, *x2;
5510Sstevel@tonic-gate 
5520Sstevel@tonic-gate 	if (srcdst == OPT_SRC) {
5530Sstevel@tonic-gate 		s1 = (f1->f_base)->b_src_name;
5540Sstevel@tonic-gate 		s2 = (f2->f_base)->b_src_name;
5550Sstevel@tonic-gate 	} else {
5560Sstevel@tonic-gate 		s1 = (f1->f_base)->b_dst_name;
5570Sstevel@tonic-gate 		s2 = (f2->f_base)->b_dst_name;
5580Sstevel@tonic-gate 	}
5590Sstevel@tonic-gate 	x1 = f1->f_fullname;
5600Sstevel@tonic-gate 	x2 = f2->f_fullname;
5610Sstevel@tonic-gate 
5620Sstevel@tonic-gate 	/*
5630Sstevel@tonic-gate 	 * Compare the two names, and if they differ before they end
5640Sstevel@tonic-gate 	 * this is a non-match.  If they both end at the same time,
5650Sstevel@tonic-gate 	 * this is a match.
5660Sstevel@tonic-gate 	 *
5670Sstevel@tonic-gate 	 * The trick here is that each string is actually the logical
5680Sstevel@tonic-gate 	 * concatenation of two strings, and we need to automatically
5690Sstevel@tonic-gate 	 * wrap from the first to the second string in each pair.  There
5700Sstevel@tonic-gate 	 * is no requirement that the two (concatenated) strings be
5710Sstevel@tonic-gate 	 * broken at the same point, so we have a slightly baroque
5720Sstevel@tonic-gate 	 * comparsion loop.
5730Sstevel@tonic-gate 	 */
5740Sstevel@tonic-gate 	while (*s1 && *s1 == *s2) {
5750Sstevel@tonic-gate 
5760Sstevel@tonic-gate 		/*
5770Sstevel@tonic-gate 		 * strings have been identical so far, so advance the
5780Sstevel@tonic-gate 		 * pointers and continue the comparison.  The trick
5790Sstevel@tonic-gate 		 * is that when either string ends, we have to wrap
5800Sstevel@tonic-gate 		 * over to its extension.
5810Sstevel@tonic-gate 		 */
5820Sstevel@tonic-gate 		s1++; s2++;
5830Sstevel@tonic-gate 		if (*s1 && *s2)
5840Sstevel@tonic-gate 			continue;
5850Sstevel@tonic-gate 
5860Sstevel@tonic-gate 		/*
5870Sstevel@tonic-gate 		 * at least one of the strings has ended.
5880Sstevel@tonic-gate 		 * there is an implicit slash between the string
5890Sstevel@tonic-gate 		 * and its extension, and this has to be matched
5900Sstevel@tonic-gate 		 * against the other string.
5910Sstevel@tonic-gate 		 */
5920Sstevel@tonic-gate 		if (*s1 != *s2) {
5930Sstevel@tonic-gate 			if (*s1 == 0 && *s2 == '/')
5940Sstevel@tonic-gate 				s2++;
5950Sstevel@tonic-gate 			else if (*s2 == 0 && *s1 == '/')
5960Sstevel@tonic-gate 				s1++;
5970Sstevel@tonic-gate 			else
5980Sstevel@tonic-gate 				/* the disagreement doesn't come at a slash */
5990Sstevel@tonic-gate 				break;
6000Sstevel@tonic-gate 		}
6010Sstevel@tonic-gate 
6020Sstevel@tonic-gate 		/*
6030Sstevel@tonic-gate 		 * if either string has ended, wrap to its extension
6040Sstevel@tonic-gate 		 */
6050Sstevel@tonic-gate 		if (*s1 == 0 && x1 != 0) {
6060Sstevel@tonic-gate 			s1 = x1;
6070Sstevel@tonic-gate 			x1 = 0;
6080Sstevel@tonic-gate 		}
6090Sstevel@tonic-gate 		if (*s2 == 0 && x2 != 0) {
6100Sstevel@tonic-gate 			s2 = x2;
6110Sstevel@tonic-gate 			x2 = 0;
6120Sstevel@tonic-gate 		}
6130Sstevel@tonic-gate 	}
6140Sstevel@tonic-gate 
6150Sstevel@tonic-gate 	return (*s1 == *s2);
6160Sstevel@tonic-gate }
6170Sstevel@tonic-gate 
6180Sstevel@tonic-gate /*
6190Sstevel@tonic-gate  * routine:
6200Sstevel@tonic-gate  *	find_link
6210Sstevel@tonic-gate  *
6220Sstevel@tonic-gate  * purpose:
6230Sstevel@tonic-gate  *	to figure out if there is a file to which we should
6240Sstevel@tonic-gate  *	be creating a link (rather than making a copy)
6250Sstevel@tonic-gate  *
6260Sstevel@tonic-gate  * parameters:
6270Sstevel@tonic-gate  *	file node for the file to be created (that we hope is merely a link)
6280Sstevel@tonic-gate  *	which side is to be changed (src/dst)
6290Sstevel@tonic-gate  *
6300Sstevel@tonic-gate  * return:
6310Sstevel@tonic-gate  *	0	no link is appropriate
6320Sstevel@tonic-gate  *	else	pointer to file node for link referent
6330Sstevel@tonic-gate  *
6340Sstevel@tonic-gate  * notes:
6350Sstevel@tonic-gate  *	there are a few strange heuristics in this routine and I
6360Sstevel@tonic-gate  *	wouldn't bet my soul that I got all of them right.  The general
6370Sstevel@tonic-gate  *	theory is that when a new file is created, we look to see if it
6380Sstevel@tonic-gate  *	is a link to another file on the changed side, and if it is, we
6390Sstevel@tonic-gate  *	find the corresponding file on the unchanged side.
6400Sstevel@tonic-gate  *
6410Sstevel@tonic-gate  *	cases we want to be able to handle:
6420Sstevel@tonic-gate  *	    1.	one or more links are created to a prexisting file
6430Sstevel@tonic-gate  *	    2.	a preexisting only link is renamed
6440Sstevel@tonic-gate  *	    3.  a rename of one of multiple links to a preexisting file
6450Sstevel@tonic-gate  *	    4.	a single file is created with multiple links
6460Sstevel@tonic-gate  */
6470Sstevel@tonic-gate struct file *
find_link(struct file * fp,side_t srcdst)6480Sstevel@tonic-gate find_link(struct file *fp, side_t srcdst)
6490Sstevel@tonic-gate {	struct file *lp;
6500Sstevel@tonic-gate 	side_t chgside, tgtside;
6510Sstevel@tonic-gate 	struct fileinfo *chgp, *tgtp, *basp, *fcp, *ftp;
6520Sstevel@tonic-gate 
6530Sstevel@tonic-gate 	/* chg = side on which the change was noticed		*/
6540Sstevel@tonic-gate 	/* tgt = side to which the change is to be propagated	*/
6550Sstevel@tonic-gate 	chgside = (srcdst == OPT_SRC) ? OPT_DST : OPT_SRC;
6560Sstevel@tonic-gate 	tgtside = (srcdst == OPT_SRC) ? OPT_SRC : OPT_DST;
6570Sstevel@tonic-gate 	fcp = &fp->f_info[chgside];
6580Sstevel@tonic-gate 	ftp = &fp->f_info[tgtside];
6590Sstevel@tonic-gate 
6600Sstevel@tonic-gate 	/*
6610Sstevel@tonic-gate 	 * cases 1 and 3
6620Sstevel@tonic-gate 	 *
6630Sstevel@tonic-gate 	 * When a new link is created, we should be able to find
6640Sstevel@tonic-gate 	 * another file in the changed hierarchy that has the same
6650Sstevel@tonic-gate 	 * I-node number.  We expect it to be on the changed list
6660Sstevel@tonic-gate 	 * because the link count will have gone up or because all
6670Sstevel@tonic-gate 	 * of the copies are new.  If we find one, then the new file
6680Sstevel@tonic-gate 	 * on the receiving file should be a link to the corresponding
6690Sstevel@tonic-gate 	 * existing file.
6700Sstevel@tonic-gate 	 *
6710Sstevel@tonic-gate 	 * case 4
6720Sstevel@tonic-gate 	 *
6730Sstevel@tonic-gate 	 * the first link will be dealt with as a copy, but all
6740Sstevel@tonic-gate 	 * subsequent links should find an existing file analogous
6750Sstevel@tonic-gate 	 * to one of the links on the changed side, and create
6760Sstevel@tonic-gate 	 * corresponding links on the other side.
6770Sstevel@tonic-gate 	 *
6780Sstevel@tonic-gate 	 * in each of these cases, there should be multiple links
6790Sstevel@tonic-gate 	 * on the changed side.  If the linkcount on the changed
6800Sstevel@tonic-gate 	 * side is one, we needn't bother searching for other links.
6810Sstevel@tonic-gate 	 */
6820Sstevel@tonic-gate 	if (fcp->f_nlink > 1)
6830Sstevel@tonic-gate 	for (lp = changes; lp; lp = lp->f_rnext) {
6840Sstevel@tonic-gate 		/* finding the same node doesn't count	*/
6850Sstevel@tonic-gate 		if (fp == lp)
6860Sstevel@tonic-gate 			continue;
6870Sstevel@tonic-gate 
6880Sstevel@tonic-gate 		tgtp = &lp->f_info[tgtside];
6890Sstevel@tonic-gate 		chgp = &lp->f_info[chgside];
6900Sstevel@tonic-gate 
6910Sstevel@tonic-gate 		/*
6920Sstevel@tonic-gate 		 * if the file doesn't already exist on the target side
6930Sstevel@tonic-gate 		 * we cannot make a link to it
6940Sstevel@tonic-gate 		 */
6950Sstevel@tonic-gate 		if (tgtp->f_mode == 0)
6960Sstevel@tonic-gate 			continue;
6970Sstevel@tonic-gate 
6980Sstevel@tonic-gate 		/*
6990Sstevel@tonic-gate 		 * if this is indeed a link, then the prospective file on
7000Sstevel@tonic-gate 		 * the changed side will have the same dev/inum as the file
7010Sstevel@tonic-gate 		 * we are looking for
7020Sstevel@tonic-gate 		 */
7030Sstevel@tonic-gate 		if (fcp->f_d_maj != chgp->f_d_maj)
7040Sstevel@tonic-gate 			continue;
7050Sstevel@tonic-gate 		if (fcp->f_d_min != chgp->f_d_min)
7060Sstevel@tonic-gate 			continue;
7070Sstevel@tonic-gate 		if (fcp->f_ino != chgp->f_ino)
7080Sstevel@tonic-gate 			continue;
7090Sstevel@tonic-gate 
7100Sstevel@tonic-gate 		/*
7110Sstevel@tonic-gate 		 * if the target side is already a link to this file,
7120Sstevel@tonic-gate 		 * then there is no new link to be created
7130Sstevel@tonic-gate 		 * FIX: how does this interact with copies over links
7140Sstevel@tonic-gate 		 */
7150Sstevel@tonic-gate 		if ((ftp->f_d_maj == tgtp->f_d_maj) &&
7160Sstevel@tonic-gate 		    (ftp->f_d_min == tgtp->f_d_min) &&
7170Sstevel@tonic-gate 		    (ftp->f_ino   == tgtp->f_ino))
7180Sstevel@tonic-gate 			continue;
7190Sstevel@tonic-gate 
7200Sstevel@tonic-gate 		/*
7210Sstevel@tonic-gate 		 * there is a pathological situation where a single file
7220Sstevel@tonic-gate 		 * might appear under multiple base directories.  This is
7230Sstevel@tonic-gate 		 * damned awkward to detect in any other way, so we must
7240Sstevel@tonic-gate 		 * check to see if we have just found another database
7250Sstevel@tonic-gate 		 * instance for the same file (on the changed side).
7260Sstevel@tonic-gate 		 */
7270Sstevel@tonic-gate 		if ((fp->f_base != lp->f_base) && same_name(fp, lp, chgside))
7280Sstevel@tonic-gate 			continue;
7290Sstevel@tonic-gate 
7300Sstevel@tonic-gate 		if (opt_debug & DBG_ANAL)
7310Sstevel@tonic-gate 			fprintf(stderr, "ANAL: FIND LINK %s and %s\n",
7320Sstevel@tonic-gate 				fp->f_fullname, lp->f_fullname);
7330Sstevel@tonic-gate 
7340Sstevel@tonic-gate 		return (lp);
7350Sstevel@tonic-gate 	}
7360Sstevel@tonic-gate 
7370Sstevel@tonic-gate 	/*
7380Sstevel@tonic-gate 	 * case 2: a simple rename of the only link
7390Sstevel@tonic-gate 	 *
7400Sstevel@tonic-gate 	 * In this case, there may not be any other existing file on
7410Sstevel@tonic-gate 	 * the changed side that has the same I-node number.  There
7420Sstevel@tonic-gate 	 * might, however, be a record of such a file in the baseline.
7430Sstevel@tonic-gate 	 * If we can find an identical file with a different name that
7440Sstevel@tonic-gate 	 * has recently disappeared, we have a likely rename.
7450Sstevel@tonic-gate 	 */
7460Sstevel@tonic-gate 	for (lp = changes; lp; lp = lp->f_rnext) {
7470Sstevel@tonic-gate 
7480Sstevel@tonic-gate 		/* finding the same node doesn't count			*/
7490Sstevel@tonic-gate 		if (fp == lp)
7500Sstevel@tonic-gate 			continue;
7510Sstevel@tonic-gate 
7520Sstevel@tonic-gate 		tgtp = &lp->f_info[tgtside];
7530Sstevel@tonic-gate 		chgp = &lp->f_info[chgside];
7540Sstevel@tonic-gate 
7550Sstevel@tonic-gate 		/*
7560Sstevel@tonic-gate 		 * if the file still exists on the changed side this is
7570Sstevel@tonic-gate 		 * not a simple rename, and in fact the previous pass
7580Sstevel@tonic-gate 		 * would have found it.
7590Sstevel@tonic-gate 		 */
7600Sstevel@tonic-gate 		if (chgp->f_mode != 0)
7610Sstevel@tonic-gate 			continue;
7620Sstevel@tonic-gate 
7630Sstevel@tonic-gate 		/*
7640Sstevel@tonic-gate 		 * the inode number for the new link on the changed
7650Sstevel@tonic-gate 		 * side must match the inode number for the old link
7660Sstevel@tonic-gate 		 * from the baseline.
7670Sstevel@tonic-gate 		 */
7680Sstevel@tonic-gate 		if (fcp->f_d_maj != ((srcdst == OPT_SRC) ? lp->f_d_maj
7690Sstevel@tonic-gate 							: lp->f_s_maj))
7700Sstevel@tonic-gate 			continue;
7710Sstevel@tonic-gate 		if (fcp->f_d_min != ((srcdst == OPT_SRC) ? lp->f_d_min
7720Sstevel@tonic-gate 							: lp->f_s_min))
7730Sstevel@tonic-gate 			continue;
7740Sstevel@tonic-gate 		if (fcp->f_ino != ((srcdst == OPT_SRC) ? lp->f_d_inum
7750Sstevel@tonic-gate 							: lp->f_s_inum))
7760Sstevel@tonic-gate 			continue;
7770Sstevel@tonic-gate 
7780Sstevel@tonic-gate 		/* finding a file we are already linked to doesn't help	*/
7790Sstevel@tonic-gate 		if ((ftp->f_d_maj == tgtp->f_d_maj) &&
7800Sstevel@tonic-gate 		    (ftp->f_d_min == tgtp->f_d_min) &&
7810Sstevel@tonic-gate 		    (ftp->f_ino   == tgtp->f_ino))
7820Sstevel@tonic-gate 			continue;
7830Sstevel@tonic-gate 
7840Sstevel@tonic-gate 		/*
7850Sstevel@tonic-gate 		 * there is a danger that we will confuse an
7860Sstevel@tonic-gate 		 * inode reallocation with a rename.  We should
7870Sstevel@tonic-gate 		 * only consider this to be a rename if the
7880Sstevel@tonic-gate 		 * new file is identical to the old one
7890Sstevel@tonic-gate 		 */
7900Sstevel@tonic-gate 		basp = &lp->f_info[OPT_BASE];
7910Sstevel@tonic-gate 		if (fcp->f_type != basp->f_type)
7920Sstevel@tonic-gate 			continue;
7930Sstevel@tonic-gate 		if (fcp->f_size != basp->f_size)
7940Sstevel@tonic-gate 			continue;
7950Sstevel@tonic-gate 		if (fcp->f_mode != basp->f_mode)
7960Sstevel@tonic-gate 			continue;
7970Sstevel@tonic-gate 		if (fcp->f_uid != basp->f_uid)
7980Sstevel@tonic-gate 			continue;
7990Sstevel@tonic-gate 		if (fcp->f_gid != basp->f_gid)
8000Sstevel@tonic-gate 			continue;
8010Sstevel@tonic-gate 
8020Sstevel@tonic-gate 		if (opt_debug & DBG_ANAL)
8030Sstevel@tonic-gate 			fprintf(stderr, "ANAL: FIND RENAME %s and %s\n",
8040Sstevel@tonic-gate 				fp->f_fullname, lp->f_fullname);
8050Sstevel@tonic-gate 
8060Sstevel@tonic-gate 		return (lp);
8070Sstevel@tonic-gate 	}
8080Sstevel@tonic-gate 
8090Sstevel@tonic-gate 	return (0);
8100Sstevel@tonic-gate }
8110Sstevel@tonic-gate 
8120Sstevel@tonic-gate /*
8130Sstevel@tonic-gate  * routine:
8140Sstevel@tonic-gate  *	has_other_links
8150Sstevel@tonic-gate  *
8160Sstevel@tonic-gate  * purpose:
8170Sstevel@tonic-gate  *	to determine whether or not there is more that one link to a
8180Sstevel@tonic-gate  *	particular file.  We are willing to delete a link to a file
8190Sstevel@tonic-gate  *	that has changed if we will still have other links to it.
8200Sstevel@tonic-gate  *	The trick here is that we only care about links under our
8210Sstevel@tonic-gate  *	dominion.
8220Sstevel@tonic-gate  *
8230Sstevel@tonic-gate  * parameters:
8240Sstevel@tonic-gate  *	file pointer to node we are interested in
8250Sstevel@tonic-gate  *	which side we are looking to additional links on
8260Sstevel@tonic-gate  *
8270Sstevel@tonic-gate  * returns:
8280Sstevel@tonic-gate  *	TRUE if there are multiple links
8290Sstevel@tonic-gate  *	FALSE if this is the only one we know of
8300Sstevel@tonic-gate  */
8310Sstevel@tonic-gate bool_t
has_other_links(struct file * fp,side_t srcdst)8320Sstevel@tonic-gate has_other_links(struct file *fp, side_t srcdst)
8330Sstevel@tonic-gate {	struct file *lp;
8340Sstevel@tonic-gate 	struct fileinfo *fip, *lip;
8350Sstevel@tonic-gate 
8360Sstevel@tonic-gate 	fip = &fp->f_info[srcdst];
8370Sstevel@tonic-gate 
8380Sstevel@tonic-gate 	/* if the link count is one, there couldn't be others	*/
8390Sstevel@tonic-gate 	if (fip->f_nlink < 2)
8400Sstevel@tonic-gate 		return (FALSE);
8410Sstevel@tonic-gate 
8420Sstevel@tonic-gate 	/* look for any other files for the same inode		*/
8430Sstevel@tonic-gate 	for (lp = changes; lp; lp = lp->f_rnext) {
8440Sstevel@tonic-gate 		/* finding the same node doesn't count	*/
8450Sstevel@tonic-gate 		if (fp == lp)
8460Sstevel@tonic-gate 			continue;
8470Sstevel@tonic-gate 
8480Sstevel@tonic-gate 		lip = &lp->f_info[srcdst];
8490Sstevel@tonic-gate 
8500Sstevel@tonic-gate 		/*
8510Sstevel@tonic-gate 		 * file must still exist on this side
8520Sstevel@tonic-gate 		 */
8530Sstevel@tonic-gate 		if (lip->f_mode == 0)
8540Sstevel@tonic-gate 			continue;
8550Sstevel@tonic-gate 
8560Sstevel@tonic-gate 		/*
8570Sstevel@tonic-gate 		 * if this is indeed a link, then the prospective file on
8580Sstevel@tonic-gate 		 * the changed side will have the same dev/inum as the file
8590Sstevel@tonic-gate 		 * we are looking for
8600Sstevel@tonic-gate 		 */
8610Sstevel@tonic-gate 		if (lip->f_d_maj != fip->f_d_maj)
8620Sstevel@tonic-gate 			continue;
8630Sstevel@tonic-gate 		if (lip->f_d_min != fip->f_d_min)
8640Sstevel@tonic-gate 			continue;
8650Sstevel@tonic-gate 		if (lip->f_ino != fip->f_ino)
8660Sstevel@tonic-gate 			continue;
8670Sstevel@tonic-gate 
8680Sstevel@tonic-gate 		/*
8690Sstevel@tonic-gate 		 * we have found at least one other link
8700Sstevel@tonic-gate 		 */
8710Sstevel@tonic-gate 		return (TRUE);
8720Sstevel@tonic-gate 	}
8730Sstevel@tonic-gate 
8740Sstevel@tonic-gate 	return (FALSE);
8750Sstevel@tonic-gate }
8760Sstevel@tonic-gate 
8770Sstevel@tonic-gate /*
8780Sstevel@tonic-gate  * routine:
8790Sstevel@tonic-gate  *	link_update
8800Sstevel@tonic-gate  *
8810Sstevel@tonic-gate  * purpose:
8820Sstevel@tonic-gate  *	to propoagate a stat change to all other file nodes that
8830Sstevel@tonic-gate  *	correspond to the same I-node on the changed side
8840Sstevel@tonic-gate  *
8850Sstevel@tonic-gate  * parameters:
8860Sstevel@tonic-gate  *	file pointer for the updated file
8870Sstevel@tonic-gate  *	which side was changed
8880Sstevel@tonic-gate  *
8890Sstevel@tonic-gate  * returns:
8900Sstevel@tonic-gate  *	void
8910Sstevel@tonic-gate  *
8920Sstevel@tonic-gate  * notes:
8930Sstevel@tonic-gate  *	if we have copied onto a file, we have copied onto all
8940Sstevel@tonic-gate  *	of its links, but since we do all stats before we do any
8950Sstevel@tonic-gate  *	copies, the stat information recently collected for links
8960Sstevel@tonic-gate  *	is no longer up-to-date, and this would result in incorrect
8970Sstevel@tonic-gate  *	reconciliation (redundant copies).
8980Sstevel@tonic-gate  *
8990Sstevel@tonic-gate  *	There is an assumption here that all links to a changed
9000Sstevel@tonic-gate  *	file will be in the change list.  This is true for almost
9010Sstevel@tonic-gate  *	all cases not involving restriction.  If we do fail to
9020Sstevel@tonic-gate  *	update the baseline for a file that was off the change list,
9030Sstevel@tonic-gate  *	the worst that is likely to happen is that we will think
9040Sstevel@tonic-gate  *	it changed later (but will almost surely find that both
9050Sstevel@tonic-gate  *	copies agree).
9060Sstevel@tonic-gate  */
9070Sstevel@tonic-gate void
link_update(struct file * fp,side_t which)9080Sstevel@tonic-gate link_update(struct file *fp, side_t which)
9090Sstevel@tonic-gate {	struct file *lp;
9100Sstevel@tonic-gate 
9110Sstevel@tonic-gate 	for (lp = changes; lp; lp = lp->f_rnext) {
9120Sstevel@tonic-gate 		/* finding the current entry doesn't count	*/
9130Sstevel@tonic-gate 		if (lp == fp)
9140Sstevel@tonic-gate 			continue;
9150Sstevel@tonic-gate 
9160Sstevel@tonic-gate 		/* look for same i#, maj, min on changed side	*/
9170Sstevel@tonic-gate 		if (lp->f_info[which].f_ino != fp->f_info[which].f_ino)
9180Sstevel@tonic-gate 			continue;
9190Sstevel@tonic-gate 		if (lp->f_info[which].f_d_maj != fp->f_info[which].f_d_maj)
9200Sstevel@tonic-gate 			continue;
9210Sstevel@tonic-gate 		if (lp->f_info[which].f_d_min != fp->f_info[which].f_d_min)
9220Sstevel@tonic-gate 			continue;
9230Sstevel@tonic-gate 
9240Sstevel@tonic-gate 		/*
9250Sstevel@tonic-gate 		 * this appears to be another link to the same file
9260Sstevel@tonic-gate 		 * so the updated stat information for one must be
9270Sstevel@tonic-gate 		 * correct for the other.
9280Sstevel@tonic-gate 		 */
9290Sstevel@tonic-gate 		lp->f_info[which].f_type	= fp->f_info[which].f_type;
9300Sstevel@tonic-gate 		lp->f_info[which].f_size	= fp->f_info[which].f_size;
9310Sstevel@tonic-gate 		lp->f_info[which].f_mode	= fp->f_info[which].f_mode;
9320Sstevel@tonic-gate 		lp->f_info[which].f_uid		= fp->f_info[which].f_uid;
9330Sstevel@tonic-gate 		lp->f_info[which].f_gid		= fp->f_info[which].f_gid;
9340Sstevel@tonic-gate 		lp->f_info[which].f_modtime	= fp->f_info[which].f_modtime;
9350Sstevel@tonic-gate 		lp->f_info[which].f_modns	= fp->f_info[which].f_modns;
9360Sstevel@tonic-gate 		lp->f_info[which].f_nlink	= fp->f_info[which].f_nlink;
9370Sstevel@tonic-gate 		lp->f_info[which].f_rd_maj	= fp->f_info[which].f_rd_maj;
9380Sstevel@tonic-gate 		lp->f_info[which].f_rd_min	= fp->f_info[which].f_rd_min;
9390Sstevel@tonic-gate 
9400Sstevel@tonic-gate 		if (opt_debug & DBG_STAT)
9410Sstevel@tonic-gate 			fprintf(stderr,
9420Sstevel@tonic-gate 				"STAT: UPDATE LINK, file=%s, mod=%08lx.%08lx\n",
9430Sstevel@tonic-gate 				lp->f_name, lp->f_info[which].f_modtime,
9440Sstevel@tonic-gate 				lp->f_info[which].f_modns);
9450Sstevel@tonic-gate 	}
9460Sstevel@tonic-gate }
9470Sstevel@tonic-gate 
9480Sstevel@tonic-gate /*
9490Sstevel@tonic-gate  * routine:
9500Sstevel@tonic-gate  *	queue_file
9510Sstevel@tonic-gate  *
9520Sstevel@tonic-gate  * purpose:
9530Sstevel@tonic-gate  *	append a file to the list of needed reconciliations
9540Sstevel@tonic-gate  *
9550Sstevel@tonic-gate  * parameters:
9560Sstevel@tonic-gate  *	pointer to file
9570Sstevel@tonic-gate  *
9580Sstevel@tonic-gate  * notes:
9590Sstevel@tonic-gate  *	when a request is appended to the reconciliation list,
9600Sstevel@tonic-gate  *	we fill in the full name.  We delayed this in hopes that
9610Sstevel@tonic-gate  *	it wouldn't be necessary (saving cycles and memory)
9620Sstevel@tonic-gate  *
9630Sstevel@tonic-gate  *	There is some funny business with modification times.
9640Sstevel@tonic-gate  *	In general, we queue files in order of the latest modification
9650Sstevel@tonic-gate  *	time so that propagations preserve relative ordering.  There
9660Sstevel@tonic-gate  *	are, however, a few important exceptions:
9670Sstevel@tonic-gate  *	    1.	all directory creations happen at time zero,
9680Sstevel@tonic-gate  *		so that they are created before any files can
9690Sstevel@tonic-gate  *		be added to them.
9700Sstevel@tonic-gate  *	    2.	all directory deletions happen at time infinity-depth,
9710Sstevel@tonic-gate  *		so that everything else can be removed before the
9720Sstevel@tonic-gate  *		directories themselves are removed.
9730Sstevel@tonic-gate  *	    3.	all file deletions happen at time infinity-depth
9740Sstevel@tonic-gate  *		so that (in renames) the links will preceed the unlinks.
9750Sstevel@tonic-gate  */
9760Sstevel@tonic-gate static void
queue_file(struct file * fp)9770Sstevel@tonic-gate queue_file(struct file *fp)
9780Sstevel@tonic-gate {	struct file **pp, *np;
9790Sstevel@tonic-gate 
9800Sstevel@tonic-gate #define	TIME_ZERO	0L		/* the earliest possible time	*/
9810Sstevel@tonic-gate #define	TIME_LONG	0x7FFFFFFF	/* the latest possible time	*/
9820Sstevel@tonic-gate 
9830Sstevel@tonic-gate 	/*
9840Sstevel@tonic-gate 	 * figure out the modification time for sequencing purposes
9850Sstevel@tonic-gate 	 */
9860Sstevel@tonic-gate 	if ((fp->f_srcdiffs|fp->f_dstdiffs) & D_DELETE) {
9870Sstevel@tonic-gate 		/*
9880Sstevel@tonic-gate 		 * deletions are performed last, and depth first
9890Sstevel@tonic-gate 		 */
9900Sstevel@tonic-gate 		fp->f_modtime = TIME_LONG - fp->f_depth;
9910Sstevel@tonic-gate 	} else if (fp->f_info[OPT_SRC].f_type != S_IFDIR &&
9920Sstevel@tonic-gate 	    fp->f_info[OPT_DST].f_type != S_IFDIR) {
9930Sstevel@tonic-gate 		/*
9940Sstevel@tonic-gate 		 * for most files we use the latest mod time
9950Sstevel@tonic-gate 		 */
9960Sstevel@tonic-gate 		fp->f_modtime = fp->f_info[OPT_SRC].f_modtime;
9970Sstevel@tonic-gate 		fp->f_modns   = fp->f_info[OPT_SRC].f_modns;
9980Sstevel@tonic-gate 		if (fp->f_modtime < fp->f_info[OPT_DST].f_modtime) {
9990Sstevel@tonic-gate 			fp->f_modtime = fp->f_info[OPT_DST].f_modtime;
10000Sstevel@tonic-gate 			fp->f_modns   = fp->f_info[OPT_DST].f_modns;
10010Sstevel@tonic-gate 		}
10020Sstevel@tonic-gate 	} else {
10030Sstevel@tonic-gate 		/*
10040Sstevel@tonic-gate 		 * new directory creations need to happen before anything
10050Sstevel@tonic-gate 		 * else and are automatically sequenced in traversal order
10060Sstevel@tonic-gate 		 */
10070Sstevel@tonic-gate 		fp->f_modtime = TIME_ZERO;
10080Sstevel@tonic-gate 	}
10090Sstevel@tonic-gate 
10100Sstevel@tonic-gate 	/*
10110Sstevel@tonic-gate 	 * insertion is time ordered, and for equal times,
10120Sstevel@tonic-gate 	 * insertions is in (pre-order) traversal order
10130Sstevel@tonic-gate 	 */
10140Sstevel@tonic-gate 	for (pp = &changes; (np = *pp) != 0; pp = &np->f_rnext) {
10150Sstevel@tonic-gate 		if (fp->f_modtime > np->f_modtime)
10160Sstevel@tonic-gate 			continue;
10170Sstevel@tonic-gate 		if (fp->f_modtime < np->f_modtime)
10180Sstevel@tonic-gate 			break;
10190Sstevel@tonic-gate 		if (fp->f_modns < np->f_modns)
10200Sstevel@tonic-gate 			break;
10210Sstevel@tonic-gate 	}
10220Sstevel@tonic-gate 
10230Sstevel@tonic-gate 	fp->f_fullname = strdup(get_name(fp));
10240Sstevel@tonic-gate 	fp->f_rnext = np;
10250Sstevel@tonic-gate 	*pp = fp;
10260Sstevel@tonic-gate }
10270Sstevel@tonic-gate 
10280Sstevel@tonic-gate 
10290Sstevel@tonic-gate /*
10300Sstevel@tonic-gate  * routines:
10310Sstevel@tonic-gate  *	push_name/pop_name/get_name
10320Sstevel@tonic-gate  *
10330Sstevel@tonic-gate  * purpose:
10340Sstevel@tonic-gate  *	maintain a name stack so we can form name of a particular file
10350Sstevel@tonic-gate  *	as the concatenation of all of the names between it and the
10360Sstevel@tonic-gate  *	(know to be fully qualified) base directory.
10370Sstevel@tonic-gate  *
10380Sstevel@tonic-gate  * notes:
10390Sstevel@tonic-gate  *	we go to this trouble because most files never change and
10400Sstevel@tonic-gate  *	so we don't need to associate full names with every one.
10410Sstevel@tonic-gate  *	This stack is maintained during analysis, and if we decide
10420Sstevel@tonic-gate  *	to add a file to the reconciliation list, we can use the
10430Sstevel@tonic-gate  *	stack to generate a fully qualified name at that time.
10440Sstevel@tonic-gate  *
10450Sstevel@tonic-gate  *	we compress out '/./' when we return a name.  Given that the
10460Sstevel@tonic-gate  *	stack was built by a tree walk, the only place a /./ should
10470Sstevel@tonic-gate  *	appear is at the first level after the base ... but there
10480Sstevel@tonic-gate  *	are legitimate ways for them to appear there.
10490Sstevel@tonic-gate  *
10500Sstevel@tonic-gate  *	these names can get deep, so we dynamically size our name buffer
10510Sstevel@tonic-gate  */
10520Sstevel@tonic-gate static const char *namestack[ MAX_DEPTH + 1 ];
10530Sstevel@tonic-gate static int namedepth = 0;
10540Sstevel@tonic-gate static int namelen = 0;
10550Sstevel@tonic-gate 
10560Sstevel@tonic-gate void
push_name(const char * name)10570Sstevel@tonic-gate push_name(const char *name)
10580Sstevel@tonic-gate {
10590Sstevel@tonic-gate 	namestack[ namedepth++ ] = name;
10600Sstevel@tonic-gate 	namelen += 2 + strlen(name);
10610Sstevel@tonic-gate 
10620Sstevel@tonic-gate 	/* make sure we don't overflow our name stack	*/
10630Sstevel@tonic-gate 	if (namedepth >= MAX_DEPTH) {
10640Sstevel@tonic-gate 		fprintf(stderr, gettext(ERR_deep), name);
10650Sstevel@tonic-gate 		exit(ERR_OTHER);
10660Sstevel@tonic-gate 	}
10670Sstevel@tonic-gate }
10680Sstevel@tonic-gate 
10690Sstevel@tonic-gate void
pop_name(void)10700Sstevel@tonic-gate pop_name(void)
10710Sstevel@tonic-gate {
10720Sstevel@tonic-gate 	namelen -= 2 + strlen(namestack[--namedepth]);
10730Sstevel@tonic-gate 	namestack[ namedepth ] = 0;
10740Sstevel@tonic-gate 
10750Sstevel@tonic-gate #ifdef	DBG_ERRORS
10760Sstevel@tonic-gate 	/* just a little sanity check here	*/
10770Sstevel@tonic-gate 	if (namedepth <= 0) {
10780Sstevel@tonic-gate 		if (namedepth < 0) {
10790Sstevel@tonic-gate 			fprintf(stderr, "ASSERTION FAILURE: namedepth < 0\n");
10800Sstevel@tonic-gate 			exit(ERR_OTHER);
10810Sstevel@tonic-gate 		} else if (namelen != 0) {
10820Sstevel@tonic-gate 			fprintf(stderr, "ASSERTION FAILURE: namelen != 0\n");
10830Sstevel@tonic-gate 			exit(ERR_OTHER);
10840Sstevel@tonic-gate 		}
10850Sstevel@tonic-gate 	}
10860Sstevel@tonic-gate #endif
10870Sstevel@tonic-gate }
10880Sstevel@tonic-gate 
10890Sstevel@tonic-gate char
get_name(struct file * fp)10900Sstevel@tonic-gate *get_name(struct file *fp)
10910Sstevel@tonic-gate {	int i;
10920Sstevel@tonic-gate 	static char *namebuf = 0;
10930Sstevel@tonic-gate 	static int buflen = 0;
10940Sstevel@tonic-gate 
10950Sstevel@tonic-gate 	/* make sure we have an adequate buffer	*/
10960Sstevel@tonic-gate 	i = namelen + 1 + strlen(fp->f_name);
10970Sstevel@tonic-gate 	if (buflen < i) {
10980Sstevel@tonic-gate 		for (buflen = MAX_PATH; buflen < i; buflen += MAX_NAME);
10990Sstevel@tonic-gate 		namebuf = (char *) realloc(namebuf, buflen);
11000Sstevel@tonic-gate 	}
11010Sstevel@tonic-gate 
11020Sstevel@tonic-gate 	/* assemble the name	*/
11030Sstevel@tonic-gate 	namebuf[0] = 0;
11040Sstevel@tonic-gate 	for (i = 0; i < namedepth; i++) {
11050Sstevel@tonic-gate 		if (strcmp(namestack[i], ".")) {
11060Sstevel@tonic-gate 			strcat(namebuf, namestack[i]);
11070Sstevel@tonic-gate 			strcat(namebuf, "/");
11080Sstevel@tonic-gate 		}
11090Sstevel@tonic-gate 	}
11100Sstevel@tonic-gate 
11110Sstevel@tonic-gate 	strcat(namebuf, fp->f_name);
11120Sstevel@tonic-gate 
11130Sstevel@tonic-gate 	return (namebuf);
11140Sstevel@tonic-gate }
1115