| 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 | } |