Commit | Line | Data |
---|---|---|
805e021f CE |
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 | * afs_vnop_symlink.c - symlink and readlink vnodeops. | |
12 | * | |
13 | * Implements: | |
14 | * afs_symlink | |
15 | * afs_MemHandleLink | |
16 | * afs_UFSHandleLink | |
17 | * afs_readlink | |
18 | * | |
19 | */ | |
20 | ||
21 | #include <afsconfig.h> | |
22 | #include "afs/param.h" | |
23 | ||
24 | ||
25 | #include "afs/sysincludes.h" /* Standard vendor system headers */ | |
26 | #include "afsincludes.h" /* Afs-based standard headers */ | |
27 | #include "afs/afs_stats.h" /* statistics */ | |
28 | #include "afs/afs_cbqueue.h" | |
29 | #include "afs/nfsclient.h" | |
30 | #include "afs/afs_osidnlc.h" | |
31 | ||
32 | extern afs_rwlock_t afs_xvcache; | |
33 | extern afs_rwlock_t afs_xcbhash; | |
34 | ||
35 | /* Note: There is the bare bones beginning of symlink hints in the now | |
36 | * defunct afs/afs_lookup.c file. Since they are not in use, making the call | |
37 | * is just a performance hit. | |
38 | */ | |
39 | ||
40 | static int | |
41 | afs_DisconCreateSymlink(struct vcache *avc, char *aname, | |
42 | struct vrequest *areq) { | |
43 | struct dcache *tdc; | |
44 | struct osi_file *tfile; | |
45 | afs_size_t offset, len; | |
46 | ||
47 | tdc = afs_GetDCache(avc, 0, areq, &offset, &len, 0); | |
48 | if (!tdc) { | |
49 | /* printf("afs_DisconCreateSymlink: can't get new dcache for symlink.\n"); */ | |
50 | return ENETDOWN; | |
51 | } | |
52 | ||
53 | len = strlen(aname); | |
54 | avc->f.m.Length = len; | |
55 | ||
56 | ObtainWriteLock(&tdc->lock, 720); | |
57 | afs_AdjustSize(tdc, len); | |
58 | tdc->validPos = len; | |
59 | tfile = afs_CFileOpen(&tdc->f.inode); | |
60 | osi_Assert(tfile); | |
61 | afs_CFileWrite(tfile, 0, aname, len); | |
62 | afs_CFileClose(tfile); | |
63 | ReleaseWriteLock(&tdc->lock); | |
64 | return 0; | |
65 | } | |
66 | ||
67 | /* don't set CDirty in here because RPC is called synchronously */ | |
68 | int | |
69 | afs_symlink(OSI_VC_DECL(adp), char *aname, struct vattr *attrs, | |
70 | char *atargetName, struct vcache **tvcp, afs_ucred_t *acred) | |
71 | { | |
72 | afs_uint32 now = 0; | |
73 | struct vrequest *treq = NULL; | |
74 | afs_int32 code = 0; | |
75 | struct afs_conn *tc; | |
76 | struct VenusFid newFid; | |
77 | struct dcache *tdc; | |
78 | afs_size_t offset, len; | |
79 | afs_int32 alen; | |
80 | struct server *hostp = 0; | |
81 | struct vcache *tvc; | |
82 | struct AFSStoreStatus InStatus; | |
83 | struct AFSFetchStatus *OutFidStatus, *OutDirStatus; | |
84 | struct AFSCallBack CallBack; | |
85 | struct AFSVolSync tsync; | |
86 | struct volume *volp = 0; | |
87 | struct afs_fakestat_state fakestate; | |
88 | struct rx_connection *rxconn; | |
89 | XSTATS_DECLS; | |
90 | OSI_VC_CONVERT(adp); | |
91 | ||
92 | AFS_STATCNT(afs_symlink); | |
93 | afs_Trace2(afs_iclSetp, CM_TRACE_SYMLINK, ICL_TYPE_POINTER, adp, | |
94 | ICL_TYPE_STRING, aname); | |
95 | ||
96 | OutFidStatus = osi_AllocSmallSpace(sizeof(struct AFSFetchStatus)); | |
97 | OutDirStatus = osi_AllocSmallSpace(sizeof(struct AFSFetchStatus)); | |
98 | memset(&InStatus, 0, sizeof(InStatus)); | |
99 | ||
100 | if ((code = afs_CreateReq(&treq, acred))) | |
101 | goto done2; | |
102 | ||
103 | afs_InitFakeStat(&fakestate); | |
104 | ||
105 | AFS_DISCON_LOCK(); | |
106 | ||
107 | code = afs_EvalFakeStat(&adp, &fakestate, treq); | |
108 | if (code) | |
109 | goto done; | |
110 | ||
111 | if (strlen(aname) > AFSNAMEMAX || strlen(atargetName) > AFSPATHMAX) { | |
112 | code = ENAMETOOLONG; | |
113 | goto done; | |
114 | } | |
115 | ||
116 | if (afs_IsDynroot(adp)) { | |
117 | code = afs_DynrootVOPSymlink(adp, acred, aname, atargetName); | |
118 | goto done; | |
119 | } | |
120 | if (afs_IsDynrootMount(adp)) { | |
121 | code = EROFS; | |
122 | goto done; | |
123 | } | |
124 | ||
125 | code = afs_VerifyVCache(adp, treq); | |
126 | if (code) { | |
127 | code = afs_CheckCode(code, treq, 30); | |
128 | goto done; | |
129 | } | |
130 | ||
131 | /** If the volume is read-only, return error without making an RPC to the | |
132 | * fileserver | |
133 | */ | |
134 | if (adp->f.states & CRO) { | |
135 | code = EROFS; | |
136 | goto done; | |
137 | } | |
138 | ||
139 | if (AFS_IS_DISCONNECTED && !AFS_IS_DISCON_RW) { | |
140 | code = ENETDOWN; | |
141 | goto done; | |
142 | } | |
143 | ||
144 | InStatus.Mask = AFS_SETMODTIME | AFS_SETMODE; | |
145 | InStatus.ClientModTime = osi_Time(); | |
146 | alen = strlen(atargetName); /* we want it to include the null */ | |
147 | if ( (*atargetName == '#' || *atargetName == '%') && alen > 1 && atargetName[alen-1] == '.') { | |
148 | InStatus.UnixModeBits = 0644; /* mt pt: null from "." at end */ | |
149 | if (alen == 1) | |
150 | alen++; /* Empty string */ | |
151 | } else { | |
152 | InStatus.UnixModeBits = 0755; | |
153 | alen++; /* add in the null */ | |
154 | } | |
155 | tdc = afs_GetDCache(adp, (afs_size_t) 0, treq, &offset, &len, 1); | |
156 | volp = afs_FindVolume(&adp->f.fid, READ_LOCK); /*parent is also in same vol */ | |
157 | ObtainWriteLock(&adp->lock, 156); | |
158 | if (tdc) | |
159 | ObtainWriteLock(&tdc->lock, 636); | |
160 | /* No further locks: if the SymLink succeeds, it does not matter what happens | |
161 | * to our local copy of the directory. If somebody tampers with it in the meantime, | |
162 | * the copy will be invalidated */ | |
163 | if (!AFS_IS_DISCON_RW) { | |
164 | do { | |
165 | tc = afs_Conn(&adp->f.fid, treq, SHARED_LOCK, &rxconn); | |
166 | if (tc) { | |
167 | hostp = tc->parent->srvr->server; | |
168 | XSTATS_START_TIME(AFS_STATS_FS_RPCIDX_SYMLINK); | |
169 | if (adp->f.states & CForeign) { | |
170 | now = osi_Time(); | |
171 | RX_AFS_GUNLOCK(); | |
172 | code = | |
173 | RXAFS_DFSSymlink(rxconn, | |
174 | (struct AFSFid *)&adp->f.fid.Fid, | |
175 | aname, atargetName, &InStatus, | |
176 | (struct AFSFid *)&newFid.Fid, | |
177 | OutFidStatus, OutDirStatus, | |
178 | &CallBack, &tsync); | |
179 | RX_AFS_GLOCK(); | |
180 | } else { | |
181 | RX_AFS_GUNLOCK(); | |
182 | code = | |
183 | RXAFS_Symlink(rxconn, (struct AFSFid *)&adp->f.fid.Fid, | |
184 | aname, atargetName, &InStatus, | |
185 | (struct AFSFid *)&newFid.Fid, | |
186 | OutFidStatus, OutDirStatus, &tsync); | |
187 | RX_AFS_GLOCK(); | |
188 | } | |
189 | XSTATS_END_TIME; | |
190 | } else | |
191 | code = -1; | |
192 | } while (afs_Analyze | |
193 | (tc, rxconn, code, &adp->f.fid, treq, AFS_STATS_FS_RPCIDX_SYMLINK, | |
194 | SHARED_LOCK, NULL)); | |
195 | } else { | |
196 | newFid.Cell = adp->f.fid.Cell; | |
197 | newFid.Fid.Volume = adp->f.fid.Fid.Volume; | |
198 | afs_GenFakeFid(&newFid, VREG, 0); | |
199 | } | |
200 | ||
201 | ObtainWriteLock(&afs_xvcache, 40); | |
202 | if (code) { | |
203 | if (code < 0) { | |
204 | afs_StaleVCache(adp); | |
205 | } | |
206 | ReleaseWriteLock(&adp->lock); | |
207 | ReleaseWriteLock(&afs_xvcache); | |
208 | if (tdc) { | |
209 | ReleaseWriteLock(&tdc->lock); | |
210 | afs_PutDCache(tdc); | |
211 | } | |
212 | goto done; | |
213 | } | |
214 | /* otherwise, we should see if we can make the change to the dir locally */ | |
215 | if (AFS_IS_DISCON_RW || afs_LocalHero(adp, tdc, OutDirStatus, 1)) { | |
216 | /* we can do it locally */ | |
217 | ObtainWriteLock(&afs_xdcache, 293); | |
218 | /* If the following fails because the name has been created in the meantime, the | |
219 | * directory is out-of-date - the file server knows best! */ | |
220 | code = afs_dir_Create(tdc, aname, &newFid.Fid); | |
221 | ReleaseWriteLock(&afs_xdcache); | |
222 | if (code && !AFS_IS_DISCON_RW) { | |
223 | ZapDCE(tdc); /* surprise error -- use invalid value */ | |
224 | DZap(tdc); | |
225 | } | |
226 | } | |
227 | if (tdc) { | |
228 | ReleaseWriteLock(&tdc->lock); | |
229 | afs_PutDCache(tdc); | |
230 | } | |
231 | newFid.Cell = adp->f.fid.Cell; | |
232 | newFid.Fid.Volume = adp->f.fid.Fid.Volume; | |
233 | ReleaseWriteLock(&adp->lock); | |
234 | ||
235 | /* now we're done with parent dir, create the link's entry. Note that | |
236 | * no one can get a pointer to the new cache entry until we release | |
237 | * the xvcache lock. */ | |
238 | tvc = afs_NewVCache(&newFid, hostp); | |
239 | if (!tvc) | |
240 | { | |
241 | code = -2; | |
242 | ReleaseWriteLock(&afs_xvcache); | |
243 | goto done; | |
244 | } | |
245 | ObtainWriteLock(&tvc->lock, 157); | |
246 | ObtainWriteLock(&afs_xcbhash, 500); | |
247 | tvc->f.states |= CStatd; /* have valid info */ | |
248 | tvc->f.states &= ~CBulkFetching; | |
249 | ||
250 | if (adp->f.states & CForeign) { | |
251 | tvc->f.states |= CForeign; | |
252 | /* We don't have to worry about losing the callback since we're doing it | |
253 | * under the afs_xvcache lock actually, afs_NewVCache may drop the | |
254 | * afs_xvcache lock, if it calls afs_FlushVCache */ | |
255 | tvc->cbExpires = CallBack.ExpirationTime + now; | |
256 | afs_QueueCallback(tvc, CBHash(CallBack.ExpirationTime), volp); | |
257 | } else { | |
258 | tvc->cbExpires = 0x7fffffff; /* never expires, they can't change */ | |
259 | /* since it never expires, we don't have to queue the callback */ | |
260 | } | |
261 | ReleaseWriteLock(&afs_xcbhash); | |
262 | ||
263 | if (AFS_IS_DISCON_RW) { | |
264 | attrs->va_mode = InStatus.UnixModeBits; | |
265 | afs_GenDisconStatus(adp, tvc, &newFid, attrs, treq, VLNK); | |
266 | code = afs_DisconCreateSymlink(tvc, atargetName, treq); | |
267 | if (code) { | |
268 | /* XXX - When this goes wrong, we need to tidy up the changes we made to | |
269 | * the parent, and get rid of the vcache we just created */ | |
270 | ReleaseWriteLock(&tvc->lock); | |
271 | ReleaseWriteLock(&afs_xvcache); | |
272 | afs_PutVCache(tvc); | |
273 | goto done; | |
274 | } | |
275 | afs_DisconAddDirty(tvc, VDisconCreate, 0); | |
276 | } else { | |
277 | afs_ProcessFS(tvc, OutFidStatus, treq); | |
278 | } | |
279 | ||
280 | if (!tvc->linkData) { | |
281 | tvc->linkData = afs_osi_Alloc(alen); | |
282 | osi_Assert(tvc->linkData != NULL); | |
283 | strncpy(tvc->linkData, atargetName, alen - 1); | |
284 | tvc->linkData[alen - 1] = 0; | |
285 | } | |
286 | ReleaseWriteLock(&tvc->lock); | |
287 | ReleaseWriteLock(&afs_xvcache); | |
288 | if (tvcp) | |
289 | *tvcp = tvc; | |
290 | else | |
291 | afs_PutVCache(tvc); | |
292 | code = 0; | |
293 | done: | |
294 | afs_PutFakeStat(&fakestate); | |
295 | if (volp) | |
296 | afs_PutVolume(volp, READ_LOCK); | |
297 | AFS_DISCON_UNLOCK(); | |
298 | code = afs_CheckCode(code, treq, 31); | |
299 | afs_DestroyReq(treq); | |
300 | done2: | |
301 | osi_FreeSmallSpace(OutFidStatus); | |
302 | osi_FreeSmallSpace(OutDirStatus); | |
303 | return code; | |
304 | } | |
305 | ||
306 | int | |
307 | afs_MemHandleLink(struct vcache *avc, struct vrequest *areq) | |
308 | { | |
309 | struct dcache *tdc; | |
310 | char *tp, *rbuf; | |
311 | afs_size_t offset, len; | |
312 | afs_int32 tlen, alen; | |
313 | afs_int32 code; | |
314 | ||
315 | AFS_STATCNT(afs_MemHandleLink); | |
316 | /* two different formats, one for links protected 644, have a "." at | |
317 | * the end of the file name, which we turn into a null. Others, | |
318 | * protected 755, we add a null to the end of */ | |
319 | if (!avc->linkData) { | |
320 | void *addr; | |
321 | tdc = afs_GetDCache(avc, (afs_size_t) 0, areq, &offset, &len, 0); | |
322 | if (!tdc) { | |
323 | return EIO; | |
324 | } | |
325 | /* otherwise we have the data loaded, go for it */ | |
326 | if (len > 1024) { | |
327 | afs_PutDCache(tdc); | |
328 | return EFAULT; | |
329 | } | |
330 | if (avc->f.m.Mode & 0111) | |
331 | alen = len + 1; /* regular link */ | |
332 | else | |
333 | alen = len; /* mt point */ | |
334 | rbuf = osi_AllocLargeSpace(AFS_LRALLOCSIZ); | |
335 | ObtainReadLock(&tdc->lock); | |
336 | addr = afs_MemCacheOpen(&tdc->f.inode); | |
337 | tlen = len; | |
338 | code = afs_MemReadBlk(addr, 0, rbuf, tlen); | |
339 | afs_MemCacheClose(addr); | |
340 | ReleaseReadLock(&tdc->lock); | |
341 | afs_PutDCache(tdc); | |
342 | rbuf[alen - 1] = 0; | |
343 | alen = strlen(rbuf) + 1; | |
344 | tp = afs_osi_Alloc(alen); /* make room for terminating null */ | |
345 | osi_Assert(tp != NULL); | |
346 | memcpy(tp, rbuf, alen); | |
347 | osi_FreeLargeSpace(rbuf); | |
348 | if (code != len) { | |
349 | afs_osi_Free(tp, alen); | |
350 | return EIO; | |
351 | } | |
352 | avc->linkData = tp; | |
353 | } | |
354 | return 0; | |
355 | } | |
356 | ||
357 | int | |
358 | afs_UFSHandleLink(struct vcache *avc, struct vrequest *areq) | |
359 | { | |
360 | struct dcache *tdc; | |
361 | char *tp, *rbuf; | |
362 | void *tfile; | |
363 | afs_size_t offset, len; | |
364 | afs_int32 tlen, alen; | |
365 | afs_int32 code; | |
366 | ||
367 | /* two different formats, one for links protected 644, have a "." at the | |
368 | * end of the file name, which we turn into a null. Others, protected | |
369 | * 755, we add a null to the end of */ | |
370 | AFS_STATCNT(afs_UFSHandleLink); | |
371 | if (!avc->linkData) { | |
372 | tdc = afs_GetDCache(avc, (afs_size_t) 0, areq, &offset, &len, 0); | |
373 | afs_Trace3(afs_iclSetp, CM_TRACE_UFSLINK, ICL_TYPE_POINTER, avc, | |
374 | ICL_TYPE_POINTER, tdc, ICL_TYPE_OFFSET, | |
375 | ICL_HANDLE_OFFSET(avc->f.m.Length)); | |
376 | if (!tdc) { | |
377 | if (AFS_IS_DISCONNECTED) | |
378 | return ENETDOWN; | |
379 | else | |
380 | return EIO; | |
381 | } | |
382 | /* otherwise we have the data loaded, go for it */ | |
383 | if (len > 1024) { | |
384 | afs_PutDCache(tdc); | |
385 | return EFAULT; | |
386 | } | |
387 | if (avc->f.m.Mode & 0111) | |
388 | alen = len + 1; /* regular link */ | |
389 | else | |
390 | alen = len; /* mt point */ | |
391 | rbuf = osi_AllocLargeSpace(AFS_LRALLOCSIZ); | |
392 | tlen = len; | |
393 | ObtainReadLock(&tdc->lock); | |
394 | tfile = osi_UFSOpen(&tdc->f.inode); | |
395 | if (!tfile) { | |
396 | ReleaseReadLock(&tdc->lock); | |
397 | afs_PutDCache(tdc); | |
398 | osi_FreeLargeSpace(rbuf); | |
399 | return EIO; | |
400 | } | |
401 | code = afs_osi_Read(tfile, -1, rbuf, tlen); | |
402 | osi_UFSClose(tfile); | |
403 | ReleaseReadLock(&tdc->lock); | |
404 | afs_PutDCache(tdc); | |
405 | rbuf[alen - 1] = '\0'; | |
406 | alen = strlen(rbuf) + 1; | |
407 | tp = afs_osi_Alloc(alen); /* make room for terminating null */ | |
408 | osi_Assert(tp != NULL); | |
409 | memcpy(tp, rbuf, alen); | |
410 | osi_FreeLargeSpace(rbuf); | |
411 | if (code != tlen) { | |
412 | afs_osi_Free(tp, alen); | |
413 | return EIO; | |
414 | } | |
415 | avc->linkData = tp; | |
416 | } | |
417 | return 0; | |
418 | } | |
419 | ||
420 | int | |
421 | afs_readlink(OSI_VC_DECL(avc), struct uio *auio, afs_ucred_t *acred) | |
422 | { | |
423 | afs_int32 code; | |
424 | struct vrequest *treq = NULL; | |
425 | char *tp; | |
426 | struct afs_fakestat_state fakestat; | |
427 | OSI_VC_CONVERT(avc); | |
428 | ||
429 | AFS_STATCNT(afs_readlink); | |
430 | afs_Trace1(afs_iclSetp, CM_TRACE_READLINK, ICL_TYPE_POINTER, avc); | |
431 | if ((code = afs_CreateReq(&treq, acred))) | |
432 | return code; | |
433 | afs_InitFakeStat(&fakestat); | |
434 | ||
435 | AFS_DISCON_LOCK(); | |
436 | ||
437 | code = afs_EvalFakeStat(&avc, &fakestat, treq); | |
438 | if (code) | |
439 | goto done; | |
440 | code = afs_VerifyVCache(avc, treq); | |
441 | if (code) | |
442 | goto done; | |
443 | if (vType(avc) != VLNK) { | |
444 | code = EINVAL; | |
445 | goto done; | |
446 | } | |
447 | ObtainWriteLock(&avc->lock, 158); | |
448 | code = afs_HandleLink(avc, treq); | |
449 | /* finally uiomove it to user-land */ | |
450 | if (code == 0) { | |
451 | tp = avc->linkData; | |
452 | if (tp) | |
453 | AFS_UIOMOVE(tp, strlen(tp), UIO_READ, auio, code); | |
454 | else { | |
455 | code = EIO; | |
456 | } | |
457 | } | |
458 | ReleaseWriteLock(&avc->lock); | |
459 | done: | |
460 | afs_PutFakeStat(&fakestat); | |
461 | AFS_DISCON_UNLOCK(); | |
462 | code = afs_CheckCode(code, treq, 32); | |
463 | afs_DestroyReq(treq); | |
464 | return code; | |
465 | } |