1 /*- 2 * SPDX-License-Identifier: BSD-2-Clause 3 * 4 * Copyright (c) 2013 Juniper Networks, Inc. 5 * Copyright (c) 2022-2023 Klara, Inc. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 17 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 19 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 20 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 22 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 23 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 24 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 25 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 26 * SUCH DAMAGE. 27 */ 28 29 #include "opt_tarfs.h" 30 31 #include <sys/param.h> 32 #include <sys/systm.h> 33 #include <sys/bio.h> 34 #include <sys/buf.h> 35 #include <sys/dirent.h> 36 #include <sys/fcntl.h> 37 #include <sys/limits.h> 38 #include <sys/mount.h> 39 #include <sys/namei.h> 40 #include <sys/proc.h> 41 #include <sys/vnode.h> 42 43 #include <fs/tarfs/tarfs.h> 44 #include <fs/tarfs/tarfs_dbg.h> 45 46 static int 47 tarfs_open(struct vop_open_args *ap) 48 { 49 struct tarfs_node *tnp; 50 struct vnode *vp; 51 52 vp = ap->a_vp; 53 MPASS(VOP_ISLOCKED(vp)); 54 tnp = VP_TO_TARFS_NODE(vp); 55 56 TARFS_DPF(VNODE, "%s(%p=%s, %o)\n", __func__, 57 tnp, tnp->name, ap->a_mode); 58 59 if (vp->v_type != VREG && vp->v_type != VDIR) 60 return (EOPNOTSUPP); 61 62 vnode_create_vobject(vp, tnp->size, ap->a_td); 63 return (0); 64 } 65 66 static int 67 tarfs_close(struct vop_close_args *ap) 68 { 69 #ifdef TARFS_DEBUG 70 struct tarfs_node *tnp; 71 struct vnode *vp; 72 73 vp = ap->a_vp; 74 75 MPASS(VOP_ISLOCKED(vp)); 76 tnp = VP_TO_TARFS_NODE(vp); 77 78 TARFS_DPF(VNODE, "%s(%p=%s)\n", __func__, 79 tnp, tnp->name); 80 #else 81 (void)ap; 82 #endif 83 return (0); 84 } 85 86 static int 87 tarfs_access(struct vop_access_args *ap) 88 { 89 struct tarfs_node *tnp; 90 struct vnode *vp; 91 accmode_t accmode; 92 struct ucred *cred; 93 int error; 94 95 vp = ap->a_vp; 96 accmode = ap->a_accmode; 97 cred = ap->a_cred; 98 99 MPASS(VOP_ISLOCKED(vp)); 100 tnp = VP_TO_TARFS_NODE(vp); 101 102 TARFS_DPF(VNODE, "%s(%p=%s, %o)\n", __func__, 103 tnp, tnp->name, accmode); 104 105 switch (vp->v_type) { 106 case VDIR: 107 case VLNK: 108 case VREG: 109 if ((accmode & VWRITE) != 0) 110 return (EROFS); 111 break; 112 case VBLK: 113 case VCHR: 114 case VFIFO: 115 break; 116 default: 117 return (EINVAL); 118 } 119 120 if ((accmode & VWRITE) != 0) 121 return (EPERM); 122 123 error = vaccess(vp->v_type, tnp->mode, tnp->uid, 124 tnp->gid, accmode, cred); 125 return (error); 126 } 127 128 static int 129 tarfs_bmap(struct vop_bmap_args *ap) 130 { 131 struct tarfs_node *tnp; 132 struct vnode *vp; 133 off_t off; 134 uint64_t iosize; 135 int ra, rb, rmax; 136 137 vp = ap->a_vp; 138 iosize = vp->v_mount->mnt_stat.f_iosize; 139 140 if (ap->a_bop != NULL) 141 *ap->a_bop = &vp->v_bufobj; 142 if (ap->a_bnp != NULL) 143 *ap->a_bnp = ap->a_bn * btodb(iosize); 144 if (ap->a_runp == NULL) 145 return (0); 146 147 tnp = VP_TO_TARFS_NODE(vp); 148 off = ap->a_bn * iosize; 149 150 ra = rb = 0; 151 for (u_int i = 0; i < tnp->nblk; i++) { 152 off_t bs, be; 153 154 bs = tnp->blk[i].o; 155 be = tnp->blk[i].o + tnp->blk[i].l; 156 if (off > be) 157 continue; 158 else if (off < bs) { 159 /* We're in a hole. */ 160 ra = bs - off < iosize ? 161 0 : howmany(bs - (off + iosize), iosize); 162 rb = howmany(off - (i == 0 ? 163 0 : tnp->blk[i - 1].o + tnp->blk[i - 1].l), 164 iosize); 165 break; 166 } else { 167 /* We'll be reading from the backing file. */ 168 ra = be - off < iosize ? 169 0 : howmany(be - (off + iosize), iosize); 170 rb = howmany(off - bs, iosize); 171 break; 172 } 173 } 174 175 rmax = vp->v_mount->mnt_iosize_max / iosize - 1; 176 *ap->a_runp = imin(ra, rmax); 177 if (ap->a_runb != NULL) 178 *ap->a_runb = imin(rb, rmax); 179 return (0); 180 } 181 182 static int 183 tarfs_getattr(struct vop_getattr_args *ap) 184 { 185 struct tarfs_node *tnp; 186 struct vnode *vp; 187 struct vattr *vap; 188 189 vp = ap->a_vp; 190 vap = ap->a_vap; 191 tnp = VP_TO_TARFS_NODE(vp); 192 193 TARFS_DPF(VNODE, "%s(%p=%s)\n", __func__, 194 tnp, tnp->name); 195 196 vap->va_type = vp->v_type; 197 vap->va_mode = tnp->mode; 198 vap->va_nlink = tnp->nlink; 199 vap->va_gid = tnp->gid; 200 vap->va_uid = tnp->uid; 201 vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0]; 202 vap->va_fileid = tnp->ino; 203 vap->va_size = tnp->size; 204 vap->va_blocksize = vp->v_mount->mnt_stat.f_iosize; 205 vap->va_atime = tnp->atime; 206 vap->va_ctime = tnp->ctime; 207 vap->va_mtime = tnp->mtime; 208 vap->va_birthtime = tnp->birthtime; 209 vap->va_gen = tnp->gen; 210 vap->va_flags = tnp->flags; 211 vap->va_rdev = VN_ISDEV(vp) ? tnp->rdev : NODEV; 212 vap->va_bytes = round_page(tnp->physize); 213 vap->va_filerev = 0; 214 215 return (0); 216 } 217 218 static int 219 tarfs_lookup(struct vop_cachedlookup_args *ap) 220 { 221 struct tarfs_mount *tmp; 222 struct tarfs_node *dirnode, *parent, *tnp; 223 struct componentname *cnp; 224 struct vnode *dvp, **vpp; 225 #ifdef TARFS_DEBUG 226 struct vnode *vp; 227 #endif 228 int error; 229 230 dvp = ap->a_dvp; 231 vpp = ap->a_vpp; 232 cnp = ap->a_cnp; 233 234 *vpp = NULL; 235 dirnode = VP_TO_TARFS_NODE(dvp); 236 parent = dirnode->parent; 237 tmp = dirnode->tmp; 238 tnp = NULL; 239 240 TARFS_DPF(LOOKUP, "%s(%p=%s, %.*s)\n", __func__, 241 dirnode, dirnode->name, 242 (int)cnp->cn_namelen, cnp->cn_nameptr); 243 244 error = VOP_ACCESS(dvp, VEXEC, cnp->cn_cred, curthread); 245 if (error != 0) 246 return (error); 247 248 if (cnp->cn_flags & ISDOTDOT) { 249 /* Do not allow .. on the root node */ 250 if (parent == NULL || parent == dirnode) 251 return (ENOENT); 252 253 /* Allocate a new vnode on the matching entry */ 254 error = vn_vget_ino(dvp, parent->ino, cnp->cn_lkflags, 255 vpp); 256 if (error != 0) 257 return (error); 258 } else if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.') { 259 vref(dvp); 260 *vpp = dvp; 261 #ifdef TARFS_DEBUG 262 } else if (dirnode == dirnode->tmp->root && 263 (vp = dirnode->tmp->znode) != NULL && 264 cnp->cn_namelen == TARFS_ZIO_NAMELEN && 265 memcmp(cnp->cn_nameptr, TARFS_ZIO_NAME, TARFS_ZIO_NAMELEN) == 0) { 266 error = vn_lock(vp, cnp->cn_lkflags); 267 if (error != 0) 268 return (error); 269 vref(vp); 270 *vpp = vp; 271 return (0); 272 #endif 273 } else { 274 tnp = tarfs_lookup_node(dirnode, NULL, cnp); 275 if (tnp == NULL) { 276 TARFS_DPF(LOOKUP, "%s(%p=%s, %.*s): file not found\n", __func__, 277 dirnode, dirnode->name, 278 (int)cnp->cn_namelen, cnp->cn_nameptr); 279 return (ENOENT); 280 } 281 282 if ((cnp->cn_flags & ISLASTCN) == 0 && 283 (tnp->type != VDIR && tnp->type != VLNK)) 284 return (ENOTDIR); 285 286 error = VFS_VGET(tmp->vfs, tnp->ino, cnp->cn_lkflags, vpp); 287 if (error != 0) 288 return (error); 289 } 290 291 #ifdef TARFS_DEBUG 292 if (tnp == NULL) 293 tnp = VP_TO_TARFS_NODE(*vpp); 294 TARFS_DPF(LOOKUP, "%s: found vnode %p, tarfs_node %p\n", __func__, 295 *vpp, tnp); 296 #endif /* TARFS_DEBUG */ 297 298 /* Store the result of the cache if MAKEENTRY is specified in flags */ 299 if ((cnp->cn_flags & MAKEENTRY) != 0 && cnp->cn_nameiop != CREATE) 300 cache_enter(dvp, *vpp, cnp); 301 302 return (error); 303 } 304 305 static int 306 tarfs_readdir(struct vop_readdir_args *ap) 307 { 308 struct dirent cde = { }; 309 struct tarfs_node *current, *tnp; 310 struct vnode *vp; 311 struct uio *uio; 312 int *eofflag; 313 uint64_t **cookies; 314 int *ncookies; 315 off_t off; 316 u_int idx, ndirents; 317 int error; 318 319 vp = ap->a_vp; 320 uio = ap->a_uio; 321 eofflag = ap->a_eofflag; 322 cookies = ap->a_cookies; 323 ncookies = ap->a_ncookies; 324 325 if (vp->v_type != VDIR) 326 return (ENOTDIR); 327 328 tnp = VP_TO_TARFS_NODE(vp); 329 off = uio->uio_offset; 330 current = NULL; 331 ndirents = 0; 332 333 TARFS_DPF(VNODE, "%s(%p=%s, %zu, %zd)\n", __func__, 334 tnp, tnp->name, uio->uio_offset, uio->uio_resid); 335 336 if (uio->uio_offset == TARFS_COOKIE_EOF) { 337 if (eofflag != NULL) { 338 TARFS_DPF(VNODE, "%s: Setting EOF flag\n", __func__); 339 *eofflag = 1; 340 } 341 TARFS_DPF(VNODE, "%s: EOF\n", __func__); 342 return (0); 343 } 344 345 if (uio->uio_offset == TARFS_COOKIE_DOT) { 346 TARFS_DPF(VNODE, "%s: Generating . entry\n", __func__); 347 /* fake . entry */ 348 cde.d_fileno = tnp->ino; 349 cde.d_type = DT_DIR; 350 cde.d_namlen = 1; 351 cde.d_name[0] = '.'; 352 cde.d_name[1] = '\0'; 353 cde.d_reclen = GENERIC_DIRSIZ(&cde); 354 if (cde.d_reclen > uio->uio_resid) 355 goto full; 356 dirent_terminate(&cde); 357 error = uiomove(&cde, cde.d_reclen, uio); 358 if (error) 359 return (error); 360 /* next is .. */ 361 uio->uio_offset = TARFS_COOKIE_DOTDOT; 362 ndirents++; 363 } 364 365 if (uio->uio_offset == TARFS_COOKIE_DOTDOT) { 366 TARFS_DPF(VNODE, "%s: Generating .. entry\n", __func__); 367 /* fake .. entry */ 368 MPASS(tnp->parent != NULL); 369 TARFS_NODE_LOCK(tnp->parent); 370 cde.d_fileno = tnp->parent->ino; 371 TARFS_NODE_UNLOCK(tnp->parent); 372 cde.d_type = DT_DIR; 373 cde.d_namlen = 2; 374 cde.d_name[0] = '.'; 375 cde.d_name[1] = '.'; 376 cde.d_name[2] = '\0'; 377 cde.d_reclen = GENERIC_DIRSIZ(&cde); 378 if (cde.d_reclen > uio->uio_resid) 379 goto full; 380 dirent_terminate(&cde); 381 error = uiomove(&cde, cde.d_reclen, uio); 382 if (error) 383 return (error); 384 /* next is first child */ 385 current = TAILQ_FIRST(&tnp->dir.dirhead); 386 if (current == NULL) 387 goto done; 388 uio->uio_offset = current->ino; 389 TARFS_DPF(VNODE, "%s: [%u] setting current node to %p=%s\n", 390 __func__, ndirents, current, current->name); 391 ndirents++; 392 } 393 394 /* resuming previous call */ 395 if (current == NULL) { 396 current = tarfs_lookup_dir(tnp, uio->uio_offset); 397 if (current == NULL) { 398 error = EINVAL; 399 goto done; 400 } 401 uio->uio_offset = current->ino; 402 TARFS_DPF(VNODE, "%s: [%u] setting current node to %p=%s\n", 403 __func__, ndirents, current, current->name); 404 } 405 406 for (;;) { 407 cde.d_fileno = current->ino; 408 switch (current->type) { 409 case VBLK: 410 cde.d_type = DT_BLK; 411 break; 412 case VCHR: 413 cde.d_type = DT_CHR; 414 break; 415 case VDIR: 416 cde.d_type = DT_DIR; 417 break; 418 case VFIFO: 419 cde.d_type = DT_FIFO; 420 break; 421 case VLNK: 422 cde.d_type = DT_LNK; 423 break; 424 case VREG: 425 cde.d_type = DT_REG; 426 break; 427 default: 428 panic("%s: tarfs_node %p, type %d\n", __func__, 429 current, current->type); 430 } 431 cde.d_namlen = current->namelen; 432 MPASS(tnp->namelen < sizeof(cde.d_name)); 433 (void)memcpy(cde.d_name, current->name, current->namelen); 434 cde.d_name[current->namelen] = '\0'; 435 cde.d_reclen = GENERIC_DIRSIZ(&cde); 436 if (cde.d_reclen > uio->uio_resid) 437 goto full; 438 dirent_terminate(&cde); 439 error = uiomove(&cde, cde.d_reclen, uio); 440 if (error != 0) 441 goto done; 442 ndirents++; 443 /* next sibling */ 444 current = TAILQ_NEXT(current, dirents); 445 if (current == NULL) 446 goto done; 447 uio->uio_offset = current->ino; 448 TARFS_DPF(VNODE, "%s: [%u] setting current node to %p=%s\n", 449 __func__, ndirents, current, current->name); 450 } 451 full: 452 if (cde.d_reclen > uio->uio_resid) { 453 TARFS_DPF(VNODE, "%s: out of space, returning\n", 454 __func__); 455 error = (ndirents == 0) ? EINVAL : 0; 456 } 457 done: 458 TARFS_DPF(VNODE, "%s: %u entries written\n", __func__, ndirents); 459 TARFS_DPF(VNODE, "%s: saving cache information\n", __func__); 460 if (current == NULL) { 461 uio->uio_offset = TARFS_COOKIE_EOF; 462 tnp->dir.lastcookie = 0; 463 tnp->dir.lastnode = NULL; 464 } else { 465 tnp->dir.lastcookie = current->ino; 466 tnp->dir.lastnode = current; 467 } 468 469 if (eofflag != NULL) { 470 TARFS_DPF(VNODE, "%s: Setting EOF flag\n", __func__); 471 *eofflag = (error == 0 && current == NULL); 472 } 473 474 /* Update for NFS */ 475 if (error == 0 && cookies != NULL && ncookies != NULL) { 476 TARFS_DPF(VNODE, "%s: Updating NFS cookies\n", __func__); 477 current = NULL; 478 *cookies = malloc(ndirents * sizeof(off_t), M_TEMP, M_WAITOK); 479 *ncookies = ndirents; 480 for (idx = 0; idx < ndirents; idx++) { 481 if (off == TARFS_COOKIE_DOT) 482 off = TARFS_COOKIE_DOTDOT; 483 else { 484 if (off == TARFS_COOKIE_DOTDOT) { 485 current = TAILQ_FIRST(&tnp->dir.dirhead); 486 } else if (current != NULL) { 487 current = TAILQ_NEXT(current, dirents); 488 } else { 489 current = tarfs_lookup_dir(tnp, off); 490 current = TAILQ_NEXT(current, dirents); 491 } 492 if (current == NULL) 493 off = TARFS_COOKIE_EOF; 494 else 495 off = current->ino; 496 } 497 498 TARFS_DPF(VNODE, "%s: [%u] offset %zu\n", __func__, 499 idx, off); 500 (*cookies)[idx] = off; 501 } 502 MPASS(uio->uio_offset == off); 503 } 504 505 return (error); 506 } 507 508 static int 509 tarfs_read(struct vop_read_args *ap) 510 { 511 struct tarfs_node *tnp; 512 struct uio *uiop; 513 struct vnode *vp; 514 size_t len; 515 off_t resid; 516 int error; 517 518 uiop = ap->a_uio; 519 vp = ap->a_vp; 520 521 if (VN_ISDEV(vp)) 522 return (EOPNOTSUPP); 523 524 if (vp->v_type != VREG) 525 return (EISDIR); 526 527 if (uiop->uio_offset < 0) 528 return (EINVAL); 529 530 tnp = VP_TO_TARFS_NODE(vp); 531 error = 0; 532 533 TARFS_DPF(VNODE, "%s(%p=%s, %zu, %zd)\n", __func__, 534 tnp, tnp->name, uiop->uio_offset, uiop->uio_resid); 535 536 while ((resid = uiop->uio_resid) > 0) { 537 if (tnp->size <= uiop->uio_offset) 538 break; 539 len = MIN(tnp->size - uiop->uio_offset, resid); 540 if (len == 0) 541 break; 542 543 error = tarfs_read_file(tnp, len, uiop); 544 if (error != 0 || resid == uiop->uio_resid) 545 break; 546 } 547 548 return (error); 549 } 550 551 static int 552 tarfs_readlink(struct vop_readlink_args *ap) 553 { 554 struct tarfs_node *tnp; 555 struct uio *uiop; 556 struct vnode *vp; 557 int error; 558 559 uiop = ap->a_uio; 560 vp = ap->a_vp; 561 562 MPASS(uiop->uio_offset == 0); 563 MPASS(vp->v_type == VLNK); 564 565 tnp = VP_TO_TARFS_NODE(vp); 566 567 TARFS_DPF(VNODE, "%s(%p=%s)\n", __func__, 568 tnp, tnp->name); 569 570 error = uiomove(tnp->link.name, 571 MIN(tnp->size, uiop->uio_resid), uiop); 572 573 return (error); 574 } 575 576 static int 577 tarfs_reclaim(struct vop_reclaim_args *ap) 578 { 579 struct tarfs_node *tnp; 580 struct vnode *vp; 581 582 vp = ap->a_vp; 583 tnp = VP_TO_TARFS_NODE(vp); 584 585 vfs_hash_remove(vp); 586 587 TARFS_NODE_LOCK(tnp); 588 tnp->vnode = NULL; 589 vp->v_data = NULL; 590 TARFS_NODE_UNLOCK(tnp); 591 592 return (0); 593 } 594 595 static int 596 tarfs_print(struct vop_print_args *ap) 597 { 598 struct tarfs_node *tnp; 599 struct vnode *vp; 600 601 vp = ap->a_vp; 602 tnp = VP_TO_TARFS_NODE(vp); 603 604 printf("tag tarfs, tarfs_node %p, links %lu\n", 605 tnp, (unsigned long)tnp->nlink); 606 printf("\tmode 0%o, owner %d, group %d, size %zd\n", 607 tnp->mode, tnp->uid, tnp->gid, 608 tnp->size); 609 610 if (vp->v_type == VFIFO) 611 fifo_printinfo(vp); 612 613 printf("\n"); 614 615 return (0); 616 } 617 618 static int 619 tarfs_strategy(struct vop_strategy_args *ap) 620 { 621 struct uio auio; 622 struct iovec iov; 623 struct tarfs_node *tnp; 624 struct buf *bp; 625 off_t off; 626 size_t len; 627 int error; 628 629 tnp = VP_TO_TARFS_NODE(ap->a_vp); 630 bp = ap->a_bp; 631 MPASS(bp->b_iocmd == BIO_READ); 632 MPASS(bp->b_iooffset >= 0); 633 MPASS(bp->b_bcount > 0); 634 MPASS(bp->b_bufsize >= bp->b_bcount); 635 TARFS_DPF(VNODE, "%s(%p=%s, %zu, %ld/%ld)\n", __func__, tnp, 636 tnp->name, (size_t)bp->b_iooffset, bp->b_bcount, bp->b_bufsize); 637 iov.iov_base = bp->b_data; 638 iov.iov_len = bp->b_bcount; 639 off = bp->b_iooffset; 640 len = bp->b_bcount; 641 bp->b_resid = len; 642 if (off > tnp->size) { 643 /* XXX read beyond EOF - figure out correct handling */ 644 error = EIO; 645 goto out; 646 } 647 if (off + len > tnp->size) { 648 /* clip to file length */ 649 len = tnp->size - off; 650 } 651 auio.uio_iov = &iov; 652 auio.uio_iovcnt = 1; 653 auio.uio_offset = off; 654 auio.uio_resid = len; 655 auio.uio_segflg = UIO_SYSSPACE; 656 auio.uio_rw = UIO_READ; 657 auio.uio_td = curthread; 658 error = tarfs_read_file(tnp, len, &auio); 659 bp->b_resid -= len - auio.uio_resid; 660 out: 661 if (error != 0) { 662 bp->b_ioflags |= BIO_ERROR; 663 bp->b_error = error; 664 } 665 bp->b_flags |= B_DONE; 666 return (0); 667 } 668 669 static int 670 tarfs_vptofh(struct vop_vptofh_args *ap) 671 { 672 struct tarfs_fid *tfp; 673 struct tarfs_node *tnp; 674 _Static_assert(sizeof(struct tarfs_fid) <= sizeof(struct fid), 675 "struct tarfs_fid cannot be larger than struct fid"); 676 677 tfp = (struct tarfs_fid *)ap->a_fhp; 678 tnp = VP_TO_TARFS_NODE(ap->a_vp); 679 680 tfp->len = sizeof(struct tarfs_fid); 681 tfp->ino = tnp->ino; 682 tfp->gen = tnp->gen; 683 684 return (0); 685 } 686 687 struct vop_vector tarfs_vnodeops = { 688 .vop_default = &default_vnodeops, 689 690 .vop_access = tarfs_access, 691 .vop_bmap = tarfs_bmap, 692 .vop_cachedlookup = tarfs_lookup, 693 .vop_close = tarfs_close, 694 .vop_getattr = tarfs_getattr, 695 .vop_lookup = vfs_cache_lookup, 696 .vop_open = tarfs_open, 697 .vop_print = tarfs_print, 698 .vop_read = tarfs_read, 699 .vop_readdir = tarfs_readdir, 700 .vop_readlink = tarfs_readlink, 701 .vop_reclaim = tarfs_reclaim, 702 .vop_strategy = tarfs_strategy, 703 .vop_vptofh = tarfs_vptofh, 704 }; 705 VFS_VOP_VECTOR_REGISTER(tarfs_vnodeops); 706