xref: /illumos-gate/usr/src/cmd/filesync/anal.c (revision 7c478bd9)
1*7c478bd9Sstevel@tonic-gate /*
2*7c478bd9Sstevel@tonic-gate  * CDDL HEADER START
3*7c478bd9Sstevel@tonic-gate  *
4*7c478bd9Sstevel@tonic-gate  * The contents of this file are subject to the terms of the
5*7c478bd9Sstevel@tonic-gate  * Common Development and Distribution License, Version 1.0 only
6*7c478bd9Sstevel@tonic-gate  * (the "License").  You may not use this file except in compliance
7*7c478bd9Sstevel@tonic-gate  * with the License.
8*7c478bd9Sstevel@tonic-gate  *
9*7c478bd9Sstevel@tonic-gate  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10*7c478bd9Sstevel@tonic-gate  * or http://www.opensolaris.org/os/licensing.
11*7c478bd9Sstevel@tonic-gate  * See the License for the specific language governing permissions
12*7c478bd9Sstevel@tonic-gate  * and limitations under the License.
13*7c478bd9Sstevel@tonic-gate  *
14*7c478bd9Sstevel@tonic-gate  * When distributing Covered Code, include this CDDL HEADER in each
15*7c478bd9Sstevel@tonic-gate  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16*7c478bd9Sstevel@tonic-gate  * If applicable, add the following below this CDDL HEADER, with the
17*7c478bd9Sstevel@tonic-gate  * fields enclosed by brackets "[]" replaced with your own identifying
18*7c478bd9Sstevel@tonic-gate  * information: Portions Copyright [yyyy] [name of copyright owner]
19*7c478bd9Sstevel@tonic-gate  *
20*7c478bd9Sstevel@tonic-gate  * CDDL HEADER END
21*7c478bd9Sstevel@tonic-gate  */
22*7c478bd9Sstevel@tonic-gate /*
23*7c478bd9Sstevel@tonic-gate  * Copyright (c) 1995 Sun Microsystems, Inc.  All Rights Reserved
24*7c478bd9Sstevel@tonic-gate  *
25*7c478bd9Sstevel@tonic-gate  * module:
26*7c478bd9Sstevel@tonic-gate  *	anal.c
27*7c478bd9Sstevel@tonic-gate  *
28*7c478bd9Sstevel@tonic-gate  * purpose:
29*7c478bd9Sstevel@tonic-gate  *	routines to analyze the file trees and figure out what has changed
30*7c478bd9Sstevel@tonic-gate  *	and queue files for reconciliation.  It also contains tree enumeration
31*7c478bd9Sstevel@tonic-gate  *	routines to for other purposes (pruning and link location).
32*7c478bd9Sstevel@tonic-gate  *
33*7c478bd9Sstevel@tonic-gate  * contents:
34*7c478bd9Sstevel@tonic-gate  *
35*7c478bd9Sstevel@tonic-gate  *  change analysis:
36*7c478bd9Sstevel@tonic-gate  *	analyze .... (top level) analyze all files in the tree for changes
37*7c478bd9Sstevel@tonic-gate  *	summary .... print out change/reconciliation statistics for each base
38*7c478bd9Sstevel@tonic-gate  *	check_file . (static) look for changes and queue file for reconciliation
39*7c478bd9Sstevel@tonic-gate  *	check_changes (static) figure out if a particular file has changed
40*7c478bd9Sstevel@tonic-gate  *	queue_file . (static) add a file to the reconciliation list
41*7c478bd9Sstevel@tonic-gate  *
42*7c478bd9Sstevel@tonic-gate  *  other tree enumeration functions:
43*7c478bd9Sstevel@tonic-gate  *	prune_file . (static) recursive descent and actual pruning
44*7c478bd9Sstevel@tonic-gate  *	prune ...... (top level) initiate pruning analysis for nonexistant files
45*7c478bd9Sstevel@tonic-gate  *	find_link .. look for other files to which a file may be a link
46*7c478bd9Sstevel@tonic-gate  *	link_update. propagate changed stat info to all other links
47*7c478bd9Sstevel@tonic-gate  *	same_name .. (static) figure out if two nodes describe same file
48*7c478bd9Sstevel@tonic-gate  *
49*7c478bd9Sstevel@tonic-gate  *  misc:
50*7c478bd9Sstevel@tonic-gate  *	push_name .. maintain a running full pathname as we descend
51*7c478bd9Sstevel@tonic-gate  *	pop_name ... maintain a running full pathname as we pop back
52*7c478bd9Sstevel@tonic-gate  *	get_name ... return full pathname for the current file
53*7c478bd9Sstevel@tonic-gate  *
54*7c478bd9Sstevel@tonic-gate  * notes:
55*7c478bd9Sstevel@tonic-gate  *	analysis is limited to files that were evaluated in the previous
56*7c478bd9Sstevel@tonic-gate  *	pass ... since we don't have complete information about files that
57*7c478bd9Sstevel@tonic-gate  *	were not evaluated in the previous pass.
58*7c478bd9Sstevel@tonic-gate  */
59*7c478bd9Sstevel@tonic-gate #ident	"%W%	%E% SMI"
60*7c478bd9Sstevel@tonic-gate 
61*7c478bd9Sstevel@tonic-gate #include <stdio.h>
62*7c478bd9Sstevel@tonic-gate #include <stdlib.h>
63*7c478bd9Sstevel@tonic-gate #include <strings.h>
64*7c478bd9Sstevel@tonic-gate 
65*7c478bd9Sstevel@tonic-gate #include "messages.h"
66*7c478bd9Sstevel@tonic-gate #include "filesync.h"
67*7c478bd9Sstevel@tonic-gate #include "database.h"
68*7c478bd9Sstevel@tonic-gate #include "debug.h"
69*7c478bd9Sstevel@tonic-gate 
70*7c478bd9Sstevel@tonic-gate /*
71*7c478bd9Sstevel@tonic-gate  * routines:
72*7c478bd9Sstevel@tonic-gate  */
73*7c478bd9Sstevel@tonic-gate void push_name(const char *);
74*7c478bd9Sstevel@tonic-gate void pop_name();
75*7c478bd9Sstevel@tonic-gate char *get_name(struct file *);
76*7c478bd9Sstevel@tonic-gate static errmask_t check_file(struct file *fp);
77*7c478bd9Sstevel@tonic-gate static diffmask_t check_changes(struct file *fp, int first, int second);
78*7c478bd9Sstevel@tonic-gate static int prune_file(struct file *fp);
79*7c478bd9Sstevel@tonic-gate static void queue_file(struct file *fp);
80*7c478bd9Sstevel@tonic-gate 
81*7c478bd9Sstevel@tonic-gate /*
82*7c478bd9Sstevel@tonic-gate  * globals
83*7c478bd9Sstevel@tonic-gate  */
84*7c478bd9Sstevel@tonic-gate static struct file *changes;	/* list of files to be reconciled	*/
85*7c478bd9Sstevel@tonic-gate 
86*7c478bd9Sstevel@tonic-gate static long total_files;	/* total number of files being considered  */
87*7c478bd9Sstevel@tonic-gate static long est_deletes;	/* estimated number of files to be deleted */
88*7c478bd9Sstevel@tonic-gate static long est_rmdirs;		/* est rmdirs of non-empty directories	   */
89*7c478bd9Sstevel@tonic-gate 
90*7c478bd9Sstevel@tonic-gate int inum_changes;		/* LISTed directories whose I#s changed	   */
91*7c478bd9Sstevel@tonic-gate 
92*7c478bd9Sstevel@tonic-gate /*
93*7c478bd9Sstevel@tonic-gate  * routine:
94*7c478bd9Sstevel@tonic-gate  *	analyze
95*7c478bd9Sstevel@tonic-gate  *
96*7c478bd9Sstevel@tonic-gate  * purpose:
97*7c478bd9Sstevel@tonic-gate  *	top level routine for the analysis/reconciliation process
98*7c478bd9Sstevel@tonic-gate  *
99*7c478bd9Sstevel@tonic-gate  * parameters:
100*7c478bd9Sstevel@tonic-gate  *	none
101*7c478bd9Sstevel@tonic-gate  *
102*7c478bd9Sstevel@tonic-gate  * returns:
103*7c478bd9Sstevel@tonic-gate  *	error mask
104*7c478bd9Sstevel@tonic-gate  *
105*7c478bd9Sstevel@tonic-gate  * notes:
106*7c478bd9Sstevel@tonic-gate  *	a critical side effect of this routine is the creation of
107*7c478bd9Sstevel@tonic-gate  *	the reconciliation list, an ordered list of files that
108*7c478bd9Sstevel@tonic-gate  *	needed to be processed in the subsequent reconciliation pass
109*7c478bd9Sstevel@tonic-gate  */
110*7c478bd9Sstevel@tonic-gate errmask_t
111*7c478bd9Sstevel@tonic-gate analyze()
112*7c478bd9Sstevel@tonic-gate {	struct base *bp;
113*7c478bd9Sstevel@tonic-gate 	struct file *fp;
114*7c478bd9Sstevel@tonic-gate 	int errs = 0;
115*7c478bd9Sstevel@tonic-gate 	int err;
116*7c478bd9Sstevel@tonic-gate 	int percentage;
117*7c478bd9Sstevel@tonic-gate 	bool_t aborted = FALSE;
118*7c478bd9Sstevel@tonic-gate 	char msgbuf[MAX_LINE];
119*7c478bd9Sstevel@tonic-gate 
120*7c478bd9Sstevel@tonic-gate 	/*
121*7c478bd9Sstevel@tonic-gate 	 * run through all bases and directories looking for files
122*7c478bd9Sstevel@tonic-gate 	 * that have been renamed.  This must be done before the
123*7c478bd9Sstevel@tonic-gate 	 * difference analysis because a directory rename can introduce
124*7c478bd9Sstevel@tonic-gate 	 * radical restructuring into a name-based tree.
125*7c478bd9Sstevel@tonic-gate 	 */
126*7c478bd9Sstevel@tonic-gate 	for (bp = bases; bp; bp = bp->b_next) {
127*7c478bd9Sstevel@tonic-gate 		for (fp = bp->b_files; fp; fp = fp->f_next)
128*7c478bd9Sstevel@tonic-gate 			if (fp->f_flags & F_EVALUATE)
129*7c478bd9Sstevel@tonic-gate 				errs |= find_renames(fp);
130*7c478bd9Sstevel@tonic-gate 	}
131*7c478bd9Sstevel@tonic-gate 
132*7c478bd9Sstevel@tonic-gate 	/*
133*7c478bd9Sstevel@tonic-gate 	 * run through all bases and files looking for candidates
134*7c478bd9Sstevel@tonic-gate 	 * note, however that we only descend into trees that have
135*7c478bd9Sstevel@tonic-gate 	 * the evaluate flag turned on.  As a result of new rules or
136*7c478bd9Sstevel@tonic-gate 	 * restriction arguments, we may be deliberatly ignoring
137*7c478bd9Sstevel@tonic-gate 	 * large amounts of the baseline.   This means we won't do
138*7c478bd9Sstevel@tonic-gate 	 * any stats to update the information in those nodes, and
139*7c478bd9Sstevel@tonic-gate 	 * they will be written back just as they were.
140*7c478bd9Sstevel@tonic-gate 	 *
141*7c478bd9Sstevel@tonic-gate 	 * note that there is code to prune out baseline nodes for
142*7c478bd9Sstevel@tonic-gate 	 * files that no longer exist, but that code is in reconcile
143*7c478bd9Sstevel@tonic-gate 	 * and will never get a chance to run on nodes that aren't
144*7c478bd9Sstevel@tonic-gate 	 * analyzed.
145*7c478bd9Sstevel@tonic-gate 	 *
146*7c478bd9Sstevel@tonic-gate 	 * we also want to run though all nodes with STAT errors
147*7c478bd9Sstevel@tonic-gate 	 * so that we can put them on the reconciliation list.
148*7c478bd9Sstevel@tonic-gate 	 */
149*7c478bd9Sstevel@tonic-gate 	for (bp = bases; bp; bp = bp->b_next) {
150*7c478bd9Sstevel@tonic-gate 		for (fp = bp->b_files; fp; fp = fp->f_next)
151*7c478bd9Sstevel@tonic-gate 			if (fp->f_flags & (F_EVALUATE|F_STAT_ERROR))
152*7c478bd9Sstevel@tonic-gate 				errs |= check_file(fp);
153*7c478bd9Sstevel@tonic-gate 	}
154*7c478bd9Sstevel@tonic-gate 
155*7c478bd9Sstevel@tonic-gate 	/*
156*7c478bd9Sstevel@tonic-gate 	 * my greatest fear is that someday, somehow, by messing with
157*7c478bd9Sstevel@tonic-gate 	 * variables or baselines or who-knows-what, that someone will
158*7c478bd9Sstevel@tonic-gate 	 * run a reconciliation against a large tree that doesn't correspond
159*7c478bd9Sstevel@tonic-gate 	 * to the baseline, and I will infer that a bazillion files have
160*7c478bd9Sstevel@tonic-gate 	 * been deleted and will propagate the slaughter before anyone
161*7c478bd9Sstevel@tonic-gate 	 * can say somebody stop that maniac.
162*7c478bd9Sstevel@tonic-gate 	 *
163*7c478bd9Sstevel@tonic-gate 	 * in order to prevent such a possibility, we have a few different
164*7c478bd9Sstevel@tonic-gate 	 * sanity checks.  There is, of course, a tradeoff here between
165*7c478bd9Sstevel@tonic-gate 	 * danger and irritation.  The current set of heuristics for whether
166*7c478bd9Sstevel@tonic-gate 	 * or not to generate a warning are (any of)
167*7c478bd9Sstevel@tonic-gate 	 *
168*7c478bd9Sstevel@tonic-gate 	 *	at least CONFIRM_MIN files have been deleted AND
169*7c478bd9Sstevel@tonic-gate 	 *	CONFIRM_PCT of all files have been deleted
170*7c478bd9Sstevel@tonic-gate 	 *
171*7c478bd9Sstevel@tonic-gate 	 *	the inode number on a LISTed directory has changed
172*7c478bd9Sstevel@tonic-gate 	 *
173*7c478bd9Sstevel@tonic-gate 	 *	a non-empty directory has been deleted.
174*7c478bd9Sstevel@tonic-gate 	 */
175*7c478bd9Sstevel@tonic-gate 	msgbuf[0] = 0;
176*7c478bd9Sstevel@tonic-gate 
177*7c478bd9Sstevel@tonic-gate 	percentage = (est_deletes * 100) / (total_files ? total_files : 1);
178*7c478bd9Sstevel@tonic-gate 	if (est_deletes >= CONFIRM_MIN && percentage >= CONFIRM_PCT)
179*7c478bd9Sstevel@tonic-gate 		sprintf(msgbuf, gettext(WARN_deletes), est_deletes);
180*7c478bd9Sstevel@tonic-gate 	else if (inum_changes > 0)
181*7c478bd9Sstevel@tonic-gate 		sprintf(msgbuf, gettext(WARN_ichange), inum_changes);
182*7c478bd9Sstevel@tonic-gate 	else if (est_rmdirs)
183*7c478bd9Sstevel@tonic-gate 		sprintf(msgbuf, gettext(WARN_rmdirs), est_rmdirs);
184*7c478bd9Sstevel@tonic-gate 
185*7c478bd9Sstevel@tonic-gate 	if (msgbuf[0])
186*7c478bd9Sstevel@tonic-gate 		confirm(msgbuf);
187*7c478bd9Sstevel@tonic-gate 
188*7c478bd9Sstevel@tonic-gate 	/*
189*7c478bd9Sstevel@tonic-gate 	 * TRICK:
190*7c478bd9Sstevel@tonic-gate 	 *	the change list contains both files that have changed
191*7c478bd9Sstevel@tonic-gate 	 *	(and probably warrant reconciliation) and files that
192*7c478bd9Sstevel@tonic-gate 	 *	we couldn't get up-to-date stat information on.  The
193*7c478bd9Sstevel@tonic-gate 	 *	latter files should just be flagged as being in conflict
194*7c478bd9Sstevel@tonic-gate 	 *	so they can be reported in the summary.  The same is
195*7c478bd9Sstevel@tonic-gate 	 *	true of all subsequent files if we abort reconciliation.
196*7c478bd9Sstevel@tonic-gate 	 */
197*7c478bd9Sstevel@tonic-gate 	for (fp = changes; fp; fp = fp->f_rnext)
198*7c478bd9Sstevel@tonic-gate 		if (aborted || (fp->f_flags & F_STAT_ERROR)) {
199*7c478bd9Sstevel@tonic-gate 			fp->f_flags |= F_CONFLICT;
200*7c478bd9Sstevel@tonic-gate 			/* if it isn't in the baseline yet, don't add it */
201*7c478bd9Sstevel@tonic-gate 			if ((fp->f_flags & F_IN_BASELINE) == 0)
202*7c478bd9Sstevel@tonic-gate 				fp->f_flags |= F_REMOVE;
203*7c478bd9Sstevel@tonic-gate 			fp->f_problem = aborted ? PROB_aborted : PROB_restat;
204*7c478bd9Sstevel@tonic-gate 			(fp->f_base)->b_unresolved++;
205*7c478bd9Sstevel@tonic-gate 			errs |= ERR_UNRESOLVED;
206*7c478bd9Sstevel@tonic-gate 			if (opt_verbose)
207*7c478bd9Sstevel@tonic-gate 				fprintf(stdout,
208*7c478bd9Sstevel@tonic-gate 					gettext(aborted ? V_suppressed
209*7c478bd9Sstevel@tonic-gate 							: V_nostat),
210*7c478bd9Sstevel@tonic-gate 					fp->f_fullname);
211*7c478bd9Sstevel@tonic-gate 		} else {
212*7c478bd9Sstevel@tonic-gate 			err = reconcile(fp);
213*7c478bd9Sstevel@tonic-gate 			errs |= err;
214*7c478bd9Sstevel@tonic-gate 			if (opt_halt && (err & ERR_ABORT)) {
215*7c478bd9Sstevel@tonic-gate 				fprintf(stderr, gettext(ERR_abort_h));
216*7c478bd9Sstevel@tonic-gate 				aborted = TRUE;
217*7c478bd9Sstevel@tonic-gate 			}
218*7c478bd9Sstevel@tonic-gate 		}
219*7c478bd9Sstevel@tonic-gate 
220*7c478bd9Sstevel@tonic-gate 	return (errs);
221*7c478bd9Sstevel@tonic-gate }
222*7c478bd9Sstevel@tonic-gate 
223*7c478bd9Sstevel@tonic-gate /*
224*7c478bd9Sstevel@tonic-gate  * routine:
225*7c478bd9Sstevel@tonic-gate  *	prune_file
226*7c478bd9Sstevel@tonic-gate  *
227*7c478bd9Sstevel@tonic-gate  * purpose:
228*7c478bd9Sstevel@tonic-gate  *	to look for file entries that should be pruned from baseline
229*7c478bd9Sstevel@tonic-gate  *	prune the current file if it needs pruning, and recursively
230*7c478bd9Sstevel@tonic-gate  *	descend if it is a directory.
231*7c478bd9Sstevel@tonic-gate  *
232*7c478bd9Sstevel@tonic-gate  * parameters:
233*7c478bd9Sstevel@tonic-gate  *	pointer to file node
234*7c478bd9Sstevel@tonic-gate  */
235*7c478bd9Sstevel@tonic-gate static int
236*7c478bd9Sstevel@tonic-gate prune_file(struct file *fp)
237*7c478bd9Sstevel@tonic-gate {	struct file *cp;
238*7c478bd9Sstevel@tonic-gate 	int prunes = 0;
239*7c478bd9Sstevel@tonic-gate 
240*7c478bd9Sstevel@tonic-gate 	/* if node hasn't been evaluated, mark it for removal	*/
241*7c478bd9Sstevel@tonic-gate 	if ((fp->f_flags & (F_EVALUATE|F_STAT_ERROR)) == 0) {
242*7c478bd9Sstevel@tonic-gate 		fp->f_flags |= F_REMOVE;
243*7c478bd9Sstevel@tonic-gate 		prunes++;
244*7c478bd9Sstevel@tonic-gate 		if (opt_debug & DBG_ANAL)
245*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, "ANAL: PRUNE %s\n", fp->f_name);
246*7c478bd9Sstevel@tonic-gate 	}
247*7c478bd9Sstevel@tonic-gate 
248*7c478bd9Sstevel@tonic-gate 	/* now check our children				*/
249*7c478bd9Sstevel@tonic-gate 	for (cp = fp->f_files; cp; cp = cp->f_next)
250*7c478bd9Sstevel@tonic-gate 		prunes += prune_file(cp);
251*7c478bd9Sstevel@tonic-gate 
252*7c478bd9Sstevel@tonic-gate 	return (prunes);
253*7c478bd9Sstevel@tonic-gate }
254*7c478bd9Sstevel@tonic-gate 
255*7c478bd9Sstevel@tonic-gate /*
256*7c478bd9Sstevel@tonic-gate  * routine:
257*7c478bd9Sstevel@tonic-gate  *	prune
258*7c478bd9Sstevel@tonic-gate  *
259*7c478bd9Sstevel@tonic-gate  * purpose:
260*7c478bd9Sstevel@tonic-gate  *	to prune the baseline of entries that no longer correspond to
261*7c478bd9Sstevel@tonic-gate  *	existing rules.
262*7c478bd9Sstevel@tonic-gate  *
263*7c478bd9Sstevel@tonic-gate  * notes:
264*7c478bd9Sstevel@tonic-gate  *	This routine just calls prune_file on the top of each base tree.
265*7c478bd9Sstevel@tonic-gate  */
266*7c478bd9Sstevel@tonic-gate int
267*7c478bd9Sstevel@tonic-gate prune()
268*7c478bd9Sstevel@tonic-gate {	struct base *bp;
269*7c478bd9Sstevel@tonic-gate 	struct file *fp;
270*7c478bd9Sstevel@tonic-gate 	int prunes = 0;
271*7c478bd9Sstevel@tonic-gate 
272*7c478bd9Sstevel@tonic-gate 	for (bp = bases; bp; bp = bp->b_next) {
273*7c478bd9Sstevel@tonic-gate 		for (fp = bp->b_files; fp; fp = fp->f_next)
274*7c478bd9Sstevel@tonic-gate 			prunes += prune_file(fp);
275*7c478bd9Sstevel@tonic-gate 
276*7c478bd9Sstevel@tonic-gate 		if ((bp->b_flags & F_EVALUATE) == 0)
277*7c478bd9Sstevel@tonic-gate 			bp->b_flags |= F_REMOVE;
278*7c478bd9Sstevel@tonic-gate 	}
279*7c478bd9Sstevel@tonic-gate 
280*7c478bd9Sstevel@tonic-gate 	return (prunes);
281*7c478bd9Sstevel@tonic-gate }
282*7c478bd9Sstevel@tonic-gate 
283*7c478bd9Sstevel@tonic-gate /*
284*7c478bd9Sstevel@tonic-gate  * routine:
285*7c478bd9Sstevel@tonic-gate  *	summary
286*7c478bd9Sstevel@tonic-gate  *
287*7c478bd9Sstevel@tonic-gate  * purpose:
288*7c478bd9Sstevel@tonic-gate  *	to print out statics and conflict lists
289*7c478bd9Sstevel@tonic-gate  */
290*7c478bd9Sstevel@tonic-gate void
291*7c478bd9Sstevel@tonic-gate summary()
292*7c478bd9Sstevel@tonic-gate {	struct base *bp;
293*7c478bd9Sstevel@tonic-gate 	struct file *fp;
294*7c478bd9Sstevel@tonic-gate 	extern bool_t need_super;
295*7c478bd9Sstevel@tonic-gate 
296*7c478bd9Sstevel@tonic-gate 	(void) fflush(stdout);
297*7c478bd9Sstevel@tonic-gate 
298*7c478bd9Sstevel@tonic-gate 	for (bp = bases; bp; bp = bp->b_next) {
299*7c478bd9Sstevel@tonic-gate 
300*7c478bd9Sstevel@tonic-gate 		/* see if this base was irrelevent	*/
301*7c478bd9Sstevel@tonic-gate 		if ((bp->b_flags & F_EVALUATE) == 0)
302*7c478bd9Sstevel@tonic-gate 			continue;
303*7c478bd9Sstevel@tonic-gate 
304*7c478bd9Sstevel@tonic-gate 		/* print out a summary for this base	*/
305*7c478bd9Sstevel@tonic-gate 		fprintf(stderr, gettext(SUM_hd),
306*7c478bd9Sstevel@tonic-gate 			bp->b_src_spec, bp->b_dst_spec, bp->b_totfiles);
307*7c478bd9Sstevel@tonic-gate 		fprintf(stderr, gettext(SUM_dst),
308*7c478bd9Sstevel@tonic-gate 			bp->b_dst_copies, bp->b_dst_deletes, bp->b_dst_misc);
309*7c478bd9Sstevel@tonic-gate 		fprintf(stderr, gettext(SUM_src),
310*7c478bd9Sstevel@tonic-gate 			bp->b_src_copies, bp->b_src_deletes, bp->b_src_misc);
311*7c478bd9Sstevel@tonic-gate 		if (bp->b_unresolved)
312*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, gettext(SUM_unresolved),
313*7c478bd9Sstevel@tonic-gate 				bp->b_unresolved);
314*7c478bd9Sstevel@tonic-gate 
315*7c478bd9Sstevel@tonic-gate 
316*7c478bd9Sstevel@tonic-gate 		/* print out a list of unreconciled files for this base	*/
317*7c478bd9Sstevel@tonic-gate 		for (fp = changes; fp; fp = fp->f_rnext) {
318*7c478bd9Sstevel@tonic-gate 			if (fp->f_base != bp)
319*7c478bd9Sstevel@tonic-gate 				continue;
320*7c478bd9Sstevel@tonic-gate 			if ((fp->f_flags & F_CONFLICT) == 0)
321*7c478bd9Sstevel@tonic-gate 				continue;
322*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, "\t\t%s (%s)\n", fp->f_fullname,
323*7c478bd9Sstevel@tonic-gate 				fp->f_problem ? fp->f_problem : "???");
324*7c478bd9Sstevel@tonic-gate 		}
325*7c478bd9Sstevel@tonic-gate 
326*7c478bd9Sstevel@tonic-gate 		fprintf(stderr, "\n");
327*7c478bd9Sstevel@tonic-gate 	}
328*7c478bd9Sstevel@tonic-gate 
329*7c478bd9Sstevel@tonic-gate 	if (need_super)
330*7c478bd9Sstevel@tonic-gate 		fprintf(stderr, gettext(WARN_super));
331*7c478bd9Sstevel@tonic-gate }
332*7c478bd9Sstevel@tonic-gate 
333*7c478bd9Sstevel@tonic-gate /*
334*7c478bd9Sstevel@tonic-gate  * routine:
335*7c478bd9Sstevel@tonic-gate  *	check_file
336*7c478bd9Sstevel@tonic-gate  *
337*7c478bd9Sstevel@tonic-gate  * purpose:
338*7c478bd9Sstevel@tonic-gate  *	figure out if a file requires reconciliation and recursively
339*7c478bd9Sstevel@tonic-gate  *	descend into all sub-files and directories
340*7c478bd9Sstevel@tonic-gate  *
341*7c478bd9Sstevel@tonic-gate  * parameters:
342*7c478bd9Sstevel@tonic-gate  *	base pointer
343*7c478bd9Sstevel@tonic-gate  *	file pointer
344*7c478bd9Sstevel@tonic-gate  *
345*7c478bd9Sstevel@tonic-gate  * returns:
346*7c478bd9Sstevel@tonic-gate  *	error mask
347*7c478bd9Sstevel@tonic-gate  *	built up changes needed list
348*7c478bd9Sstevel@tonic-gate  *	updated statistics
349*7c478bd9Sstevel@tonic-gate  *
350*7c478bd9Sstevel@tonic-gate  * notes:
351*7c478bd9Sstevel@tonic-gate  *	this routine builds up a path name as it descends through
352*7c478bd9Sstevel@tonic-gate  *	the tree (see push_name, pop_name, get_name).
353*7c478bd9Sstevel@tonic-gate  */
354*7c478bd9Sstevel@tonic-gate static errmask_t
355*7c478bd9Sstevel@tonic-gate check_file(struct file *fp)
356*7c478bd9Sstevel@tonic-gate {	struct file *cp;
357*7c478bd9Sstevel@tonic-gate 	int errs = 0;
358*7c478bd9Sstevel@tonic-gate 
359*7c478bd9Sstevel@tonic-gate 	if ((fp->f_flags & F_STAT_ERROR) == 0) {
360*7c478bd9Sstevel@tonic-gate 		/* see if the source has changed	*/
361*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_modtime	= fp->f_s_modtime;
362*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_ino	= fp->f_s_inum;
363*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_d_maj	= fp->f_s_maj;
364*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_d_min	= fp->f_s_min;
365*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_nlink	= fp->f_s_nlink;
366*7c478bd9Sstevel@tonic-gate 		fp->f_srcdiffs |= check_changes(fp, OPT_BASE, OPT_SRC);
367*7c478bd9Sstevel@tonic-gate 
368*7c478bd9Sstevel@tonic-gate 		/* see if the destination has changed	*/
369*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_modtime	= fp->f_d_modtime;
370*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_ino    	= fp->f_d_inum;
371*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_d_maj    = fp->f_d_maj;
372*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_d_min    = fp->f_d_min;
373*7c478bd9Sstevel@tonic-gate 		fp->f_info[OPT_BASE].f_nlink	= fp->f_d_nlink;
374*7c478bd9Sstevel@tonic-gate 		fp->f_dstdiffs |= check_changes(fp, OPT_BASE, OPT_DST);
375*7c478bd9Sstevel@tonic-gate 
376*7c478bd9Sstevel@tonic-gate 		/* if nobody thinks the file exists, baseline needs pruning */
377*7c478bd9Sstevel@tonic-gate 		if ((fp->f_flags & (F_IN_SOURCE|F_IN_DEST)) == 0) {
378*7c478bd9Sstevel@tonic-gate 			fp->f_srcdiffs |= D_DELETE;
379*7c478bd9Sstevel@tonic-gate 			fp->f_dstdiffs |= D_DELETE;
380*7c478bd9Sstevel@tonic-gate 		}
381*7c478bd9Sstevel@tonic-gate 
382*7c478bd9Sstevel@tonic-gate 		/* keep track of possible deletions to look for trouble	*/
383*7c478bd9Sstevel@tonic-gate 		if ((fp->f_dstdiffs | fp->f_srcdiffs) & D_DELETE) {
384*7c478bd9Sstevel@tonic-gate 			est_deletes++;
385*7c478bd9Sstevel@tonic-gate 
386*7c478bd9Sstevel@tonic-gate 			/* see if file is (or has been) a non-empty directory */
387*7c478bd9Sstevel@tonic-gate 			if (fp->f_files)
388*7c478bd9Sstevel@tonic-gate 				est_rmdirs++;
389*7c478bd9Sstevel@tonic-gate 		}
390*7c478bd9Sstevel@tonic-gate 	}
391*7c478bd9Sstevel@tonic-gate 
392*7c478bd9Sstevel@tonic-gate 	/* if we found differences, queue the file for reconciliation 	*/
393*7c478bd9Sstevel@tonic-gate 	if (fp->f_srcdiffs || fp->f_dstdiffs || fp->f_flags & F_STAT_ERROR) {
394*7c478bd9Sstevel@tonic-gate 		queue_file(fp);
395*7c478bd9Sstevel@tonic-gate 
396*7c478bd9Sstevel@tonic-gate 		if (opt_debug & DBG_ANAL) {
397*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, "ANAL: src=%s",
398*7c478bd9Sstevel@tonic-gate 				showflags(diffmap, fp->f_srcdiffs));
399*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, " dst=%s",
400*7c478bd9Sstevel@tonic-gate 				showflags(diffmap, fp->f_dstdiffs));
401*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, " flgs=%s",
402*7c478bd9Sstevel@tonic-gate 				showflags(fileflags, fp->f_flags));
403*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, " name=%s\n", fp->f_fullname);
404*7c478bd9Sstevel@tonic-gate 		}
405*7c478bd9Sstevel@tonic-gate 	}
406*7c478bd9Sstevel@tonic-gate 
407*7c478bd9Sstevel@tonic-gate 	/* bump the total file count	*/
408*7c478bd9Sstevel@tonic-gate 	fp->f_base->b_totfiles++;
409*7c478bd9Sstevel@tonic-gate 	total_files++;
410*7c478bd9Sstevel@tonic-gate 
411*7c478bd9Sstevel@tonic-gate 	/* if this is not a directory, we're done	*/
412*7c478bd9Sstevel@tonic-gate 	if (fp->f_files == 0)
413*7c478bd9Sstevel@tonic-gate 		return (errs);
414*7c478bd9Sstevel@tonic-gate 
415*7c478bd9Sstevel@tonic-gate 	/*
416*7c478bd9Sstevel@tonic-gate 	 * If this is a directory, we need to recursively analyze
417*7c478bd9Sstevel@tonic-gate 	 * our children, but only children who have been evaluated.
418*7c478bd9Sstevel@tonic-gate 	 * If a node has not been evaluated, then we don't have
419*7c478bd9Sstevel@tonic-gate 	 * updated stat information and there is nothing to analyze.
420*7c478bd9Sstevel@tonic-gate 	 *
421*7c478bd9Sstevel@tonic-gate 	 * we also want to run though all nodes with STAT errors
422*7c478bd9Sstevel@tonic-gate 	 * so that we can put them on the reconciliation list.
423*7c478bd9Sstevel@tonic-gate 	 * If a directory is unreadable on one side, all files
424*7c478bd9Sstevel@tonic-gate 	 * under that directory (ON BOTH SIDES) must be marked as
425*7c478bd9Sstevel@tonic-gate 	 * blocked by stat errors.
426*7c478bd9Sstevel@tonic-gate 	 */
427*7c478bd9Sstevel@tonic-gate 	push_name(fp->f_name);
428*7c478bd9Sstevel@tonic-gate 
429*7c478bd9Sstevel@tonic-gate 	for (cp = fp->f_files; cp; cp = cp->f_next) {
430*7c478bd9Sstevel@tonic-gate 		if (fp->f_flags & F_STAT_ERROR)
431*7c478bd9Sstevel@tonic-gate 			cp->f_flags |= F_STAT_ERROR;
432*7c478bd9Sstevel@tonic-gate 		if (cp->f_flags & (F_EVALUATE|F_STAT_ERROR))
433*7c478bd9Sstevel@tonic-gate 			errs |= check_file(cp);
434*7c478bd9Sstevel@tonic-gate 	}
435*7c478bd9Sstevel@tonic-gate 
436*7c478bd9Sstevel@tonic-gate 	pop_name();
437*7c478bd9Sstevel@tonic-gate 
438*7c478bd9Sstevel@tonic-gate 	return (errs);
439*7c478bd9Sstevel@tonic-gate }
440*7c478bd9Sstevel@tonic-gate 
441*7c478bd9Sstevel@tonic-gate /*
442*7c478bd9Sstevel@tonic-gate  * routine:
443*7c478bd9Sstevel@tonic-gate  *	check_changes
444*7c478bd9Sstevel@tonic-gate  *
445*7c478bd9Sstevel@tonic-gate  * purpose:
446*7c478bd9Sstevel@tonic-gate  *	to figure out what has changed for a specific file
447*7c478bd9Sstevel@tonic-gate  *
448*7c478bd9Sstevel@tonic-gate  * parameters:
449*7c478bd9Sstevel@tonic-gate  *	file pointer
450*7c478bd9Sstevel@tonic-gate  *	the reference info
451*7c478bd9Sstevel@tonic-gate  *	the info to be checked for changes
452*7c478bd9Sstevel@tonic-gate  *
453*7c478bd9Sstevel@tonic-gate  * returns:
454*7c478bd9Sstevel@tonic-gate  *	diff mask
455*7c478bd9Sstevel@tonic-gate  *
456*7c478bd9Sstevel@tonic-gate  * notes:
457*7c478bd9Sstevel@tonic-gate  *	this routine doesn't pretend to understand what happened.
458*7c478bd9Sstevel@tonic-gate  *	it merely enumerates the ways in which the files differ.
459*7c478bd9Sstevel@tonic-gate  */
460*7c478bd9Sstevel@tonic-gate static diffmask_t
461*7c478bd9Sstevel@tonic-gate check_changes(struct file *fp, int ref, int new)
462*7c478bd9Sstevel@tonic-gate {	struct fileinfo *rp, *np;
463*7c478bd9Sstevel@tonic-gate 	int mask = 0;
464*7c478bd9Sstevel@tonic-gate 	int type;
465*7c478bd9Sstevel@tonic-gate 
466*7c478bd9Sstevel@tonic-gate 	rp = &fp->f_info[ref];
467*7c478bd9Sstevel@tonic-gate 	np = &fp->f_info[new];
468*7c478bd9Sstevel@tonic-gate 
469*7c478bd9Sstevel@tonic-gate 	if (np->f_uid != rp->f_uid)
470*7c478bd9Sstevel@tonic-gate 		mask |= D_UID;
471*7c478bd9Sstevel@tonic-gate 
472*7c478bd9Sstevel@tonic-gate 	if (np->f_gid != rp->f_gid)
473*7c478bd9Sstevel@tonic-gate 		mask |= D_GID;
474*7c478bd9Sstevel@tonic-gate 
475*7c478bd9Sstevel@tonic-gate 	if (np->f_mode != rp->f_mode)
476*7c478bd9Sstevel@tonic-gate 		mask |= D_PROT;
477*7c478bd9Sstevel@tonic-gate 
478*7c478bd9Sstevel@tonic-gate 	type = np->f_type;
479*7c478bd9Sstevel@tonic-gate 	if (type != rp->f_type) {
480*7c478bd9Sstevel@tonic-gate 		if (type == 0)
481*7c478bd9Sstevel@tonic-gate 			mask |= D_DELETE;
482*7c478bd9Sstevel@tonic-gate 		else if (rp->f_type == 0)
483*7c478bd9Sstevel@tonic-gate 			mask |= D_CREATE;
484*7c478bd9Sstevel@tonic-gate 		else
485*7c478bd9Sstevel@tonic-gate 			mask |= D_TYPE;
486*7c478bd9Sstevel@tonic-gate 	} else if (type == S_IFBLK || type == S_IFCHR) {
487*7c478bd9Sstevel@tonic-gate 		/*
488*7c478bd9Sstevel@tonic-gate 		 * for special files, we only look at the maj/min
489*7c478bd9Sstevel@tonic-gate 		 */
490*7c478bd9Sstevel@tonic-gate 		if (np->f_rd_maj != rp->f_rd_maj)
491*7c478bd9Sstevel@tonic-gate 			mask |= D_SIZE;
492*7c478bd9Sstevel@tonic-gate 		if (np->f_rd_min != rp->f_rd_min)
493*7c478bd9Sstevel@tonic-gate 			mask |= D_SIZE;
494*7c478bd9Sstevel@tonic-gate 	} else if (type != S_IFDIR) {
495*7c478bd9Sstevel@tonic-gate 		/*
496*7c478bd9Sstevel@tonic-gate 		 * for directories, we don't look directly at
497*7c478bd9Sstevel@tonic-gate 		 * the contents, so these fields don't mean
498*7c478bd9Sstevel@tonic-gate 		 * anything.  If the directories have changed
499*7c478bd9Sstevel@tonic-gate 		 * in any interesting way, we'll find it by
500*7c478bd9Sstevel@tonic-gate 		 * walking the tree.
501*7c478bd9Sstevel@tonic-gate 		 */
502*7c478bd9Sstevel@tonic-gate 		if (np->f_modtime > rp->f_modtime)
503*7c478bd9Sstevel@tonic-gate 			mask |= D_MTIME;
504*7c478bd9Sstevel@tonic-gate 
505*7c478bd9Sstevel@tonic-gate 		if (np->f_size != rp->f_size)
506*7c478bd9Sstevel@tonic-gate 			mask |= D_SIZE;
507*7c478bd9Sstevel@tonic-gate 
508*7c478bd9Sstevel@tonic-gate 		if (np->f_nlink != rp->f_nlink)
509*7c478bd9Sstevel@tonic-gate 			mask |= D_LINKS;
510*7c478bd9Sstevel@tonic-gate 	}
511*7c478bd9Sstevel@tonic-gate 
512*7c478bd9Sstevel@tonic-gate 	if (cmp_acls(rp, np) == 0)
513*7c478bd9Sstevel@tonic-gate 		mask |= D_FACLS;
514*7c478bd9Sstevel@tonic-gate 
515*7c478bd9Sstevel@tonic-gate 	return (mask);
516*7c478bd9Sstevel@tonic-gate }
517*7c478bd9Sstevel@tonic-gate 
518*7c478bd9Sstevel@tonic-gate /*
519*7c478bd9Sstevel@tonic-gate  * routine:
520*7c478bd9Sstevel@tonic-gate  *	same_name
521*7c478bd9Sstevel@tonic-gate  *
522*7c478bd9Sstevel@tonic-gate  * purpose:
523*7c478bd9Sstevel@tonic-gate  *	to figure out whether or not two databsae nodes actually refer to
524*7c478bd9Sstevel@tonic-gate  *	the same file.
525*7c478bd9Sstevel@tonic-gate  *
526*7c478bd9Sstevel@tonic-gate  * parameters:
527*7c478bd9Sstevel@tonic-gate  *	pointers to two file description nodes
528*7c478bd9Sstevel@tonic-gate  *	which side we should check
529*7c478bd9Sstevel@tonic-gate  *
530*7c478bd9Sstevel@tonic-gate  * returns:
531*7c478bd9Sstevel@tonic-gate  *	TRUE/FALSE
532*7c478bd9Sstevel@tonic-gate  *
533*7c478bd9Sstevel@tonic-gate  * notes:
534*7c478bd9Sstevel@tonic-gate  *	if a single directory is specified in multiple base pairs, it
535*7c478bd9Sstevel@tonic-gate  *	is possible to have multiple nodes in the database describing
536*7c478bd9Sstevel@tonic-gate  *	the same file.  This routine is supposed to detect those cases.
537*7c478bd9Sstevel@tonic-gate  *
538*7c478bd9Sstevel@tonic-gate  *	what should be a trivial string comparison is complicated by
539*7c478bd9Sstevel@tonic-gate  *	the possibility that the two nodes might describe the same file
540*7c478bd9Sstevel@tonic-gate  *	from base directories at different depths.  Thus, rather than
541*7c478bd9Sstevel@tonic-gate  *	comparing two strings, we really want to compare the concatenation
542*7c478bd9Sstevel@tonic-gate  *	of two pairs of strings.  Unfortunately calling full_name would
543*7c478bd9Sstevel@tonic-gate  *	be awkward right now, so instead we have our own comparison
544*7c478bd9Sstevel@tonic-gate  *	routine that automatically skips from the first string to
545*7c478bd9Sstevel@tonic-gate  *	the second.
546*7c478bd9Sstevel@tonic-gate  */
547*7c478bd9Sstevel@tonic-gate static bool_t
548*7c478bd9Sstevel@tonic-gate same_name(struct file *f1, struct file *f2, side_t srcdst)
549*7c478bd9Sstevel@tonic-gate {
550*7c478bd9Sstevel@tonic-gate 	char *s1, *s2, *x1, *x2;
551*7c478bd9Sstevel@tonic-gate 
552*7c478bd9Sstevel@tonic-gate 	if (srcdst == OPT_SRC) {
553*7c478bd9Sstevel@tonic-gate 		s1 = (f1->f_base)->b_src_name;
554*7c478bd9Sstevel@tonic-gate 		s2 = (f2->f_base)->b_src_name;
555*7c478bd9Sstevel@tonic-gate 	} else {
556*7c478bd9Sstevel@tonic-gate 		s1 = (f1->f_base)->b_dst_name;
557*7c478bd9Sstevel@tonic-gate 		s2 = (f2->f_base)->b_dst_name;
558*7c478bd9Sstevel@tonic-gate 	}
559*7c478bd9Sstevel@tonic-gate 	x1 = f1->f_fullname;
560*7c478bd9Sstevel@tonic-gate 	x2 = f2->f_fullname;
561*7c478bd9Sstevel@tonic-gate 
562*7c478bd9Sstevel@tonic-gate 	/*
563*7c478bd9Sstevel@tonic-gate 	 * Compare the two names, and if they differ before they end
564*7c478bd9Sstevel@tonic-gate 	 * this is a non-match.  If they both end at the same time,
565*7c478bd9Sstevel@tonic-gate 	 * this is a match.
566*7c478bd9Sstevel@tonic-gate 	 *
567*7c478bd9Sstevel@tonic-gate 	 * The trick here is that each string is actually the logical
568*7c478bd9Sstevel@tonic-gate 	 * concatenation of two strings, and we need to automatically
569*7c478bd9Sstevel@tonic-gate 	 * wrap from the first to the second string in each pair.  There
570*7c478bd9Sstevel@tonic-gate 	 * is no requirement that the two (concatenated) strings be
571*7c478bd9Sstevel@tonic-gate 	 * broken at the same point, so we have a slightly baroque
572*7c478bd9Sstevel@tonic-gate 	 * comparsion loop.
573*7c478bd9Sstevel@tonic-gate 	 */
574*7c478bd9Sstevel@tonic-gate 	while (*s1 && *s1 == *s2) {
575*7c478bd9Sstevel@tonic-gate 
576*7c478bd9Sstevel@tonic-gate 		/*
577*7c478bd9Sstevel@tonic-gate 		 * strings have been identical so far, so advance the
578*7c478bd9Sstevel@tonic-gate 		 * pointers and continue the comparison.  The trick
579*7c478bd9Sstevel@tonic-gate 		 * is that when either string ends, we have to wrap
580*7c478bd9Sstevel@tonic-gate 		 * over to its extension.
581*7c478bd9Sstevel@tonic-gate 		 */
582*7c478bd9Sstevel@tonic-gate 		s1++; s2++;
583*7c478bd9Sstevel@tonic-gate 		if (*s1 && *s2)
584*7c478bd9Sstevel@tonic-gate 			continue;
585*7c478bd9Sstevel@tonic-gate 
586*7c478bd9Sstevel@tonic-gate 		/*
587*7c478bd9Sstevel@tonic-gate 		 * at least one of the strings has ended.
588*7c478bd9Sstevel@tonic-gate 		 * there is an implicit slash between the string
589*7c478bd9Sstevel@tonic-gate 		 * and its extension, and this has to be matched
590*7c478bd9Sstevel@tonic-gate 		 * against the other string.
591*7c478bd9Sstevel@tonic-gate 		 */
592*7c478bd9Sstevel@tonic-gate 		if (*s1 != *s2) {
593*7c478bd9Sstevel@tonic-gate 			if (*s1 == 0 && *s2 == '/')
594*7c478bd9Sstevel@tonic-gate 				s2++;
595*7c478bd9Sstevel@tonic-gate 			else if (*s2 == 0 && *s1 == '/')
596*7c478bd9Sstevel@tonic-gate 				s1++;
597*7c478bd9Sstevel@tonic-gate 			else
598*7c478bd9Sstevel@tonic-gate 				/* the disagreement doesn't come at a slash */
599*7c478bd9Sstevel@tonic-gate 				break;
600*7c478bd9Sstevel@tonic-gate 		}
601*7c478bd9Sstevel@tonic-gate 
602*7c478bd9Sstevel@tonic-gate 		/*
603*7c478bd9Sstevel@tonic-gate 		 * if either string has ended, wrap to its extension
604*7c478bd9Sstevel@tonic-gate 		 */
605*7c478bd9Sstevel@tonic-gate 		if (*s1 == 0 && x1 != 0) {
606*7c478bd9Sstevel@tonic-gate 			s1 = x1;
607*7c478bd9Sstevel@tonic-gate 			x1 = 0;
608*7c478bd9Sstevel@tonic-gate 		}
609*7c478bd9Sstevel@tonic-gate 		if (*s2 == 0 && x2 != 0) {
610*7c478bd9Sstevel@tonic-gate 			s2 = x2;
611*7c478bd9Sstevel@tonic-gate 			x2 = 0;
612*7c478bd9Sstevel@tonic-gate 		}
613*7c478bd9Sstevel@tonic-gate 	}
614*7c478bd9Sstevel@tonic-gate 
615*7c478bd9Sstevel@tonic-gate 	return (*s1 == *s2);
616*7c478bd9Sstevel@tonic-gate }
617*7c478bd9Sstevel@tonic-gate 
618*7c478bd9Sstevel@tonic-gate /*
619*7c478bd9Sstevel@tonic-gate  * routine:
620*7c478bd9Sstevel@tonic-gate  *	find_link
621*7c478bd9Sstevel@tonic-gate  *
622*7c478bd9Sstevel@tonic-gate  * purpose:
623*7c478bd9Sstevel@tonic-gate  *	to figure out if there is a file to which we should
624*7c478bd9Sstevel@tonic-gate  *	be creating a link (rather than making a copy)
625*7c478bd9Sstevel@tonic-gate  *
626*7c478bd9Sstevel@tonic-gate  * parameters:
627*7c478bd9Sstevel@tonic-gate  *	file node for the file to be created (that we hope is merely a link)
628*7c478bd9Sstevel@tonic-gate  *	which side is to be changed (src/dst)
629*7c478bd9Sstevel@tonic-gate  *
630*7c478bd9Sstevel@tonic-gate  * return:
631*7c478bd9Sstevel@tonic-gate  *	0	no link is appropriate
632*7c478bd9Sstevel@tonic-gate  *	else	pointer to file node for link referent
633*7c478bd9Sstevel@tonic-gate  *
634*7c478bd9Sstevel@tonic-gate  * notes:
635*7c478bd9Sstevel@tonic-gate  *	there are a few strange heuristics in this routine and I
636*7c478bd9Sstevel@tonic-gate  *	wouldn't bet my soul that I got all of them right.  The general
637*7c478bd9Sstevel@tonic-gate  *	theory is that when a new file is created, we look to see if it
638*7c478bd9Sstevel@tonic-gate  *	is a link to another file on the changed side, and if it is, we
639*7c478bd9Sstevel@tonic-gate  *	find the corresponding file on the unchanged side.
640*7c478bd9Sstevel@tonic-gate  *
641*7c478bd9Sstevel@tonic-gate  *	cases we want to be able to handle:
642*7c478bd9Sstevel@tonic-gate  *	    1.	one or more links are created to a prexisting file
643*7c478bd9Sstevel@tonic-gate  *	    2.	a preexisting only link is renamed
644*7c478bd9Sstevel@tonic-gate  *	    3.  a rename of one of multiple links to a preexisting file
645*7c478bd9Sstevel@tonic-gate  *	    4.	a single file is created with multiple links
646*7c478bd9Sstevel@tonic-gate  */
647*7c478bd9Sstevel@tonic-gate struct file *
648*7c478bd9Sstevel@tonic-gate find_link(struct file *fp, side_t srcdst)
649*7c478bd9Sstevel@tonic-gate {	struct file *lp;
650*7c478bd9Sstevel@tonic-gate 	side_t chgside, tgtside;
651*7c478bd9Sstevel@tonic-gate 	struct fileinfo *chgp, *tgtp, *basp, *fcp, *ftp;
652*7c478bd9Sstevel@tonic-gate 
653*7c478bd9Sstevel@tonic-gate 	/* chg = side on which the change was noticed		*/
654*7c478bd9Sstevel@tonic-gate 	/* tgt = side to which the change is to be propagated	*/
655*7c478bd9Sstevel@tonic-gate 	chgside = (srcdst == OPT_SRC) ? OPT_DST : OPT_SRC;
656*7c478bd9Sstevel@tonic-gate 	tgtside = (srcdst == OPT_SRC) ? OPT_SRC : OPT_DST;
657*7c478bd9Sstevel@tonic-gate 	fcp = &fp->f_info[chgside];
658*7c478bd9Sstevel@tonic-gate 	ftp = &fp->f_info[tgtside];
659*7c478bd9Sstevel@tonic-gate 
660*7c478bd9Sstevel@tonic-gate 	/*
661*7c478bd9Sstevel@tonic-gate 	 * cases 1 and 3
662*7c478bd9Sstevel@tonic-gate 	 *
663*7c478bd9Sstevel@tonic-gate 	 * When a new link is created, we should be able to find
664*7c478bd9Sstevel@tonic-gate 	 * another file in the changed hierarchy that has the same
665*7c478bd9Sstevel@tonic-gate 	 * I-node number.  We expect it to be on the changed list
666*7c478bd9Sstevel@tonic-gate 	 * because the link count will have gone up or because all
667*7c478bd9Sstevel@tonic-gate 	 * of the copies are new.  If we find one, then the new file
668*7c478bd9Sstevel@tonic-gate 	 * on the receiving file should be a link to the corresponding
669*7c478bd9Sstevel@tonic-gate 	 * existing file.
670*7c478bd9Sstevel@tonic-gate 	 *
671*7c478bd9Sstevel@tonic-gate 	 * case 4
672*7c478bd9Sstevel@tonic-gate 	 *
673*7c478bd9Sstevel@tonic-gate 	 * the first link will be dealt with as a copy, but all
674*7c478bd9Sstevel@tonic-gate 	 * subsequent links should find an existing file analogous
675*7c478bd9Sstevel@tonic-gate 	 * to one of the links on the changed side, and create
676*7c478bd9Sstevel@tonic-gate 	 * corresponding links on the other side.
677*7c478bd9Sstevel@tonic-gate 	 *
678*7c478bd9Sstevel@tonic-gate 	 * in each of these cases, there should be multiple links
679*7c478bd9Sstevel@tonic-gate 	 * on the changed side.  If the linkcount on the changed
680*7c478bd9Sstevel@tonic-gate 	 * side is one, we needn't bother searching for other links.
681*7c478bd9Sstevel@tonic-gate 	 */
682*7c478bd9Sstevel@tonic-gate 	if (fcp->f_nlink > 1)
683*7c478bd9Sstevel@tonic-gate 	for (lp = changes; lp; lp = lp->f_rnext) {
684*7c478bd9Sstevel@tonic-gate 		/* finding the same node doesn't count	*/
685*7c478bd9Sstevel@tonic-gate 		if (fp == lp)
686*7c478bd9Sstevel@tonic-gate 			continue;
687*7c478bd9Sstevel@tonic-gate 
688*7c478bd9Sstevel@tonic-gate 		tgtp = &lp->f_info[tgtside];
689*7c478bd9Sstevel@tonic-gate 		chgp = &lp->f_info[chgside];
690*7c478bd9Sstevel@tonic-gate 
691*7c478bd9Sstevel@tonic-gate 		/*
692*7c478bd9Sstevel@tonic-gate 		 * if the file doesn't already exist on the target side
693*7c478bd9Sstevel@tonic-gate 		 * we cannot make a link to it
694*7c478bd9Sstevel@tonic-gate 		 */
695*7c478bd9Sstevel@tonic-gate 		if (tgtp->f_mode == 0)
696*7c478bd9Sstevel@tonic-gate 			continue;
697*7c478bd9Sstevel@tonic-gate 
698*7c478bd9Sstevel@tonic-gate 		/*
699*7c478bd9Sstevel@tonic-gate 		 * if this is indeed a link, then the prospective file on
700*7c478bd9Sstevel@tonic-gate 		 * the changed side will have the same dev/inum as the file
701*7c478bd9Sstevel@tonic-gate 		 * we are looking for
702*7c478bd9Sstevel@tonic-gate 		 */
703*7c478bd9Sstevel@tonic-gate 		if (fcp->f_d_maj != chgp->f_d_maj)
704*7c478bd9Sstevel@tonic-gate 			continue;
705*7c478bd9Sstevel@tonic-gate 		if (fcp->f_d_min != chgp->f_d_min)
706*7c478bd9Sstevel@tonic-gate 			continue;
707*7c478bd9Sstevel@tonic-gate 		if (fcp->f_ino != chgp->f_ino)
708*7c478bd9Sstevel@tonic-gate 			continue;
709*7c478bd9Sstevel@tonic-gate 
710*7c478bd9Sstevel@tonic-gate 		/*
711*7c478bd9Sstevel@tonic-gate 		 * if the target side is already a link to this file,
712*7c478bd9Sstevel@tonic-gate 		 * then there is no new link to be created
713*7c478bd9Sstevel@tonic-gate 		 * FIX: how does this interact with copies over links
714*7c478bd9Sstevel@tonic-gate 		 */
715*7c478bd9Sstevel@tonic-gate 		if ((ftp->f_d_maj == tgtp->f_d_maj) &&
716*7c478bd9Sstevel@tonic-gate 		    (ftp->f_d_min == tgtp->f_d_min) &&
717*7c478bd9Sstevel@tonic-gate 		    (ftp->f_ino   == tgtp->f_ino))
718*7c478bd9Sstevel@tonic-gate 			continue;
719*7c478bd9Sstevel@tonic-gate 
720*7c478bd9Sstevel@tonic-gate 		/*
721*7c478bd9Sstevel@tonic-gate 		 * there is a pathological situation where a single file
722*7c478bd9Sstevel@tonic-gate 		 * might appear under multiple base directories.  This is
723*7c478bd9Sstevel@tonic-gate 		 * damned awkward to detect in any other way, so we must
724*7c478bd9Sstevel@tonic-gate 		 * check to see if we have just found another database
725*7c478bd9Sstevel@tonic-gate 		 * instance for the same file (on the changed side).
726*7c478bd9Sstevel@tonic-gate 		 */
727*7c478bd9Sstevel@tonic-gate 		if ((fp->f_base != lp->f_base) && same_name(fp, lp, chgside))
728*7c478bd9Sstevel@tonic-gate 			continue;
729*7c478bd9Sstevel@tonic-gate 
730*7c478bd9Sstevel@tonic-gate 		if (opt_debug & DBG_ANAL)
731*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, "ANAL: FIND LINK %s and %s\n",
732*7c478bd9Sstevel@tonic-gate 				fp->f_fullname, lp->f_fullname);
733*7c478bd9Sstevel@tonic-gate 
734*7c478bd9Sstevel@tonic-gate 		return (lp);
735*7c478bd9Sstevel@tonic-gate 	}
736*7c478bd9Sstevel@tonic-gate 
737*7c478bd9Sstevel@tonic-gate 	/*
738*7c478bd9Sstevel@tonic-gate 	 * case 2: a simple rename of the only link
739*7c478bd9Sstevel@tonic-gate 	 *
740*7c478bd9Sstevel@tonic-gate 	 * In this case, there may not be any other existing file on
741*7c478bd9Sstevel@tonic-gate 	 * the changed side that has the same I-node number.  There
742*7c478bd9Sstevel@tonic-gate 	 * might, however, be a record of such a file in the baseline.
743*7c478bd9Sstevel@tonic-gate 	 * If we can find an identical file with a different name that
744*7c478bd9Sstevel@tonic-gate 	 * has recently disappeared, we have a likely rename.
745*7c478bd9Sstevel@tonic-gate 	 */
746*7c478bd9Sstevel@tonic-gate 	for (lp = changes; lp; lp = lp->f_rnext) {
747*7c478bd9Sstevel@tonic-gate 
748*7c478bd9Sstevel@tonic-gate 		/* finding the same node doesn't count			*/
749*7c478bd9Sstevel@tonic-gate 		if (fp == lp)
750*7c478bd9Sstevel@tonic-gate 			continue;
751*7c478bd9Sstevel@tonic-gate 
752*7c478bd9Sstevel@tonic-gate 		tgtp = &lp->f_info[tgtside];
753*7c478bd9Sstevel@tonic-gate 		chgp = &lp->f_info[chgside];
754*7c478bd9Sstevel@tonic-gate 
755*7c478bd9Sstevel@tonic-gate 		/*
756*7c478bd9Sstevel@tonic-gate 		 * if the file still exists on the changed side this is
757*7c478bd9Sstevel@tonic-gate 		 * not a simple rename, and in fact the previous pass
758*7c478bd9Sstevel@tonic-gate 		 * would have found it.
759*7c478bd9Sstevel@tonic-gate 		 */
760*7c478bd9Sstevel@tonic-gate 		if (chgp->f_mode != 0)
761*7c478bd9Sstevel@tonic-gate 			continue;
762*7c478bd9Sstevel@tonic-gate 
763*7c478bd9Sstevel@tonic-gate 		/*
764*7c478bd9Sstevel@tonic-gate 		 * the inode number for the new link on the changed
765*7c478bd9Sstevel@tonic-gate 		 * side must match the inode number for the old link
766*7c478bd9Sstevel@tonic-gate 		 * from the baseline.
767*7c478bd9Sstevel@tonic-gate 		 */
768*7c478bd9Sstevel@tonic-gate 		if (fcp->f_d_maj != ((srcdst == OPT_SRC) ? lp->f_d_maj
769*7c478bd9Sstevel@tonic-gate 							: lp->f_s_maj))
770*7c478bd9Sstevel@tonic-gate 			continue;
771*7c478bd9Sstevel@tonic-gate 		if (fcp->f_d_min != ((srcdst == OPT_SRC) ? lp->f_d_min
772*7c478bd9Sstevel@tonic-gate 							: lp->f_s_min))
773*7c478bd9Sstevel@tonic-gate 			continue;
774*7c478bd9Sstevel@tonic-gate 		if (fcp->f_ino != ((srcdst == OPT_SRC) ? lp->f_d_inum
775*7c478bd9Sstevel@tonic-gate 							: lp->f_s_inum))
776*7c478bd9Sstevel@tonic-gate 			continue;
777*7c478bd9Sstevel@tonic-gate 
778*7c478bd9Sstevel@tonic-gate 		/* finding a file we are already linked to doesn't help	*/
779*7c478bd9Sstevel@tonic-gate 		if ((ftp->f_d_maj == tgtp->f_d_maj) &&
780*7c478bd9Sstevel@tonic-gate 		    (ftp->f_d_min == tgtp->f_d_min) &&
781*7c478bd9Sstevel@tonic-gate 		    (ftp->f_ino   == tgtp->f_ino))
782*7c478bd9Sstevel@tonic-gate 			continue;
783*7c478bd9Sstevel@tonic-gate 
784*7c478bd9Sstevel@tonic-gate 		/*
785*7c478bd9Sstevel@tonic-gate 		 * there is a danger that we will confuse an
786*7c478bd9Sstevel@tonic-gate 		 * inode reallocation with a rename.  We should
787*7c478bd9Sstevel@tonic-gate 		 * only consider this to be a rename if the
788*7c478bd9Sstevel@tonic-gate 		 * new file is identical to the old one
789*7c478bd9Sstevel@tonic-gate 		 */
790*7c478bd9Sstevel@tonic-gate 		basp = &lp->f_info[OPT_BASE];
791*7c478bd9Sstevel@tonic-gate 		if (fcp->f_type != basp->f_type)
792*7c478bd9Sstevel@tonic-gate 			continue;
793*7c478bd9Sstevel@tonic-gate 		if (fcp->f_size != basp->f_size)
794*7c478bd9Sstevel@tonic-gate 			continue;
795*7c478bd9Sstevel@tonic-gate 		if (fcp->f_mode != basp->f_mode)
796*7c478bd9Sstevel@tonic-gate 			continue;
797*7c478bd9Sstevel@tonic-gate 		if (fcp->f_uid != basp->f_uid)
798*7c478bd9Sstevel@tonic-gate 			continue;
799*7c478bd9Sstevel@tonic-gate 		if (fcp->f_gid != basp->f_gid)
800*7c478bd9Sstevel@tonic-gate 			continue;
801*7c478bd9Sstevel@tonic-gate 
802*7c478bd9Sstevel@tonic-gate 		if (opt_debug & DBG_ANAL)
803*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, "ANAL: FIND RENAME %s and %s\n",
804*7c478bd9Sstevel@tonic-gate 				fp->f_fullname, lp->f_fullname);
805*7c478bd9Sstevel@tonic-gate 
806*7c478bd9Sstevel@tonic-gate 		return (lp);
807*7c478bd9Sstevel@tonic-gate 	}
808*7c478bd9Sstevel@tonic-gate 
809*7c478bd9Sstevel@tonic-gate 	return (0);
810*7c478bd9Sstevel@tonic-gate }
811*7c478bd9Sstevel@tonic-gate 
812*7c478bd9Sstevel@tonic-gate /*
813*7c478bd9Sstevel@tonic-gate  * routine:
814*7c478bd9Sstevel@tonic-gate  *	has_other_links
815*7c478bd9Sstevel@tonic-gate  *
816*7c478bd9Sstevel@tonic-gate  * purpose:
817*7c478bd9Sstevel@tonic-gate  *	to determine whether or not there is more that one link to a
818*7c478bd9Sstevel@tonic-gate  *	particular file.  We are willing to delete a link to a file
819*7c478bd9Sstevel@tonic-gate  *	that has changed if we will still have other links to it.
820*7c478bd9Sstevel@tonic-gate  *	The trick here is that we only care about links under our
821*7c478bd9Sstevel@tonic-gate  *	dominion.
822*7c478bd9Sstevel@tonic-gate  *
823*7c478bd9Sstevel@tonic-gate  * parameters:
824*7c478bd9Sstevel@tonic-gate  *	file pointer to node we are interested in
825*7c478bd9Sstevel@tonic-gate  *	which side we are looking to additional links on
826*7c478bd9Sstevel@tonic-gate  *
827*7c478bd9Sstevel@tonic-gate  * returns:
828*7c478bd9Sstevel@tonic-gate  *	TRUE if there are multiple links
829*7c478bd9Sstevel@tonic-gate  *	FALSE if this is the only one we know of
830*7c478bd9Sstevel@tonic-gate  */
831*7c478bd9Sstevel@tonic-gate bool_t
832*7c478bd9Sstevel@tonic-gate has_other_links(struct file *fp, side_t srcdst)
833*7c478bd9Sstevel@tonic-gate {	struct file *lp;
834*7c478bd9Sstevel@tonic-gate 	struct fileinfo *fip, *lip;
835*7c478bd9Sstevel@tonic-gate 
836*7c478bd9Sstevel@tonic-gate 	fip = &fp->f_info[srcdst];
837*7c478bd9Sstevel@tonic-gate 
838*7c478bd9Sstevel@tonic-gate 	/* if the link count is one, there couldn't be others	*/
839*7c478bd9Sstevel@tonic-gate 	if (fip->f_nlink < 2)
840*7c478bd9Sstevel@tonic-gate 		return (FALSE);
841*7c478bd9Sstevel@tonic-gate 
842*7c478bd9Sstevel@tonic-gate 	/* look for any other files for the same inode		*/
843*7c478bd9Sstevel@tonic-gate 	for (lp = changes; lp; lp = lp->f_rnext) {
844*7c478bd9Sstevel@tonic-gate 		/* finding the same node doesn't count	*/
845*7c478bd9Sstevel@tonic-gate 		if (fp == lp)
846*7c478bd9Sstevel@tonic-gate 			continue;
847*7c478bd9Sstevel@tonic-gate 
848*7c478bd9Sstevel@tonic-gate 		lip = &lp->f_info[srcdst];
849*7c478bd9Sstevel@tonic-gate 
850*7c478bd9Sstevel@tonic-gate 		/*
851*7c478bd9Sstevel@tonic-gate 		 * file must still exist on this side
852*7c478bd9Sstevel@tonic-gate 		 */
853*7c478bd9Sstevel@tonic-gate 		if (lip->f_mode == 0)
854*7c478bd9Sstevel@tonic-gate 			continue;
855*7c478bd9Sstevel@tonic-gate 
856*7c478bd9Sstevel@tonic-gate 		/*
857*7c478bd9Sstevel@tonic-gate 		 * if this is indeed a link, then the prospective file on
858*7c478bd9Sstevel@tonic-gate 		 * the changed side will have the same dev/inum as the file
859*7c478bd9Sstevel@tonic-gate 		 * we are looking for
860*7c478bd9Sstevel@tonic-gate 		 */
861*7c478bd9Sstevel@tonic-gate 		if (lip->f_d_maj != fip->f_d_maj)
862*7c478bd9Sstevel@tonic-gate 			continue;
863*7c478bd9Sstevel@tonic-gate 		if (lip->f_d_min != fip->f_d_min)
864*7c478bd9Sstevel@tonic-gate 			continue;
865*7c478bd9Sstevel@tonic-gate 		if (lip->f_ino != fip->f_ino)
866*7c478bd9Sstevel@tonic-gate 			continue;
867*7c478bd9Sstevel@tonic-gate 
868*7c478bd9Sstevel@tonic-gate 		/*
869*7c478bd9Sstevel@tonic-gate 		 * we have found at least one other link
870*7c478bd9Sstevel@tonic-gate 		 */
871*7c478bd9Sstevel@tonic-gate 		return (TRUE);
872*7c478bd9Sstevel@tonic-gate 	}
873*7c478bd9Sstevel@tonic-gate 
874*7c478bd9Sstevel@tonic-gate 	return (FALSE);
875*7c478bd9Sstevel@tonic-gate }
876*7c478bd9Sstevel@tonic-gate 
877*7c478bd9Sstevel@tonic-gate /*
878*7c478bd9Sstevel@tonic-gate  * routine:
879*7c478bd9Sstevel@tonic-gate  *	link_update
880*7c478bd9Sstevel@tonic-gate  *
881*7c478bd9Sstevel@tonic-gate  * purpose:
882*7c478bd9Sstevel@tonic-gate  *	to propoagate a stat change to all other file nodes that
883*7c478bd9Sstevel@tonic-gate  *	correspond to the same I-node on the changed side
884*7c478bd9Sstevel@tonic-gate  *
885*7c478bd9Sstevel@tonic-gate  * parameters:
886*7c478bd9Sstevel@tonic-gate  *	file pointer for the updated file
887*7c478bd9Sstevel@tonic-gate  *	which side was changed
888*7c478bd9Sstevel@tonic-gate  *
889*7c478bd9Sstevel@tonic-gate  * returns:
890*7c478bd9Sstevel@tonic-gate  *	void
891*7c478bd9Sstevel@tonic-gate  *
892*7c478bd9Sstevel@tonic-gate  * notes:
893*7c478bd9Sstevel@tonic-gate  *	if we have copied onto a file, we have copied onto all
894*7c478bd9Sstevel@tonic-gate  *	of its links, but since we do all stats before we do any
895*7c478bd9Sstevel@tonic-gate  *	copies, the stat information recently collected for links
896*7c478bd9Sstevel@tonic-gate  *	is no longer up-to-date, and this would result in incorrect
897*7c478bd9Sstevel@tonic-gate  *	reconciliation (redundant copies).
898*7c478bd9Sstevel@tonic-gate  *
899*7c478bd9Sstevel@tonic-gate  *	There is an assumption here that all links to a changed
900*7c478bd9Sstevel@tonic-gate  *	file will be in the change list.  This is true for almost
901*7c478bd9Sstevel@tonic-gate  *	all cases not involving restriction.  If we do fail to
902*7c478bd9Sstevel@tonic-gate  *	update the baseline for a file that was off the change list,
903*7c478bd9Sstevel@tonic-gate  *	the worst that is likely to happen is that we will think
904*7c478bd9Sstevel@tonic-gate  *	it changed later (but will almost surely find that both
905*7c478bd9Sstevel@tonic-gate  *	copies agree).
906*7c478bd9Sstevel@tonic-gate  */
907*7c478bd9Sstevel@tonic-gate void
908*7c478bd9Sstevel@tonic-gate link_update(struct file *fp, side_t which)
909*7c478bd9Sstevel@tonic-gate {	struct file *lp;
910*7c478bd9Sstevel@tonic-gate 
911*7c478bd9Sstevel@tonic-gate 	for (lp = changes; lp; lp = lp->f_rnext) {
912*7c478bd9Sstevel@tonic-gate 		/* finding the current entry doesn't count	*/
913*7c478bd9Sstevel@tonic-gate 		if (lp == fp)
914*7c478bd9Sstevel@tonic-gate 			continue;
915*7c478bd9Sstevel@tonic-gate 
916*7c478bd9Sstevel@tonic-gate 		/* look for same i#, maj, min on changed side	*/
917*7c478bd9Sstevel@tonic-gate 		if (lp->f_info[which].f_ino != fp->f_info[which].f_ino)
918*7c478bd9Sstevel@tonic-gate 			continue;
919*7c478bd9Sstevel@tonic-gate 		if (lp->f_info[which].f_d_maj != fp->f_info[which].f_d_maj)
920*7c478bd9Sstevel@tonic-gate 			continue;
921*7c478bd9Sstevel@tonic-gate 		if (lp->f_info[which].f_d_min != fp->f_info[which].f_d_min)
922*7c478bd9Sstevel@tonic-gate 			continue;
923*7c478bd9Sstevel@tonic-gate 
924*7c478bd9Sstevel@tonic-gate 		/*
925*7c478bd9Sstevel@tonic-gate 		 * this appears to be another link to the same file
926*7c478bd9Sstevel@tonic-gate 		 * so the updated stat information for one must be
927*7c478bd9Sstevel@tonic-gate 		 * correct for the other.
928*7c478bd9Sstevel@tonic-gate 		 */
929*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_type	= fp->f_info[which].f_type;
930*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_size	= fp->f_info[which].f_size;
931*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_mode	= fp->f_info[which].f_mode;
932*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_uid		= fp->f_info[which].f_uid;
933*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_gid		= fp->f_info[which].f_gid;
934*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_modtime	= fp->f_info[which].f_modtime;
935*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_modns	= fp->f_info[which].f_modns;
936*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_nlink	= fp->f_info[which].f_nlink;
937*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_rd_maj	= fp->f_info[which].f_rd_maj;
938*7c478bd9Sstevel@tonic-gate 		lp->f_info[which].f_rd_min	= fp->f_info[which].f_rd_min;
939*7c478bd9Sstevel@tonic-gate 
940*7c478bd9Sstevel@tonic-gate 		if (opt_debug & DBG_STAT)
941*7c478bd9Sstevel@tonic-gate 			fprintf(stderr,
942*7c478bd9Sstevel@tonic-gate 				"STAT: UPDATE LINK, file=%s, mod=%08lx.%08lx\n",
943*7c478bd9Sstevel@tonic-gate 				lp->f_name, lp->f_info[which].f_modtime,
944*7c478bd9Sstevel@tonic-gate 				lp->f_info[which].f_modns);
945*7c478bd9Sstevel@tonic-gate 	}
946*7c478bd9Sstevel@tonic-gate }
947*7c478bd9Sstevel@tonic-gate 
948*7c478bd9Sstevel@tonic-gate /*
949*7c478bd9Sstevel@tonic-gate  * routine:
950*7c478bd9Sstevel@tonic-gate  *	queue_file
951*7c478bd9Sstevel@tonic-gate  *
952*7c478bd9Sstevel@tonic-gate  * purpose:
953*7c478bd9Sstevel@tonic-gate  *	append a file to the list of needed reconciliations
954*7c478bd9Sstevel@tonic-gate  *
955*7c478bd9Sstevel@tonic-gate  * parameters:
956*7c478bd9Sstevel@tonic-gate  *	pointer to file
957*7c478bd9Sstevel@tonic-gate  *
958*7c478bd9Sstevel@tonic-gate  * notes:
959*7c478bd9Sstevel@tonic-gate  *	when a request is appended to the reconciliation list,
960*7c478bd9Sstevel@tonic-gate  *	we fill in the full name.  We delayed this in hopes that
961*7c478bd9Sstevel@tonic-gate  *	it wouldn't be necessary (saving cycles and memory)
962*7c478bd9Sstevel@tonic-gate  *
963*7c478bd9Sstevel@tonic-gate  *	There is some funny business with modification times.
964*7c478bd9Sstevel@tonic-gate  *	In general, we queue files in order of the latest modification
965*7c478bd9Sstevel@tonic-gate  *	time so that propagations preserve relative ordering.  There
966*7c478bd9Sstevel@tonic-gate  *	are, however, a few important exceptions:
967*7c478bd9Sstevel@tonic-gate  *	    1.	all directory creations happen at time zero,
968*7c478bd9Sstevel@tonic-gate  *		so that they are created before any files can
969*7c478bd9Sstevel@tonic-gate  *		be added to them.
970*7c478bd9Sstevel@tonic-gate  *	    2.	all directory deletions happen at time infinity-depth,
971*7c478bd9Sstevel@tonic-gate  *		so that everything else can be removed before the
972*7c478bd9Sstevel@tonic-gate  *		directories themselves are removed.
973*7c478bd9Sstevel@tonic-gate  *	    3.	all file deletions happen at time infinity-depth
974*7c478bd9Sstevel@tonic-gate  *		so that (in renames) the links will preceed the unlinks.
975*7c478bd9Sstevel@tonic-gate  */
976*7c478bd9Sstevel@tonic-gate static void
977*7c478bd9Sstevel@tonic-gate queue_file(struct file *fp)
978*7c478bd9Sstevel@tonic-gate {	struct file **pp, *np;
979*7c478bd9Sstevel@tonic-gate 
980*7c478bd9Sstevel@tonic-gate #define	TIME_ZERO	0L		/* the earliest possible time	*/
981*7c478bd9Sstevel@tonic-gate #define	TIME_LONG	0x7FFFFFFF	/* the latest possible time	*/
982*7c478bd9Sstevel@tonic-gate 
983*7c478bd9Sstevel@tonic-gate 	/*
984*7c478bd9Sstevel@tonic-gate 	 * figure out the modification time for sequencing purposes
985*7c478bd9Sstevel@tonic-gate 	 */
986*7c478bd9Sstevel@tonic-gate 	if ((fp->f_srcdiffs|fp->f_dstdiffs) & D_DELETE) {
987*7c478bd9Sstevel@tonic-gate 		/*
988*7c478bd9Sstevel@tonic-gate 		 * deletions are performed last, and depth first
989*7c478bd9Sstevel@tonic-gate 		 */
990*7c478bd9Sstevel@tonic-gate 		fp->f_modtime = TIME_LONG - fp->f_depth;
991*7c478bd9Sstevel@tonic-gate 	} else if (fp->f_info[OPT_SRC].f_type != S_IFDIR &&
992*7c478bd9Sstevel@tonic-gate 	    fp->f_info[OPT_DST].f_type != S_IFDIR) {
993*7c478bd9Sstevel@tonic-gate 		/*
994*7c478bd9Sstevel@tonic-gate 		 * for most files we use the latest mod time
995*7c478bd9Sstevel@tonic-gate 		 */
996*7c478bd9Sstevel@tonic-gate 		fp->f_modtime = fp->f_info[OPT_SRC].f_modtime;
997*7c478bd9Sstevel@tonic-gate 		fp->f_modns   = fp->f_info[OPT_SRC].f_modns;
998*7c478bd9Sstevel@tonic-gate 		if (fp->f_modtime < fp->f_info[OPT_DST].f_modtime) {
999*7c478bd9Sstevel@tonic-gate 			fp->f_modtime = fp->f_info[OPT_DST].f_modtime;
1000*7c478bd9Sstevel@tonic-gate 			fp->f_modns   = fp->f_info[OPT_DST].f_modns;
1001*7c478bd9Sstevel@tonic-gate 		}
1002*7c478bd9Sstevel@tonic-gate 	} else {
1003*7c478bd9Sstevel@tonic-gate 		/*
1004*7c478bd9Sstevel@tonic-gate 		 * new directory creations need to happen before anything
1005*7c478bd9Sstevel@tonic-gate 		 * else and are automatically sequenced in traversal order
1006*7c478bd9Sstevel@tonic-gate 		 */
1007*7c478bd9Sstevel@tonic-gate 		fp->f_modtime = TIME_ZERO;
1008*7c478bd9Sstevel@tonic-gate 	}
1009*7c478bd9Sstevel@tonic-gate 
1010*7c478bd9Sstevel@tonic-gate 	/*
1011*7c478bd9Sstevel@tonic-gate 	 * insertion is time ordered, and for equal times,
1012*7c478bd9Sstevel@tonic-gate 	 * insertions is in (pre-order) traversal order
1013*7c478bd9Sstevel@tonic-gate 	 */
1014*7c478bd9Sstevel@tonic-gate 	for (pp = &changes; (np = *pp) != 0; pp = &np->f_rnext) {
1015*7c478bd9Sstevel@tonic-gate 		if (fp->f_modtime > np->f_modtime)
1016*7c478bd9Sstevel@tonic-gate 			continue;
1017*7c478bd9Sstevel@tonic-gate 		if (fp->f_modtime < np->f_modtime)
1018*7c478bd9Sstevel@tonic-gate 			break;
1019*7c478bd9Sstevel@tonic-gate 		if (fp->f_modns < np->f_modns)
1020*7c478bd9Sstevel@tonic-gate 			break;
1021*7c478bd9Sstevel@tonic-gate 	}
1022*7c478bd9Sstevel@tonic-gate 
1023*7c478bd9Sstevel@tonic-gate 	fp->f_fullname = strdup(get_name(fp));
1024*7c478bd9Sstevel@tonic-gate 	fp->f_rnext = np;
1025*7c478bd9Sstevel@tonic-gate 	*pp = fp;
1026*7c478bd9Sstevel@tonic-gate }
1027*7c478bd9Sstevel@tonic-gate 
1028*7c478bd9Sstevel@tonic-gate 
1029*7c478bd9Sstevel@tonic-gate /*
1030*7c478bd9Sstevel@tonic-gate  * routines:
1031*7c478bd9Sstevel@tonic-gate  *	push_name/pop_name/get_name
1032*7c478bd9Sstevel@tonic-gate  *
1033*7c478bd9Sstevel@tonic-gate  * purpose:
1034*7c478bd9Sstevel@tonic-gate  *	maintain a name stack so we can form name of a particular file
1035*7c478bd9Sstevel@tonic-gate  *	as the concatenation of all of the names between it and the
1036*7c478bd9Sstevel@tonic-gate  *	(know to be fully qualified) base directory.
1037*7c478bd9Sstevel@tonic-gate  *
1038*7c478bd9Sstevel@tonic-gate  * notes:
1039*7c478bd9Sstevel@tonic-gate  *	we go to this trouble because most files never change and
1040*7c478bd9Sstevel@tonic-gate  *	so we don't need to associate full names with every one.
1041*7c478bd9Sstevel@tonic-gate  *	This stack is maintained during analysis, and if we decide
1042*7c478bd9Sstevel@tonic-gate  *	to add a file to the reconciliation list, we can use the
1043*7c478bd9Sstevel@tonic-gate  *	stack to generate a fully qualified name at that time.
1044*7c478bd9Sstevel@tonic-gate  *
1045*7c478bd9Sstevel@tonic-gate  *	we compress out '/./' when we return a name.  Given that the
1046*7c478bd9Sstevel@tonic-gate  *	stack was built by a tree walk, the only place a /./ should
1047*7c478bd9Sstevel@tonic-gate  *	appear is at the first level after the base ... but there
1048*7c478bd9Sstevel@tonic-gate  *	are legitimate ways for them to appear there.
1049*7c478bd9Sstevel@tonic-gate  *
1050*7c478bd9Sstevel@tonic-gate  *	these names can get deep, so we dynamically size our name buffer
1051*7c478bd9Sstevel@tonic-gate  */
1052*7c478bd9Sstevel@tonic-gate static const char *namestack[ MAX_DEPTH + 1 ];
1053*7c478bd9Sstevel@tonic-gate static int namedepth = 0;
1054*7c478bd9Sstevel@tonic-gate static int namelen = 0;
1055*7c478bd9Sstevel@tonic-gate 
1056*7c478bd9Sstevel@tonic-gate void
1057*7c478bd9Sstevel@tonic-gate push_name(const char *name)
1058*7c478bd9Sstevel@tonic-gate {
1059*7c478bd9Sstevel@tonic-gate 	namestack[ namedepth++ ] = name;
1060*7c478bd9Sstevel@tonic-gate 	namelen += 2 + strlen(name);
1061*7c478bd9Sstevel@tonic-gate 
1062*7c478bd9Sstevel@tonic-gate 	/* make sure we don't overflow our name stack	*/
1063*7c478bd9Sstevel@tonic-gate 	if (namedepth >= MAX_DEPTH) {
1064*7c478bd9Sstevel@tonic-gate 		fprintf(stderr, gettext(ERR_deep), name);
1065*7c478bd9Sstevel@tonic-gate 		exit(ERR_OTHER);
1066*7c478bd9Sstevel@tonic-gate 	}
1067*7c478bd9Sstevel@tonic-gate }
1068*7c478bd9Sstevel@tonic-gate 
1069*7c478bd9Sstevel@tonic-gate void
1070*7c478bd9Sstevel@tonic-gate pop_name(void)
1071*7c478bd9Sstevel@tonic-gate {
1072*7c478bd9Sstevel@tonic-gate 	namelen -= 2 + strlen(namestack[--namedepth]);
1073*7c478bd9Sstevel@tonic-gate 	namestack[ namedepth ] = 0;
1074*7c478bd9Sstevel@tonic-gate 
1075*7c478bd9Sstevel@tonic-gate #ifdef	DBG_ERRORS
1076*7c478bd9Sstevel@tonic-gate 	/* just a little sanity check here	*/
1077*7c478bd9Sstevel@tonic-gate 	if (namedepth <= 0) {
1078*7c478bd9Sstevel@tonic-gate 		if (namedepth < 0) {
1079*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, "ASSERTION FAILURE: namedepth < 0\n");
1080*7c478bd9Sstevel@tonic-gate 			exit(ERR_OTHER);
1081*7c478bd9Sstevel@tonic-gate 		} else if (namelen != 0) {
1082*7c478bd9Sstevel@tonic-gate 			fprintf(stderr, "ASSERTION FAILURE: namelen != 0\n");
1083*7c478bd9Sstevel@tonic-gate 			exit(ERR_OTHER);
1084*7c478bd9Sstevel@tonic-gate 		}
1085*7c478bd9Sstevel@tonic-gate 	}
1086*7c478bd9Sstevel@tonic-gate #endif
1087*7c478bd9Sstevel@tonic-gate }
1088*7c478bd9Sstevel@tonic-gate 
1089*7c478bd9Sstevel@tonic-gate char
1090*7c478bd9Sstevel@tonic-gate *get_name(struct file *fp)
1091*7c478bd9Sstevel@tonic-gate {	int i;
1092*7c478bd9Sstevel@tonic-gate 	static char *namebuf = 0;
1093*7c478bd9Sstevel@tonic-gate 	static int buflen = 0;
1094*7c478bd9Sstevel@tonic-gate 
1095*7c478bd9Sstevel@tonic-gate 	/* make sure we have an adequate buffer	*/
1096*7c478bd9Sstevel@tonic-gate 	i = namelen + 1 + strlen(fp->f_name);
1097*7c478bd9Sstevel@tonic-gate 	if (buflen < i) {
1098*7c478bd9Sstevel@tonic-gate 		for (buflen = MAX_PATH; buflen < i; buflen += MAX_NAME);
1099*7c478bd9Sstevel@tonic-gate 		namebuf = (char *) realloc(namebuf, buflen);
1100*7c478bd9Sstevel@tonic-gate 	}
1101*7c478bd9Sstevel@tonic-gate 
1102*7c478bd9Sstevel@tonic-gate 	/* assemble the name	*/
1103*7c478bd9Sstevel@tonic-gate 	namebuf[0] = 0;
1104*7c478bd9Sstevel@tonic-gate 	for (i = 0; i < namedepth; i++) {
1105*7c478bd9Sstevel@tonic-gate 		if (strcmp(namestack[i], ".")) {
1106*7c478bd9Sstevel@tonic-gate 			strcat(namebuf, namestack[i]);
1107*7c478bd9Sstevel@tonic-gate 			strcat(namebuf, "/");
1108*7c478bd9Sstevel@tonic-gate 		}
1109*7c478bd9Sstevel@tonic-gate 	}
1110*7c478bd9Sstevel@tonic-gate 
1111*7c478bd9Sstevel@tonic-gate 	strcat(namebuf, fp->f_name);
1112*7c478bd9Sstevel@tonic-gate 
1113*7c478bd9Sstevel@tonic-gate 	return (namebuf);
1114*7c478bd9Sstevel@tonic-gate }
1115