xref: /illumos-gate/usr/src/cmd/filesync/rename.c (revision 069e6b7e31ba5dcbc5441b98af272714d9a5455c)
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 */
65 static struct file *find_oldname(struct file *, struct file *, side_t);
66 static 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  */
88 errmask_t
89 find_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  */
141 static 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  */
217 static errmask_t
218 note_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