Import Upstream version 1.8.5
[hcoop/debian/openafs.git] / src / afs / VNOPS / afs_vnop_rename.c
1 /*
2 * Copyright 2000, International Business Machines Corporation and others.
3 * All Rights Reserved.
4 *
5 * This software has been released under the terms of the IBM Public
6 * License. For details, see the LICENSE file in the top-level source
7 * directory or online at http://www.openafs.org/dl/license10.html
8 */
9
10 /*
11 * Implements:
12 * afsrename
13 * afs_rename
14 *
15 */
16
17 #include <afsconfig.h>
18 #include "afs/param.h"
19
20
21 #include "afs/sysincludes.h" /* Standard vendor system headers */
22 #include "afsincludes.h" /* Afs-based standard headers */
23 #include "afs/afs_stats.h" /* statistics */
24 #include "afs/afs_cbqueue.h"
25 #include "afs/nfsclient.h"
26 #include "afs/afs_osidnlc.h"
27
28 extern afs_rwlock_t afs_xcbhash;
29
30 /* Note that we don't set CDirty here, this is OK because the rename
31 * RPC is called synchronously. */
32
33 int
34 afsrename(struct vcache *aodp, char *aname1, struct vcache *andp,
35 char *aname2, afs_ucred_t *acred, struct vrequest *areq)
36 {
37 struct afs_conn *tc;
38 afs_int32 code = 0;
39 afs_int32 returnCode;
40 int oneDir, doLocally;
41 afs_size_t offset, len;
42 struct VenusFid unlinkFid, fileFid;
43 struct vcache *tvc;
44 struct dcache *tdc1, *tdc2;
45 struct AFSFetchStatus *OutOldDirStatus, *OutNewDirStatus;
46 struct AFSVolSync tsync;
47 struct rx_connection *rxconn;
48 XSTATS_DECLS;
49 AFS_STATCNT(afs_rename);
50 afs_Trace4(afs_iclSetp, CM_TRACE_RENAME, ICL_TYPE_POINTER, aodp,
51 ICL_TYPE_STRING, aname1, ICL_TYPE_POINTER, andp,
52 ICL_TYPE_STRING, aname2);
53
54 OutOldDirStatus = osi_AllocSmallSpace(sizeof(struct AFSFetchStatus));
55 OutNewDirStatus = osi_AllocSmallSpace(sizeof(struct AFSFetchStatus));
56
57 if (strlen(aname1) > AFSNAMEMAX || strlen(aname2) > AFSNAMEMAX) {
58 code = ENAMETOOLONG;
59 goto done;
60 }
61
62 /* verify the latest versions of the stat cache entries */
63 tagain:
64 code = afs_VerifyVCache(aodp, areq);
65 if (code)
66 goto done;
67 code = afs_VerifyVCache(andp, areq);
68 if (code)
69 goto done;
70
71 /* lock in appropriate order, after some checks */
72 if (aodp->f.fid.Cell != andp->f.fid.Cell
73 || aodp->f.fid.Fid.Volume != andp->f.fid.Fid.Volume) {
74 code = EXDEV;
75 goto done;
76 }
77 oneDir = 0;
78 code = 0;
79 if (andp->f.fid.Fid.Vnode == aodp->f.fid.Fid.Vnode) {
80 if (!strcmp(aname1, aname2)) {
81 /* Same directory and same name; this is a noop and just return success
82 * to save cycles and follow posix standards */
83
84 code = 0;
85 goto done;
86 }
87
88 if (AFS_IS_DISCONNECTED && !AFS_IS_DISCON_RW) {
89 code = ENETDOWN;
90 goto done;
91 }
92
93 ObtainWriteLock(&andp->lock, 147);
94 tdc1 = afs_GetDCache(aodp, (afs_size_t) 0, areq, &offset, &len, 0);
95 if (!tdc1) {
96 code = EIO;
97 } else {
98 ObtainWriteLock(&tdc1->lock, 643);
99 }
100 tdc2 = tdc1;
101 oneDir = 1; /* only one dude locked */
102 } else if ((andp->f.states & CRO) || (aodp->f.states & CRO)) {
103 code = EROFS;
104 goto done;
105 } else if (andp->f.fid.Fid.Vnode < aodp->f.fid.Fid.Vnode) {
106 ObtainWriteLock(&andp->lock, 148); /* lock smaller one first */
107 ObtainWriteLock(&aodp->lock, 149);
108 tdc2 = afs_FindDCache(andp, (afs_size_t) 0);
109 if (tdc2)
110 ObtainWriteLock(&tdc2->lock, 644);
111 tdc1 = afs_GetDCache(aodp, (afs_size_t) 0, areq, &offset, &len, 0);
112 if (tdc1)
113 ObtainWriteLock(&tdc1->lock, 645);
114 else
115 code = EIO;
116 } else {
117 ObtainWriteLock(&aodp->lock, 150); /* lock smaller one first */
118 ObtainWriteLock(&andp->lock, 557);
119 tdc1 = afs_GetDCache(aodp, (afs_size_t) 0, areq, &offset, &len, 0);
120 if (tdc1)
121 ObtainWriteLock(&tdc1->lock, 646);
122 else
123 code = EIO;
124 tdc2 = afs_FindDCache(andp, (afs_size_t) 0);
125 if (tdc2)
126 ObtainWriteLock(&tdc2->lock, 647);
127 }
128
129 osi_dnlc_remove(aodp, aname1, 0);
130 osi_dnlc_remove(andp, aname2, 0);
131
132 /*
133 * Make sure that the data in the cache is current. We may have
134 * received a callback while we were waiting for the write lock.
135 */
136 if (tdc1) {
137 if (!(aodp->f.states & CStatd)
138 || !hsame(aodp->f.m.DataVersion, tdc1->f.versionNo)) {
139
140 ReleaseWriteLock(&aodp->lock);
141 if (!oneDir) {
142 if (tdc2) {
143 ReleaseWriteLock(&tdc2->lock);
144 afs_PutDCache(tdc2);
145 }
146 ReleaseWriteLock(&andp->lock);
147 }
148 ReleaseWriteLock(&tdc1->lock);
149 afs_PutDCache(tdc1);
150 goto tagain;
151 }
152 }
153
154 if (code == 0)
155 code = afs_dir_Lookup(tdc1, aname1, &fileFid.Fid);
156 if (code) {
157 if (tdc1) {
158 ReleaseWriteLock(&tdc1->lock);
159 afs_PutDCache(tdc1);
160 }
161 ReleaseWriteLock(&aodp->lock);
162 if (!oneDir) {
163 if (tdc2) {
164 ReleaseWriteLock(&tdc2->lock);
165 afs_PutDCache(tdc2);
166 }
167 ReleaseWriteLock(&andp->lock);
168 }
169 goto done;
170 }
171
172 if (!AFS_IS_DISCON_RW) {
173 /* Connected. */
174 do {
175 tc = afs_Conn(&aodp->f.fid, areq, SHARED_LOCK, &rxconn);
176 if (tc) {
177 XSTATS_START_TIME(AFS_STATS_FS_RPCIDX_RENAME);
178 RX_AFS_GUNLOCK();
179 code =
180 RXAFS_Rename(rxconn,
181 (struct AFSFid *)&aodp->f.fid.Fid,
182 aname1,
183 (struct AFSFid *)&andp->f.fid.Fid,
184 aname2,
185 OutOldDirStatus,
186 OutNewDirStatus,
187 &tsync);
188 RX_AFS_GLOCK();
189 XSTATS_END_TIME;
190 } else
191 code = -1;
192
193 } while (afs_Analyze
194 (tc, rxconn, code, &andp->f.fid, areq, AFS_STATS_FS_RPCIDX_RENAME,
195 SHARED_LOCK, NULL));
196
197 } else {
198 /* Disconnected. */
199
200 /* Seek moved file vcache. */
201 fileFid.Cell = aodp->f.fid.Cell;
202 fileFid.Fid.Volume = aodp->f.fid.Fid.Volume;
203 ObtainSharedLock(&afs_xvcache, 754);
204 tvc = afs_FindVCache(&fileFid, 0 , 1);
205 ReleaseSharedLock(&afs_xvcache);
206
207 if (tvc) {
208 /* XXX - We're locking this vcache whilst holding dcaches. Ooops */
209 ObtainWriteLock(&tvc->lock, 750);
210 if (!(tvc->f.ddirty_flags & (VDisconRename|VDisconCreate))) {
211 /* If the vnode was created locally, then we don't care
212 * about recording the rename - we'll do it automatically
213 * on replay. If we've already renamed, we've already stored
214 * the required information about where we came from.
215 */
216
217 if (!aodp->f.shadow.vnode) {
218 /* Make shadow copy of parent dir only. */
219 afs_MakeShadowDir(aodp, tdc1);
220 }
221
222 /* Save old parent dir fid so it will be searchable
223 * in the shadow dir.
224 */
225 tvc->f.oldParent.vnode = aodp->f.fid.Fid.Vnode;
226 tvc->f.oldParent.unique = aodp->f.fid.Fid.Unique;
227
228 afs_DisconAddDirty(tvc,
229 VDisconRename
230 | (oneDir ? VDisconRenameSameDir:0),
231 1);
232 }
233
234 ReleaseWriteLock(&tvc->lock);
235 afs_PutVCache(tvc);
236 } else {
237 code = ENETDOWN;
238 } /* if (tvc) */
239 } /* if !(AFS_IS_DISCON_RW)*/
240 returnCode = code; /* remember for later */
241
242 /* Now we try to do things locally. This is really loathsome code. */
243 unlinkFid.Fid.Vnode = 0;
244 if (code == 0) {
245 /* In any event, we don't really care if the data (tdc2) is not
246 * in the cache; if it isn't, we won't do the update locally. */
247 /* see if version numbers increased properly */
248 doLocally = 1;
249 if (!AFS_IS_DISCON_RW) {
250 if (oneDir) {
251 /* number increases by 1 for whole rename operation */
252 if (!afs_LocalHero(aodp, tdc1, OutOldDirStatus, 1)) {
253 doLocally = 0;
254 }
255 } else {
256 /* two separate dirs, each increasing by 1 */
257 if (!afs_LocalHero(aodp, tdc1, OutOldDirStatus, 1))
258 doLocally = 0;
259 if (!afs_LocalHero(andp, tdc2, OutNewDirStatus, 1))
260 doLocally = 0;
261 if (!doLocally) {
262 if (tdc1) {
263 ZapDCE(tdc1);
264 DZap(tdc1);
265 }
266 if (tdc2) {
267 ZapDCE(tdc2);
268 DZap(tdc2);
269 }
270 }
271 }
272 } /* if (!AFS_IS_DISCON_RW) */
273
274 /* now really do the work */
275 if (doLocally) {
276 /* first lookup the fid of the dude we're moving */
277 code = afs_dir_Lookup(tdc1, aname1, &fileFid.Fid);
278 if (code == 0) {
279 /* delete the source */
280 code = afs_dir_Delete(tdc1, aname1);
281 }
282 /* first see if target is there */
283 if (code == 0
284 && afs_dir_Lookup(tdc2, aname2,
285 &unlinkFid.Fid) == 0) {
286 /* target already exists, and will be unlinked by server */
287 code = afs_dir_Delete(tdc2, aname2);
288 }
289 if (code == 0) {
290 ObtainWriteLock(&afs_xdcache, 292);
291 code = afs_dir_Create(tdc2, aname2, &fileFid.Fid);
292 ReleaseWriteLock(&afs_xdcache);
293 }
294 if (code != 0) {
295 ZapDCE(tdc1);
296 DZap(tdc1);
297 if (!oneDir) {
298 ZapDCE(tdc2);
299 DZap(tdc2);
300 }
301 }
302 }
303
304
305 /* update dir link counts */
306 if (AFS_IS_DISCON_RW) {
307 if (!oneDir) {
308 aodp->f.m.LinkCount--;
309 andp->f.m.LinkCount++;
310 }
311 /* If we're in the same directory, link count doesn't change */
312 } else {
313 aodp->f.m.LinkCount = OutOldDirStatus->LinkCount;
314 if (!oneDir)
315 andp->f.m.LinkCount = OutNewDirStatus->LinkCount;
316 }
317
318 } else { /* operation failed (code != 0) */
319 if (code < 0) {
320 /* if failed, server might have done something anyway, and
321 * assume that we know about it */
322 ObtainWriteLock(&afs_xcbhash, 498);
323 afs_StaleVCacheFlags(aodp, AFS_STALEVC_CBLOCKED, 0);
324 afs_StaleVCacheFlags(andp, AFS_STALEVC_CBLOCKED, 0);
325 ReleaseWriteLock(&afs_xcbhash);
326 }
327 }
328
329 /* release locks */
330 if (tdc1) {
331 ReleaseWriteLock(&tdc1->lock);
332 afs_PutDCache(tdc1);
333 }
334
335 if ((!oneDir) && tdc2) {
336 ReleaseWriteLock(&tdc2->lock);
337 afs_PutDCache(tdc2);
338 }
339
340 ReleaseWriteLock(&aodp->lock);
341
342 if (!oneDir) {
343 ReleaseWriteLock(&andp->lock);
344 }
345
346 if (returnCode) {
347 code = returnCode;
348 goto done;
349 }
350
351 /* now, some more details. if unlinkFid.Fid.Vnode then we should decrement
352 * the link count on this file. Note that if fileFid is a dir, then we don't
353 * have to invalidate its ".." entry, since its DataVersion # should have
354 * changed. However, interface is not good enough to tell us the
355 * *file*'s new DataVersion, so we're stuck. Our hack: delete mark
356 * the data as having an "unknown" version (effectively discarding the ".."
357 * entry */
358 if (unlinkFid.Fid.Vnode) {
359
360 unlinkFid.Fid.Volume = aodp->f.fid.Fid.Volume;
361 unlinkFid.Cell = aodp->f.fid.Cell;
362 tvc = NULL;
363 if (!unlinkFid.Fid.Unique) {
364 tvc = afs_LookupVCache(&unlinkFid, areq, NULL, aodp, aname1);
365 }
366 if (!tvc) /* lookup failed or wasn't called */
367 tvc = afs_GetVCache(&unlinkFid, areq, NULL, NULL);
368
369 if (tvc) {
370 ObtainWriteLock(&tvc->lock, 151);
371 tvc->f.m.LinkCount--;
372 tvc->f.states &= ~CUnique; /* For the dfs xlator */
373 if (tvc->f.m.LinkCount == 0 && !osi_Active(tvc)) {
374 /* if this was last guy (probably) discard from cache.
375 * We have to be careful to not get rid of the stat
376 * information, since otherwise operations will start
377 * failing even if the file was still open (or
378 * otherwise active), and the server no longer has the
379 * info. If the file still has valid links, we'll get
380 * a break-callback msg from the server, so it doesn't
381 * matter that we don't discard the status info */
382 if (!AFS_NFSXLATORREQ(acred))
383 afs_TryToSmush(tvc, acred, 0);
384 }
385 ReleaseWriteLock(&tvc->lock);
386 afs_PutVCache(tvc);
387 }
388 }
389
390 /* now handle ".." invalidation */
391 if (!oneDir) {
392 fileFid.Fid.Volume = aodp->f.fid.Fid.Volume;
393 fileFid.Cell = aodp->f.fid.Cell;
394 if (!fileFid.Fid.Unique)
395 tvc = afs_LookupVCache(&fileFid, areq, NULL, andp, aname2);
396 else
397 tvc = afs_GetVCache(&fileFid, areq, NULL, (struct vcache *)0);
398 if (tvc && (vType(tvc) == VDIR)) {
399 ObtainWriteLock(&tvc->lock, 152);
400 tdc1 = afs_FindDCache(tvc, (afs_size_t) 0);
401 if (tdc1) {
402 if (AFS_IS_DISCON_RW) {
403 /* If disconnected, we need to fix (not discard) the "..".*/
404 afs_dir_ChangeFid(tdc1,
405 "..",
406 &aodp->f.fid.Fid.Vnode,
407 &andp->f.fid.Fid.Vnode);
408 } else {
409 ObtainWriteLock(&tdc1->lock, 648);
410 ZapDCE(tdc1); /* mark as unknown */
411 DZap(tdc1);
412 ReleaseWriteLock(&tdc1->lock);
413 afs_PutDCache(tdc1); /* put it back */
414 }
415 }
416 osi_dnlc_remove(tvc, "..", 0);
417 ReleaseWriteLock(&tvc->lock);
418 afs_PutVCache(tvc);
419 } else if (AFS_IS_DISCON_RW && tvc && (vType(tvc) == VREG)) {
420 /* XXX - Should tvc not get locked here? */
421 tvc->f.parent.vnode = andp->f.fid.Fid.Vnode;
422 tvc->f.parent.unique = andp->f.fid.Fid.Unique;
423 } else if (tvc) {
424 /* True we shouldn't come here since tvc SHOULD be a dir, but we
425 * 'syntactically' need to unless we change the 'if' above...
426 */
427 afs_PutVCache(tvc);
428 }
429 }
430 code = returnCode;
431 done:
432 osi_FreeSmallSpace(OutOldDirStatus);
433 osi_FreeSmallSpace(OutNewDirStatus);
434 return code;
435 }
436
437 int
438 #if defined(AFS_SGI_ENV)
439 afs_rename(OSI_VC_DECL(aodp), char *aname1, struct vcache *andp, char *aname2, struct pathname *npnp, afs_ucred_t *acred)
440 #else
441 afs_rename(OSI_VC_DECL(aodp), char *aname1, struct vcache *andp, char *aname2, afs_ucred_t *acred)
442 #endif
443 {
444 afs_int32 code;
445 struct afs_fakestat_state ofakestate;
446 struct afs_fakestat_state nfakestate;
447 struct vrequest *treq = NULL;
448 OSI_VC_CONVERT(aodp);
449
450 code = afs_CreateReq(&treq, acred);
451 if (code)
452 return code;
453
454 afs_InitFakeStat(&ofakestate);
455 afs_InitFakeStat(&nfakestate);
456
457 AFS_DISCON_LOCK();
458
459 code = afs_EvalFakeStat(&aodp, &ofakestate, treq);
460 if (code)
461 goto done;
462 code = afs_EvalFakeStat(&andp, &nfakestate, treq);
463 if (code)
464 goto done;
465 code = afsrename(aodp, aname1, andp, aname2, acred, treq);
466 done:
467 afs_PutFakeStat(&ofakestate);
468 afs_PutFakeStat(&nfakestate);
469
470 AFS_DISCON_UNLOCK();
471
472 code = afs_CheckCode(code, treq, 25);
473 afs_DestroyReq(treq);
474 return code;
475 }