1 /* $NetBSD: autofs_vnops.c,v 1.10 2024/12/15 21:40:05 andvar Exp $ */ 2 /*- 3 * Copyright (c) 2017 The NetBSD Foundation, Inc. 4 * Copyright (c) 2016 The DragonFly Project 5 * Copyright (c) 2014 The FreeBSD Foundation 6 * All rights reserved. 7 * 8 * This code is derived from software contributed to The NetBSD Foundation 9 * by Tomohiro Kusumi <kusumi.tomohiro@gmail.com>. 10 * 11 * This software was developed by Edward Tomasz Napierala under sponsorship 12 * from the FreeBSD Foundation. 13 * 14 * Redistribution and use in source and binary forms, with or without 15 * modification, are permitted provided that the following conditions 16 * are met: 17 * 1. Redistributions of source code must retain the above copyright 18 * notice, this list of conditions and the following disclaimer. 19 * 2. Redistributions in binary form must reproduce the above copyright 20 * notice, this list of conditions and the following disclaimer in the 21 * documentation and/or other materials provided with the distribution. 22 * 23 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 24 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 25 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 26 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 27 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 28 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 29 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 30 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 31 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 32 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 33 * SUCH DAMAGE. 34 * 35 */ 36 #include <sys/cdefs.h> 37 __KERNEL_RCSID(0, "$NetBSD: autofs_vnops.c,v 1.10 2024/12/15 21:40:05 andvar Exp $"); 38 39 #include "autofs.h" 40 41 #include <sys/stat.h> 42 #include <sys/dirent.h> 43 #include <sys/namei.h> 44 #include <miscfs/genfs/genfs.h> 45 46 static int autofs_trigger_vn(struct vnode *vp, const char *path, 47 int pathlen, struct vnode **newvp); 48 49 static int 50 autofs_access(void *v) 51 { 52 struct vop_access_args /* { 53 struct vnode *a_vp; 54 int a_mode; 55 kauth_cred_t a_cred; 56 } */ *ap = v; 57 struct vnode *vp __diagused = ap->a_vp; 58 59 KASSERT(VOP_ISLOCKED(vp)); 60 /* 61 * Nothing to do here; the only kind of access control 62 * needed is in autofs_mkdir(). 63 */ 64 return 0; 65 } 66 67 static int 68 autofs_getattr(void *v) 69 { 70 struct vop_getattr_args /* { 71 struct vnode *a_vp; 72 struct vattr *a_vap; 73 kauth_cred_t a_cred; 74 } */ *ap = v; 75 struct vnode *vp = ap->a_vp; 76 struct vattr *vap = ap->a_vap; 77 struct autofs_node *anp = VTOI(vp); 78 79 KASSERT(vp->v_type == VDIR); 80 81 /* 82 * The reason we must do this is that some tree-walking software, 83 * namely fts(3), assumes that stat(".") results will not change 84 * between chdir("subdir") and chdir(".."), and fails with ENOENT 85 * otherwise. 86 */ 87 if (autofs_mount_on_stat && 88 autofs_cached(anp, NULL, 0) == false && 89 autofs_ignore_thread() == false) { 90 struct vnode *newvp = NULLVP; 91 int error = autofs_trigger_vn(vp, "", 0, &newvp); 92 if (error) 93 return error; 94 /* 95 * Already mounted here. 96 */ 97 if (newvp) { 98 error = VOP_GETATTR(newvp, vap, ap->a_cred); 99 vput(newvp); 100 return error; 101 } 102 } 103 104 vattr_null(vap); 105 106 vap->va_type = VDIR; 107 vap->va_mode = 0755; 108 vap->va_nlink = 3; 109 vap->va_uid = 0; 110 vap->va_gid = 0; 111 vap->va_fsid = vp->v_mount->mnt_stat.f_fsidx.__fsid_val[0]; 112 vap->va_fileid = anp->an_ino; 113 vap->va_size = S_BLKSIZE; 114 vap->va_blocksize = S_BLKSIZE; 115 vap->va_mtime = anp->an_ctime; 116 vap->va_atime = anp->an_ctime; 117 vap->va_ctime = anp->an_ctime; 118 vap->va_birthtime = anp->an_ctime; 119 vap->va_gen = 0; 120 vap->va_flags = 0; 121 vap->va_rdev = 0; 122 vap->va_bytes = S_BLKSIZE; 123 vap->va_filerev = 0; 124 vap->va_vaflags = 0; 125 vap->va_spare = 0; 126 127 return 0; 128 } 129 130 /* 131 * Unlock the vnode, request automountd(8) action, and then lock it back. 132 * If anything got mounted on top of the vnode, return the new filesystem's 133 * root vnode in 'newvp', locked. A caller needs to vput() the 'newvp'. 134 */ 135 static int 136 autofs_trigger_vn(struct vnode *vp, const char *path, int pathlen, 137 struct vnode **newvp) 138 { 139 struct autofs_node *anp; 140 int error, lock_flags; 141 142 anp = vp->v_data; 143 144 /* 145 * Release the vnode lock, so that other operations, in particular 146 * mounting a filesystem on top of it, can proceed. Increase use 147 * count, to prevent the vnode from being deallocated and to prevent 148 * filesystem from being unmounted. 149 */ 150 lock_flags = VOP_ISLOCKED(vp); 151 vref(vp); 152 VOP_UNLOCK(vp); 153 154 mutex_enter(&autofs_softc->sc_lock); 155 156 /* 157 * Workaround for mounting the same thing multiple times; revisit. 158 */ 159 if (vp->v_mountedhere) { 160 error = 0; 161 goto mounted; 162 } 163 164 error = autofs_trigger(anp, path, pathlen); 165 mounted: 166 mutex_exit(&autofs_softc->sc_lock); 167 vn_lock(vp, lock_flags | LK_RETRY); 168 vrele(vp); 169 170 if (error) 171 return error; 172 173 if (!vp->v_mountedhere) { 174 *newvp = NULLVP; 175 return 0; 176 } else { 177 /* 178 * If the operation that succeeded was mount, then mark 179 * the node as non-cached. Otherwise, if someone unmounts 180 * the filesystem before the cache times out, we will fail 181 * to trigger. 182 */ 183 autofs_node_uncache(anp); 184 } 185 186 error = VFS_ROOT(vp->v_mountedhere, LK_EXCLUSIVE, newvp); 187 if (error) { 188 AUTOFS_WARN("VFS_ROOT() failed with error %d", error); 189 return error; 190 } 191 192 return 0; 193 } 194 195 static int 196 autofs_lookup(void *v) 197 { 198 struct vop_lookup_v2_args /* { 199 struct vnode *a_dvp; 200 struct vnode **a_vpp; 201 struct componentname *a_cnp; 202 } */ *ap = v; 203 struct vnode *dvp = ap->a_dvp; 204 struct vnode **vpp = ap->a_vpp; 205 struct componentname *cnp = ap->a_cnp; 206 struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount); 207 struct autofs_node *anp, *child; 208 int cachefound; 209 int error; 210 const bool lastcn __diagused = (cnp->cn_flags & ISLASTCN) != 0; 211 212 KASSERT(VOP_ISLOCKED(dvp)); 213 214 anp = VTOI(dvp); 215 *vpp = NULLVP; 216 217 /* Check accessibility of directory. */ 218 KASSERT(!VOP_ACCESS(dvp, VEXEC, cnp->cn_cred)); 219 220 /* 221 * Avoid doing a linear scan of the directory if the requested 222 * directory/name couple is already in the cache. 223 */ 224 cachefound = cache_lookup(dvp, cnp->cn_nameptr, cnp->cn_namelen, 225 cnp->cn_nameiop, cnp->cn_flags, NULL, vpp); 226 if (cachefound && *vpp == NULLVP) { 227 /* Negative cache hit. */ 228 error = ENOENT; 229 goto out; 230 } else if (cachefound) { 231 error = 0; 232 goto out; 233 } 234 235 if (cnp->cn_flags & ISDOTDOT) { 236 struct autofs_node *parent; 237 /* 238 * Lookup of ".." case. 239 */ 240 KASSERT(!(lastcn && cnp->cn_nameiop == RENAME)); 241 parent = anp->an_parent; 242 if (!parent) { 243 error = ENOENT; 244 goto out; 245 } 246 247 error = vcache_get(dvp->v_mount, &parent, sizeof(parent), vpp); 248 goto out; 249 } else if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') { 250 /* 251 * Lookup of "." case. 252 */ 253 KASSERT(!(lastcn && cnp->cn_nameiop == RENAME)); 254 vref(dvp); 255 *vpp = dvp; 256 error = 0; 257 goto done; 258 } 259 260 if (autofs_cached(anp, cnp->cn_nameptr, cnp->cn_namelen) == false && 261 autofs_ignore_thread() == false) { 262 struct vnode *newvp = NULLVP; 263 error = autofs_trigger_vn(dvp, cnp->cn_nameptr, cnp->cn_namelen, 264 &newvp); 265 if (error) 266 return error; 267 /* 268 * Already mounted here. 269 */ 270 if (newvp) { 271 error = VOP_LOOKUP(newvp, vpp, cnp); 272 vput(newvp); 273 return error; 274 } 275 } 276 277 mutex_enter(&->am_lock); 278 error = autofs_node_find(anp, cnp->cn_nameptr, cnp->cn_namelen, &child); 279 if (error) { 280 if ((cnp->cn_flags & ISLASTCN) && cnp->cn_nameiop == CREATE) { 281 mutex_exit(&->am_lock); 282 error = EJUSTRETURN; 283 goto done; 284 } 285 286 mutex_exit(&->am_lock); 287 error = ENOENT; 288 goto done; 289 } 290 291 /* 292 * Dropping the node here is ok, because we never remove nodes. 293 */ 294 mutex_exit(&->am_lock); 295 296 /* Get a vnode for the matching entry. */ 297 error = vcache_get(dvp->v_mount, &child, sizeof(child), vpp); 298 done: 299 /* 300 * Cache the result, unless request was for creation (as it does 301 * not improve the performance). 302 */ 303 if (cnp->cn_nameiop != CREATE) { 304 cache_enter(dvp, *vpp, cnp->cn_nameptr, cnp->cn_namelen, 305 cnp->cn_flags); 306 } 307 out: 308 KASSERT(VOP_ISLOCKED(dvp)); 309 310 return error; 311 } 312 313 static int 314 autofs_open(void *v) 315 { 316 struct vop_open_args /* { 317 struct vnode *a_vp; 318 int a_mode; 319 kauth_cred_t a_cred; 320 } */ *ap = v; 321 struct vnode *vp __diagused = ap->a_vp; 322 323 KASSERT(VOP_ISLOCKED(vp)); 324 return 0; 325 } 326 327 static int 328 autofs_close(void *v) 329 { 330 struct vop_close_args /* { 331 struct vnode *a_vp; 332 int a_fflag; 333 kauth_cred_t a_cred; 334 } */ *ap = v; 335 struct vnode *vp __diagused = ap->a_vp; 336 337 KASSERT(VOP_ISLOCKED(vp)); 338 return 0; 339 } 340 341 static int 342 autofs_fsync(void *v) 343 { 344 struct vop_fsync_args /* { 345 struct vnode *a_vp; 346 kauth_cred_t a_cred; 347 int a_flags; 348 off_t a_offlo; 349 off_t a_offhi; 350 struct lwp *a_l; 351 } */ *ap = v; 352 struct vnode *vp __diagused = ap->a_vp; 353 354 /* Nothing to do. Should be up to date. */ 355 KASSERT(VOP_ISLOCKED(vp)); 356 return 0; 357 } 358 359 static int 360 autofs_mkdir(void *v) 361 { 362 struct vop_mkdir_v3_args /* { 363 struct vnode *a_dvp; 364 struct vnode **a_vpp; 365 struct componentname *a_cnp; 366 struct vattr *a_vap; 367 } */ *ap = v; 368 struct vnode *dvp = ap->a_dvp; 369 struct vnode **vpp = ap->a_vpp; 370 struct componentname *cnp = ap->a_cnp; 371 struct autofs_mount *amp = VFSTOAUTOFS(dvp->v_mount); 372 struct autofs_node *anp = VTOI(dvp); 373 struct autofs_node *child = NULL; 374 int error; 375 376 KASSERT(ap->a_vap->va_type == VDIR); 377 378 /* 379 * Do not allow mkdir() if the calling thread is not 380 * automountd(8) descendant. 381 */ 382 if (autofs_ignore_thread() == false) 383 return EPERM; 384 385 mutex_enter(&->am_lock); 386 error = autofs_node_new(anp, amp, cnp->cn_nameptr, cnp->cn_namelen, 387 &child); 388 if (error) { 389 mutex_exit(&->am_lock); 390 return error; 391 } 392 mutex_exit(&->am_lock); 393 394 return vcache_get(amp->am_mp, &child, sizeof(child), vpp); 395 } 396 397 static int 398 autofs_print(void *v) 399 { 400 struct vop_print_args /* { 401 struct vnode *a_vp; 402 } */ *ap = v; 403 struct vnode *vp = ap->a_vp; 404 struct autofs_node *anp = VTOI(vp); 405 406 printf("tag VT_AUTOFS, node %p, ino %jd, name %s, cached %d, " 407 "retries %d, wildcards %d", 408 anp, (intmax_t)anp->an_ino, anp->an_name, anp->an_cached, 409 anp->an_retries, anp->an_wildcards); 410 printf("\n"); 411 412 return 0; 413 } 414 415 static int 416 autofs_readdir_one(struct uio *uio, const char *name, ino_t ino) 417 { 418 struct dirent dirent; 419 420 dirent.d_fileno = ino; 421 dirent.d_type = DT_DIR; 422 strlcpy(dirent.d_name, name, sizeof(dirent.d_name)); 423 dirent.d_namlen = strlen(dirent.d_name); 424 dirent.d_reclen = _DIRENT_SIZE(&dirent); 425 426 if (!uio) 427 return 0; 428 429 if (uio->uio_resid < dirent.d_reclen) 430 return EINVAL; 431 432 return uiomove(&dirent, dirent.d_reclen, uio); 433 } 434 435 static size_t 436 autofs_dirent_reclen(const char *name) 437 { 438 struct dirent dirent; 439 440 strlcpy(dirent.d_name, name, sizeof(dirent.d_name)); 441 dirent.d_namlen = strlen(dirent.d_name); 442 443 return _DIRENT_SIZE(&dirent); 444 } 445 446 static int 447 autofs_readdir(void *v) 448 { 449 struct vop_readdir_args /* { 450 struct vnode *a_vp; 451 struct uio *a_uio; 452 kauth_cred_t a_cred; 453 int *a_eofflag; 454 off_t **a_cookies; 455 int *ncookies; 456 } */ *ap = v; 457 struct vnode *vp = ap->a_vp; 458 struct uio *uio = ap->a_uio; 459 size_t initial_resid = ap->a_uio->uio_resid; 460 struct autofs_mount *amp = VFSTOAUTOFS(vp->v_mount); 461 struct autofs_node *anp = VTOI(vp); 462 struct autofs_node *child; 463 size_t reclens; 464 int error; 465 466 if (vp->v_type != VDIR) 467 return ENOTDIR; 468 469 if (autofs_cached(anp, NULL, 0) == false && 470 autofs_ignore_thread() == false) { 471 struct vnode *newvp = NULLVP; 472 error = autofs_trigger_vn(vp, "", 0, &newvp); 473 if (error) 474 return error; 475 /* 476 * Already mounted here. 477 */ 478 if (newvp) { 479 error = VOP_READDIR(newvp, ap->a_uio, ap->a_cred, 480 ap->a_eofflag, ap->a_cookies, ap->a_ncookies); 481 vput(newvp); 482 return error; 483 } 484 } 485 486 if (uio->uio_offset < 0) 487 return EINVAL; 488 489 if (ap->a_eofflag) 490 *ap->a_eofflag = FALSE; 491 492 /* 493 * Write out the directory entry for ".". 494 */ 495 if (uio->uio_offset == 0) { 496 error = autofs_readdir_one(uio, ".", anp->an_ino); 497 if (error) 498 goto out; 499 } 500 reclens = autofs_dirent_reclen("."); 501 502 /* 503 * Write out the directory entry for "..". 504 */ 505 if (uio->uio_offset <= reclens) { 506 if (uio->uio_offset != reclens) 507 return EINVAL; 508 error = autofs_readdir_one(uio, "..", 509 anp->an_parent ? anp->an_parent->an_ino : anp->an_ino); 510 if (error) 511 goto out; 512 } 513 reclens += autofs_dirent_reclen(".."); 514 515 /* 516 * Write out the directory entries for subdirectories. 517 */ 518 mutex_enter(&->am_lock); 519 RB_FOREACH(child, autofs_node_tree, &anp->an_children) { 520 /* 521 * Check the offset to skip entries returned by previous 522 * calls to getdents(). 523 */ 524 if (uio->uio_offset > reclens) { 525 reclens += autofs_dirent_reclen(child->an_name); 526 continue; 527 } 528 529 /* 530 * Prevent seeking into the middle of dirent. 531 */ 532 if (uio->uio_offset != reclens) { 533 mutex_exit(&->am_lock); 534 return EINVAL; 535 } 536 537 error = autofs_readdir_one(uio, child->an_name, child->an_ino); 538 reclens += autofs_dirent_reclen(child->an_name); 539 if (error) { 540 mutex_exit(&->am_lock); 541 goto out; 542 } 543 } 544 mutex_exit(&->am_lock); 545 546 if (ap->a_eofflag) 547 *ap->a_eofflag = TRUE; 548 549 return 0; 550 out: 551 /* 552 * Return error if the initial buffer was too small to do anything. 553 */ 554 if (uio->uio_resid == initial_resid) 555 return error; 556 557 /* 558 * Don't return an error if we managed to copy out some entries. 559 */ 560 if (uio->uio_resid < initial_resid) 561 return 0; 562 563 return error; 564 } 565 566 static int 567 autofs_reclaim(void *v) 568 { 569 struct vop_reclaim_v2_args /* { 570 struct vnode *a_vp; 571 } */ *ap = v; 572 struct vnode *vp = ap->a_vp; 573 struct autofs_node *anp = VTOI(vp); 574 575 VOP_UNLOCK(vp); 576 577 /* 578 * We do not free autofs_node here; instead we are 579 * destroying them in autofs_node_delete(). 580 */ 581 anp->an_vnode = NULLVP; 582 vp->v_data = NULL; 583 584 return 0; 585 } 586 587 int (**autofs_vnodeop_p)(void *); 588 static const struct vnodeopv_entry_desc autofs_vnodeop_entries[] = { 589 { &vop_default_desc, vn_default_error }, 590 { &vop_parsepath_desc, genfs_parsepath }, 591 { &vop_lookup_desc, autofs_lookup }, 592 { &vop_open_desc, autofs_open }, 593 { &vop_close_desc, autofs_close }, 594 { &vop_access_desc, autofs_access }, 595 { &vop_accessx_desc, genfs_accessx }, 596 { &vop_getattr_desc, autofs_getattr }, 597 { &vop_fsync_desc, autofs_fsync }, 598 { &vop_seek_desc, genfs_seek }, 599 { &vop_mkdir_desc, autofs_mkdir }, 600 { &vop_readdir_desc, autofs_readdir }, 601 { &vop_reclaim_desc, autofs_reclaim }, 602 { &vop_lock_desc, genfs_lock }, 603 { &vop_unlock_desc, genfs_unlock }, 604 { &vop_print_desc, autofs_print }, 605 { &vop_islocked_desc, genfs_islocked }, 606 { &vop_getpages_desc, genfs_getpages }, 607 { &vop_putpages_desc, genfs_putpages }, 608 { &vop_pathconf_desc, genfs_pathconf }, 609 { NULL, NULL } 610 }; 611 612 const struct vnodeopv_desc autofs_vnodeop_opv_desc = { 613 &autofs_vnodeop_p, autofs_vnodeop_entries 614 }; 615 616 int 617 autofs_node_new(struct autofs_node *parent, struct autofs_mount *amp, 618 const char *name, int namelen, struct autofs_node **anpp) 619 { 620 struct autofs_node *anp; 621 622 KASSERT(mutex_owned(&->am_lock)); 623 624 if (parent) { 625 KASSERT(mutex_owned(&parent->an_mount->am_lock)); 626 KASSERT(autofs_node_find(parent, name, namelen, NULL) == 627 ENOENT); 628 } 629 630 anp = pool_get(&autofs_node_pool, PR_WAITOK); 631 anp->an_name = autofs_strndup(name, namelen, KM_SLEEP); 632 anp->an_ino = amp->am_last_ino++; 633 callout_init(&anp->an_callout, 0); 634 getnanotime(&anp->an_ctime); 635 anp->an_parent = parent; 636 anp->an_mount = amp; 637 anp->an_vnode = NULLVP; 638 anp->an_cached = false; 639 anp->an_wildcards = false; 640 anp->an_retries = 0; 641 if (parent) 642 RB_INSERT(autofs_node_tree, &parent->an_children, anp); 643 RB_INIT(&anp->an_children); 644 645 *anpp = anp; 646 return 0; 647 } 648 649 int 650 autofs_node_find(struct autofs_node *parent, const char *name, 651 int namelen, struct autofs_node **anpp) 652 { 653 struct autofs_node *anp, find; 654 int error; 655 656 KASSERT(mutex_owned(&parent->an_mount->am_lock)); 657 658 find.an_name = autofs_strndup(name, namelen, KM_SLEEP); 659 anp = RB_FIND(autofs_node_tree, &parent->an_children, &find); 660 if (anp) { 661 error = 0; 662 if (anpp) 663 *anpp = anp; 664 } else { 665 error = ENOENT; 666 } 667 668 kmem_strfree(find.an_name); 669 670 return error; 671 } 672 673 void 674 autofs_node_delete(struct autofs_node *anp) 675 { 676 677 KASSERT(mutex_owned(&anp->an_mount->am_lock)); 678 KASSERT(RB_EMPTY(&anp->an_children)); 679 680 callout_halt(&anp->an_callout, NULL); 681 682 if (anp->an_parent) 683 RB_REMOVE(autofs_node_tree, &anp->an_parent->an_children, anp); 684 685 kmem_strfree(anp->an_name); 686 pool_put(&autofs_node_pool, anp); 687 } 688