1/*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License, Version 1.0 only
6 * (the "License").  You may not use this file except in compliance
7 * with the License.
8 *
9 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10 * or http://www.opensolaris.org/os/licensing.
11 * See the License for the specific language governing permissions
12 * and limitations under the License.
13 *
14 * When distributing Covered Code, include this CDDL HEADER in each
15 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16 * If applicable, add the following below this CDDL HEADER, with the
17 * fields enclosed by brackets "[]" replaced with your own identifying
18 * information: Portions Copyright [yyyy] [name of copyright owner]
19 *
20 * CDDL HEADER END
21 */
22/*
23 * Copyright (c) 1995 Sun Microsystems, Inc.  All Rights Reserved
24 *
25 * module:
26 *	rename.c
27 *
28 * purpose:
29 *	routines to determine whether or not any renames have taken place
30 *	and note them (for reconciliation) if we find any
31 *
32 * contents:
33 *	find_renames . look for files that have been renamed
34 *	find_oldname . (static) find the file we were renamed from
35 *	note_rename .. (static) note the rename for subsequent reconciliation
36 *
37 * notes:
38 *	the reason renames warrant special attention is because the tree
39 *	we have constructed is name based, and a directory rename can
40 *	appear as zillions of changes.  We attempt to find and deal with
41 *	renames prior to doing the difference analysis.
42 *
43 *	The only case we deal with here is simple renames.  If new links
44 *	have been created beneath other directories (i.e. a file has been
45 *	moved from one directory to another), the generalized link finding
46 *	stuff will deal with it.
47 *
48 *	This is still under construction, and to completely deal with
49 *	directory renames may require some non-trivial tree restructuring.
50 *	There is a whole design note on this subject.  In the mean time,
51 *	we still detect file renames, so that the user will see them
52 *	reported as "mv"s rather than as "ln"s and "rm"s.  Until directory
53 *	renames are fully implemented, they will instead be handled as
54 *	mkdirs, massive links and unlinks, and rmdirs.
55 */
56#ident	"%W%	%E% SMI"
57
58#include <stdio.h>
59
60#include "filesync.h"
61#include "database.h"
62
63
64/* local routines */
65static struct file *find_oldname(struct file *, struct file *, side_t);
66static errmask_t
67	note_rename(struct file *, struct file *, struct file *, side_t);
68
69/*
70 * routine:
71 *	find_renames
72 *
73 * purpose:
74 *	recursively perform rename analysis on a directory
75 *
76 * parameters:
77 *	file node for the suspected directory
78 *
79 * returns:
80 *	error mask
81 *
82 * note:
83 *	the basic algorithm here is to search every directory
84 *	for files that have been newly created on one side,
85 *	and then look to see if they correspond to an identical
86 *	file that has been newly deleted on the same side.
87 */
88errmask_t
89find_renames(struct file *fp)
90{	struct file *np, *rp;
91	errmask_t errs = 0;
92	int stype, dtype, btype, side;
93
94	/* if this isn't a directory, there is nothing to analyze	*/
95	if (fp->f_files == 0)
96		return (0);
97
98	/* look for any files under this directory that may have been renamed */
99	for (np = fp->f_files; np; np = np->f_next) {
100		btype = np->f_info[OPT_BASE].f_type;
101		stype = np->f_info[OPT_SRC].f_type;
102		dtype = np->f_info[OPT_DST].f_type;
103
104		/* a rename must be a file that is new on only one side */
105		if (btype == 0 && stype != dtype && (!stype || !dtype)) {
106			side = stype ? OPT_SRC : OPT_DST;
107			rp = find_oldname(fp, np, side);
108			if (rp)
109				errs |= note_rename(fp, np, rp, side);
110		}
111	}
112
113	/* recursively examine all my children			*/
114	for (np = fp->f_files; np; np = np->f_next) {
115		errs |= find_renames(np);
116	}
117
118	return (errs);
119}
120
121/*
122 * routine:
123 *	find_oldname
124 *
125 * purpose:
126 *	to search for an old name for a newly discovered file
127 *
128 * parameters:
129 *	file node for the containing directory
130 *	file node for the new file
131 *	which side the rename is believed to have happened on
132 *
133 * returns:
134 *	pointer to likely previous file
135 *	0	no candidate found
136 *
137 * note:
138 *	this routine only deals with simple renames within a single
139 *	directory.
140 */
141static struct file *find_oldname(struct file *dirp, struct file *new,
142	side_t side)
143{	struct file *fp;
144	long maj, min;
145	ino_t inum;
146	off_t size;
147	side_t otherside = (side == OPT_SRC) ? OPT_DST : OPT_SRC;
148
149	/* figure out what we're looking for		*/
150	inum = new->f_info[side].f_ino;
151	maj  = new->f_info[side].f_d_maj;
152	min  = new->f_info[side].f_d_min;
153	size = new->f_info[side].f_size;
154
155	/*
156	 * search the same directory for any entry that might describe
157	 * the previous name of the new file.
158	 */
159	for (fp = dirp->f_files; fp; fp = fp->f_next) {
160		/* previous name on changed side must no longer exist	*/
161		if (fp->f_info[side].f_type != 0)
162			continue;
163
164		/* previous name on the other side must still exist	*/
165		if (fp->f_info[otherside].f_type == 0)
166			continue;
167
168		/* it must describe the same inode as the new file	*/
169		if (fp->f_info[OPT_BASE].f_type != new->f_info[side].f_type)
170			continue;	/* must be same type		*/
171		if (((side == OPT_SRC) ? fp->f_s_inum : fp->f_d_inum) != inum)
172			continue;	/* must be same inode #		*/
173		if (((side == OPT_SRC) ? fp->f_s_maj : fp->f_d_maj) != maj)
174			continue;	/* must be same major #		*/
175		if (((side == OPT_SRC) ? fp->f_s_min : fp->f_d_min) != min)
176			continue;	/* must be same minor #		*/
177
178		/*
179		 * occasionally a prompt delete and create can reuse the
180		 * same i-node in the same directory.  What we really
181		 * want is generation, but that isn't available just
182		 * yet, so our poor-man's approximation is the size.
183		 * There is little point in checking ownership and
184		 * modes, since the fact that it is in the same
185		 * directory strongly suggests that it is the same
186		 * user who is doing the deleting and creating.
187		 */
188		if (fp->f_info[OPT_BASE].f_size != size)
189			continue;
190
191		/* looks like we found a match				*/
192		return (fp);
193	}
194
195	/* no joy	*/
196	return (0);
197}
198
199/*
200 * routine:
201 *	note_rename
202 *
203 * purpose:
204 *	to record a discovered rename, so that the reconciliation
205 *	phase will deal with it as a rename rather than as link
206 *	followed by an unlink.
207 *
208 * parameters:
209 *	file node for the containing directory
210 *	file node for the new file
211 *	file node for the old file
212 *	which side the rename is believed to have happened on
213 *
214 * returns:
215 *	error mask
216 */
217static errmask_t
218note_rename(struct file *dirp, struct file *new,
219			struct file *old, side_t side)
220{
221	int dir;
222	errmask_t errs = 0;
223	static char *sidenames[] = {"base", "source", "dest"};
224
225	dir = new->f_info[side].f_type == S_IFDIR;
226
227	if (opt_debug & DBG_ANAL)
228		fprintf(stderr, "ANAL: NOTE RENAME %s %s/%s -> %s/%s on %s\n",
229			dir ? "directory" : "file",
230			dirp->f_name, old->f_name, dirp->f_name, new->f_name,
231			sidenames[side]);
232
233	/* FIX: we don't deal with directory renames yet	*/
234	if (dir)
235		return (0);
236
237	/* note that a rename has taken place			*/
238	if (side == OPT_SRC) {
239		new->f_srcdiffs |= D_RENAME_TO;
240		old->f_srcdiffs |= D_RENAME_FROM;
241	} else {
242		new->f_dstdiffs |= D_RENAME_TO;
243		old->f_dstdiffs |= D_RENAME_FROM;
244	}
245
246	/* put a link to the old name in the new name		*/
247	new->f_previous = old;
248
249	/* for most files, there is nothing else we have to do	*/
250	if (!dir)
251		return (errs);
252
253	/*
254	 * FIX ... someday we are going to have to merge the old and
255	 *	   new children into a single tree, but there are
256	 *	   horrendous backout problems if we are unable to
257	 *	   do the mvdir, so I have postponed this feature.
258	 */
259
260	return (errs);
261}
262