Merge branch 'security-master' into HEAD
[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(AFS_SMALLOCSIZ);
55     OutNewDirStatus = osi_AllocSmallSpace(AFS_SMALLOCSIZ);
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 = ENOENT;
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 = ENOENT;
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 = ENOENT;
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 = ENOENT;
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_DequeueCallback(aodp);
324             afs_DequeueCallback(andp);
325             andp->f.states &= ~CStatd;
326             aodp->f.states &= ~CStatd;
327             ReleaseWriteLock(&afs_xcbhash);
328             osi_dnlc_purgedp(andp);
329             osi_dnlc_purgedp(aodp);
330         }
331     }
332
333     /* release locks */
334     if (tdc1) {
335         ReleaseWriteLock(&tdc1->lock);
336         afs_PutDCache(tdc1);
337     }
338
339     if ((!oneDir) && tdc2) {
340         ReleaseWriteLock(&tdc2->lock);
341         afs_PutDCache(tdc2);
342     }
343
344     ReleaseWriteLock(&aodp->lock);
345
346     if (!oneDir) {
347         ReleaseWriteLock(&andp->lock);
348     }
349
350     if (returnCode) {
351         code = returnCode;
352         goto done;
353     }
354
355     /* now, some more details.  if unlinkFid.Fid.Vnode then we should decrement
356      * the link count on this file.  Note that if fileFid is a dir, then we don't
357      * have to invalidate its ".." entry, since its DataVersion # should have
358      * changed. However, interface is not good enough to tell us the
359      * *file*'s new DataVersion, so we're stuck.  Our hack: delete mark
360      * the data as having an "unknown" version (effectively discarding the ".."
361      * entry */
362     if (unlinkFid.Fid.Vnode) {
363
364         unlinkFid.Fid.Volume = aodp->f.fid.Fid.Volume;
365         unlinkFid.Cell = aodp->f.fid.Cell;
366         tvc = NULL;
367         if (!unlinkFid.Fid.Unique) {
368             tvc = afs_LookupVCache(&unlinkFid, areq, NULL, aodp, aname1);
369         }
370         if (!tvc)               /* lookup failed or wasn't called */
371             tvc = afs_GetVCache(&unlinkFid, areq, NULL, NULL);
372
373         if (tvc) {
374 #ifdef AFS_BOZONLOCK_ENV
375             afs_BozonLock(&tvc->pvnLock, tvc);  /* Since afs_TryToSmush will do a pvn_vptrunc */
376 #endif
377             ObtainWriteLock(&tvc->lock, 151);
378             tvc->f.m.LinkCount--;
379             tvc->f.states &= ~CUnique;  /* For the dfs xlator */
380             if (tvc->f.m.LinkCount == 0 && !osi_Active(tvc)) {
381                 /* if this was last guy (probably) discard from cache.
382                  * We have to be careful to not get rid of the stat
383                  * information, since otherwise operations will start
384                  * failing even if the file was still open (or
385                  * otherwise active), and the server no longer has the
386                  * info.  If the file still has valid links, we'll get
387                  * a break-callback msg from the server, so it doesn't
388                  * matter that we don't discard the status info */
389                 if (!AFS_NFSXLATORREQ(acred))
390                     afs_TryToSmush(tvc, acred, 0);
391             }
392             ReleaseWriteLock(&tvc->lock);
393 #ifdef AFS_BOZONLOCK_ENV
394             afs_BozonUnlock(&tvc->pvnLock, tvc);
395 #endif
396             afs_PutVCache(tvc);
397         }
398     }
399
400     /* now handle ".." invalidation */
401     if (!oneDir) {
402         fileFid.Fid.Volume = aodp->f.fid.Fid.Volume;
403         fileFid.Cell = aodp->f.fid.Cell;
404         if (!fileFid.Fid.Unique)
405             tvc = afs_LookupVCache(&fileFid, areq, NULL, andp, aname2);
406         else
407             tvc = afs_GetVCache(&fileFid, areq, NULL, (struct vcache *)0);
408         if (tvc && (vType(tvc) == VDIR)) {
409             ObtainWriteLock(&tvc->lock, 152);
410             tdc1 = afs_FindDCache(tvc, (afs_size_t) 0);
411             if (tdc1) {
412                 if (AFS_IS_DISCON_RW) {
413                     /* If disconnected, we need to fix (not discard) the "..".*/
414                     afs_dir_ChangeFid(tdc1,
415                         "..",
416                         &aodp->f.fid.Fid.Vnode,
417                         &andp->f.fid.Fid.Vnode);
418                 } else {
419                     ObtainWriteLock(&tdc1->lock, 648);
420                     ZapDCE(tdc1);       /* mark as unknown */
421                     DZap(tdc1);
422                     ReleaseWriteLock(&tdc1->lock);
423                     afs_PutDCache(tdc1);        /* put it back */
424                 }
425             }
426             osi_dnlc_remove(tvc, "..", 0);
427             ReleaseWriteLock(&tvc->lock);
428             afs_PutVCache(tvc);
429         } else if (AFS_IS_DISCON_RW && tvc && (vType(tvc) == VREG)) {
430             /* XXX - Should tvc not get locked here? */
431             tvc->f.parent.vnode = andp->f.fid.Fid.Vnode;
432             tvc->f.parent.unique = andp->f.fid.Fid.Unique;
433         } else if (tvc) {
434             /* True we shouldn't come here since tvc SHOULD be a dir, but we
435              * 'syntactically' need to unless  we change the 'if' above...
436              */
437             afs_PutVCache(tvc);
438         }
439     }
440     code = returnCode;
441   done:
442     osi_FreeSmallSpace(OutOldDirStatus);
443     osi_FreeSmallSpace(OutNewDirStatus);
444     return code;
445 }
446
447 int
448 #if defined(AFS_SGI_ENV)
449 afs_rename(OSI_VC_DECL(aodp), char *aname1, struct vcache *andp, char *aname2, struct pathname *npnp, afs_ucred_t *acred)
450 #else
451 afs_rename(OSI_VC_DECL(aodp), char *aname1, struct vcache *andp, char *aname2, afs_ucred_t *acred)
452 #endif
453 {
454     afs_int32 code;
455     struct afs_fakestat_state ofakestate;
456     struct afs_fakestat_state nfakestate;
457     struct vrequest treq;
458     OSI_VC_CONVERT(aodp);
459
460     code = afs_InitReq(&treq, acred);
461     if (code)
462         return code;
463     afs_InitFakeStat(&ofakestate);
464     afs_InitFakeStat(&nfakestate);
465
466     AFS_DISCON_LOCK();
467     
468     code = afs_EvalFakeStat(&aodp, &ofakestate, &treq);
469     if (code)
470         goto done;
471     code = afs_EvalFakeStat(&andp, &nfakestate, &treq);
472     if (code)
473         goto done;
474     code = afsrename(aodp, aname1, andp, aname2, acred, &treq);
475   done:
476     afs_PutFakeStat(&ofakestate);
477     afs_PutFakeStat(&nfakestate);
478
479     AFS_DISCON_UNLOCK();
480     
481     code = afs_CheckCode(code, &treq, 25);
482     return code;
483 }