1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 22 /* 23 * Copyright 2012 Joyent, Inc. All rights reserved. 24 */ 25 26 #include <sys/types.h> 27 #include <sys/param.h> 28 #include <sys/t_lock.h> 29 #include <sys/systm.h> 30 #include <sys/sysmacros.h> 31 #include <sys/user.h> 32 #include <sys/time.h> 33 #include <sys/vfs.h> 34 #include <sys/vfs_opreg.h> 35 #include <sys/vnode.h> 36 #include <sys/file.h> 37 #include <sys/fcntl.h> 38 #include <sys/flock.h> 39 #include <sys/kmem.h> 40 #include <sys/errno.h> 41 #include <sys/stat.h> 42 #include <sys/cred.h> 43 #include <sys/dirent.h> 44 #include <sys/pathname.h> 45 #include <sys/fs/hyprlofs.h> 46 #include <sys/fs/hyprlofs_info.h> 47 #include <sys/mman.h> 48 #include <vm/pvn.h> 49 #include <sys/cmn_err.h> 50 #include <sys/buf.h> 51 #include <sys/policy.h> 52 #include <fs/fs_subr.h> 53 #include <sys/ddi.h> 54 #include <sys/sunddi.h> 55 56 static int hyprlofs_add_entry(vnode_t *, char *, char *, cred_t *, 57 caller_context_t *); 58 static int hyprlofs_rm_entry(vnode_t *, char *, cred_t *, caller_context_t *, 59 int); 60 static int hyprlofs_rm_all(vnode_t *, cred_t *, caller_context_t *, int); 61 static int hyprlofs_remove(vnode_t *, char *, cred_t *, caller_context_t *, 62 int); 63 static int hyprlofs_get_all(vnode_t *, intptr_t, cred_t *, caller_context_t *, 64 int); 65 66 /* 67 * This is a somewhat arbitrary upper limit on the number of entries we can 68 * pass in on a single add/rm ioctl call. This is only used to validate that 69 * the input list looks sane. 70 */ 71 #define MAX_IOCTL_PARAMS 100000 72 73 static int 74 hyprlofs_open(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *ct) 75 { 76 vnode_t *rvp; 77 int error; 78 79 rvp = REALVP(*vpp); 80 81 if (VTOHLN(*vpp)->hln_looped == 0) 82 return (0); 83 84 /* 85 * looped back, pass through to real vnode. Need to hold new reference 86 * to vp since VOP_OPEN() may decide to release it. 87 */ 88 VN_HOLD(rvp); 89 error = VOP_OPEN(&rvp, flag, cr, ct); 90 ASSERT(rvp->v_count > 1); 91 VN_RELE(rvp); 92 93 return (error); 94 } 95 96 static int 97 hyprlofs_close(vnode_t *vp, int flag, int count, offset_t offset, cred_t *cr, 98 caller_context_t *ct) 99 { 100 if (VTOHLN(vp)->hln_looped == 0) { 101 cleanlocks(vp, ttoproc(curthread)->p_pid, 0); 102 cleanshares(vp, ttoproc(curthread)->p_pid); 103 return (0); 104 } 105 106 return (VOP_CLOSE(REALVP(vp), flag, count, offset, cr, ct)); 107 } 108 109 static int 110 hyprlofs_read(vnode_t *vp, struct uio *uiop, int ioflag, cred_t *cr, 111 caller_context_t *ct) 112 { 113 return (VOP_READ(REALVP(vp), uiop, ioflag, cr, ct)); 114 } 115 116 static int 117 hyprlofs_write(vnode_t *vp, struct uio *uiop, int ioflag, cred_t *cr, 118 caller_context_t *ct) 119 { 120 /* We don't support writing to non-regular files */ 121 if (vp->v_type != VREG) 122 return (EINVAL); 123 124 if (vn_is_readonly(vp)) 125 return (EROFS); 126 127 return (VOP_WRITE(REALVP(vp), uiop, ioflag, cr, ct)); 128 } 129 130 /* ARGSUSED */ 131 static int 132 hyprlofs_ioctl(vnode_t *vp, int cmd, intptr_t data, int flag, 133 cred_t *cr, int *rvalp, caller_context_t *ct) 134 { 135 int len, cnt, error; 136 int i; 137 model_t model; 138 char path[MAXPATHLEN]; 139 char nm[MAXPATHLEN]; 140 141 /* We only support the hyprlofs ioctls on the root vnode */ 142 if (!(vp->v_flag & VROOT)) 143 return (ENOTTY); 144 145 /* 146 * Check if managing hyprlofs is allowed. 147 */ 148 if (secpolicy_hyprlofs_control(cr) != 0) 149 return (EPERM); 150 151 if (cmd == HYPRLOFS_ADD_ENTRIES || cmd == HYPRLOFS_RM_ENTRIES) { 152 model = get_udatamodel(); 153 154 if (model == DATAMODEL_NATIVE) { 155 hyprlofs_entries_t ebuf; 156 hyprlofs_entry_t *e; 157 158 if (copyin((void *)data, &ebuf, sizeof (ebuf))) 159 return (EFAULT); 160 cnt = ebuf.hle_len; 161 if (cnt > MAX_IOCTL_PARAMS) 162 return (EINVAL); 163 len = sizeof (hyprlofs_entry_t) * cnt; 164 165 e = kmem_alloc(len, KM_SLEEP); 166 if (copyin((void *)(ebuf.hle_entries), e, len)) { 167 kmem_free(e, len); 168 return (EFAULT); 169 } 170 171 for (i = 0; i < cnt; i++) { 172 if (e[i].hle_nlen == 0 || 173 e[i].hle_nlen > MAXPATHLEN) 174 return (EINVAL); 175 176 if (copyin(e[i].hle_name, nm, e[i].hle_nlen) 177 != 0) { 178 kmem_free(e, len); 179 return (EFAULT); 180 } 181 nm[e[i].hle_nlen] = '\0'; 182 183 if (cmd == HYPRLOFS_ADD_ENTRIES) { 184 if (e[i].hle_plen == 0 || 185 e[i].hle_plen > MAXPATHLEN) 186 return (EINVAL); 187 188 if (copyin(e[i].hle_path, path, 189 e[i].hle_plen) != 0) { 190 kmem_free(e, len); 191 return (EFAULT); 192 } 193 path[e[i].hle_plen] = '\0'; 194 195 if ((error = hyprlofs_add_entry(vp, 196 path, nm, cr, ct)) != 0) { 197 kmem_free(e, len); 198 return (error); 199 } 200 } else { 201 if ((error = hyprlofs_rm_entry(vp, nm, 202 cr, ct, flag)) != 0) { 203 kmem_free(e, len); 204 return (error); 205 } 206 } 207 } 208 209 kmem_free(e, len); 210 return (0); 211 212 } else { 213 hyprlofs_entries32_t ebuf32; 214 hyprlofs_entry32_t *e32; 215 216 if (copyin((void *)data, &ebuf32, sizeof (ebuf32))) 217 return (EFAULT); 218 219 cnt = ebuf32.hle_len; 220 if (cnt > MAX_IOCTL_PARAMS) 221 return (EINVAL); 222 len = sizeof (hyprlofs_entry32_t) * cnt; 223 224 e32 = kmem_alloc(len, KM_SLEEP); 225 if (copyin((void *)(unsigned long)(ebuf32.hle_entries), 226 e32, len)) { 227 kmem_free(e32, len); 228 return (EFAULT); 229 } 230 231 for (i = 0; i < cnt; i++) { 232 if (e32[i].hle_nlen == 0 || 233 e32[i].hle_nlen > MAXPATHLEN) 234 return (EINVAL); 235 236 if (copyin((void *)(unsigned long) 237 e32[i].hle_name, nm, 238 e32[i].hle_nlen) != 0) { 239 kmem_free(e32, len); 240 return (EFAULT); 241 } 242 nm[e32[i].hle_nlen] = '\0'; 243 244 if (cmd == HYPRLOFS_ADD_ENTRIES) { 245 if (e32[i].hle_plen == 0 || 246 e32[i].hle_plen > MAXPATHLEN) 247 return (EINVAL); 248 249 if (copyin((void *)(unsigned long) 250 e32[i].hle_path, path, 251 e32[i].hle_plen) != 0) { 252 kmem_free(e32, len); 253 return (EFAULT); 254 } 255 path[e32[i].hle_plen] = '\0'; 256 257 if ((error = hyprlofs_add_entry(vp, 258 path, nm, cr, ct)) != 0) { 259 kmem_free(e32, len); 260 return (error); 261 } 262 } else { 263 if ((error = hyprlofs_rm_entry(vp, nm, 264 cr, ct, flag)) != 0) { 265 kmem_free(e32, len); 266 return (error); 267 } 268 } 269 } 270 271 kmem_free(e32, len); 272 return (0); 273 } 274 } 275 276 if (cmd == HYPRLOFS_RM_ALL) { 277 return (hyprlofs_rm_all(vp, cr, ct, flag)); 278 } 279 280 if (cmd == HYPRLOFS_GET_ENTRIES) { 281 return (hyprlofs_get_all(vp, data, cr, ct, flag)); 282 } 283 284 return (ENOTTY); 285 } 286 287 /*ARGSUSED2*/ 288 static int 289 hyprlofs_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr, 290 caller_context_t *ct) 291 { 292 hlnode_t *tp = (hlnode_t *)VTOHLN(vp); 293 294 mutex_enter(&tp->hln_tlock); 295 vap->va_type = vp->v_type; 296 vap->va_mode = tp->hln_mode & MODEMASK; 297 vap->va_uid = tp->hln_uid; 298 vap->va_gid = tp->hln_gid; 299 vap->va_fsid = tp->hln_fsid; 300 vap->va_nodeid = (ino64_t)tp->hln_nodeid; 301 vap->va_nlink = tp->hln_nlink; 302 vap->va_size = (u_offset_t)tp->hln_size; 303 vap->va_atime = tp->hln_atime; 304 vap->va_mtime = tp->hln_mtime; 305 vap->va_ctime = tp->hln_ctime; 306 vap->va_blksize = PAGESIZE; 307 vap->va_rdev = tp->hln_rdev; 308 vap->va_seq = tp->hln_seq; 309 310 vap->va_nblocks = (fsblkcnt64_t)btodb(ptob(btopr(vap->va_size))); 311 mutex_exit(&tp->hln_tlock); 312 return (0); 313 } 314 315 /*ARGSUSED4*/ 316 static int 317 hyprlofs_setattr(vnode_t *vp, vattr_t *vap, int flags, 318 cred_t *cr, caller_context_t *ct) 319 { 320 hlnode_t *tp = (hlnode_t *)VTOHLN(vp); 321 int error = 0; 322 vattr_t *get; 323 long mask; 324 325 /* 326 * Cannot set these attributes 327 */ 328 if ((vap->va_mask & AT_NOSET) || (vap->va_mask & AT_XVATTR)) 329 return (EINVAL); 330 331 mutex_enter(&tp->hln_tlock); 332 333 get = &tp->hln_attr; 334 /* 335 * Change file access modes. Must be owner or have sufficient 336 * privileges. 337 */ 338 error = secpolicy_vnode_setattr(cr, vp, vap, get, flags, 339 hyprlofs_taccess, tp); 340 341 if (error) 342 goto out; 343 344 mask = vap->va_mask; 345 346 if (mask & AT_MODE) { 347 get->va_mode &= S_IFMT; 348 get->va_mode |= vap->va_mode & ~S_IFMT; 349 } 350 351 if (mask & AT_UID) 352 get->va_uid = vap->va_uid; 353 if (mask & AT_GID) 354 get->va_gid = vap->va_gid; 355 if (mask & AT_ATIME) 356 get->va_atime = vap->va_atime; 357 if (mask & AT_MTIME) 358 get->va_mtime = vap->va_mtime; 359 360 if (mask & (AT_UID | AT_GID | AT_MODE | AT_MTIME)) 361 gethrestime(&tp->hln_ctime); 362 363 out: 364 mutex_exit(&tp->hln_tlock); 365 return (error); 366 } 367 368 static int 369 hyprlofs_access(vnode_t *vp, int mode, int flags, cred_t *cr, 370 caller_context_t *ct) 371 { 372 hlnode_t *tp = (hlnode_t *)VTOHLN(vp); 373 int error; 374 375 if (mode & VWRITE) { 376 if (vp->v_type == VREG && vn_is_readonly(vp)) 377 return (EROFS); 378 } 379 if (VTOHLN(vp)->hln_looped == 1) 380 return (VOP_ACCESS(REALVP(vp), mode, flags, cr, ct)); 381 382 mutex_enter(&tp->hln_tlock); 383 error = hyprlofs_taccess(tp, mode, cr); 384 mutex_exit(&tp->hln_tlock); 385 return (error); 386 } 387 388 /* ARGSUSED3 */ 389 static int 390 hyprlofs_lookup(vnode_t *dvp, char *nm, vnode_t **vpp, struct pathname *pnp, 391 int flags, vnode_t *rdir, cred_t *cr, caller_context_t *ct, 392 int *direntflags, pathname_t *realpnp) 393 { 394 hlnode_t *tp = (hlnode_t *)VTOHLN(dvp); 395 hlnode_t *ntp = NULL; 396 int error; 397 398 if (VTOHLN(dvp)->hln_looped == 1) 399 return (VOP_LOOKUP(REALVP(dvp), nm, vpp, pnp, flags, rdir, 400 cr, ct, direntflags, realpnp)); 401 402 if (flags & LOOKUP_XATTR) 403 return (EINVAL); 404 405 /* Null component name is a synonym for directory being searched. */ 406 if (*nm == '\0') { 407 VN_HOLD(dvp); 408 *vpp = dvp; 409 return (0); 410 } 411 ASSERT(tp); 412 413 if ((error = hyprlofs_dirlookup(tp, nm, &ntp, cr)) == 0) { 414 ASSERT(ntp); 415 *vpp = HLNTOV(ntp); 416 } 417 return (error); 418 } 419 420 /* 421 * Create the loopback from the hyprlofs vnode to the real vnode. 422 */ 423 static int 424 hyprlofs_loopback(vnode_t *dvp, vnode_t *rvp, char *nm, vattr_t *vap, 425 int mode, cred_t *cr, caller_context_t *ct) 426 { 427 hlnode_t *parent; 428 hlfsmount_t *tm; 429 int error; 430 hlnode_t *oldtp; 431 vnode_t *vp; 432 433 parent = (hlnode_t *)VTOHLN(dvp); 434 tm = (hlfsmount_t *)VTOHLM(dvp); 435 error = 0; 436 oldtp = NULL; 437 438 if (vap->va_type == VREG && (vap->va_mode & VSVTX)) { 439 /* we don't support the sticky bit */ 440 vap->va_mode &= ~VSVTX; 441 } else if (vap->va_type == VNON) { 442 return (EINVAL); 443 } 444 445 /* Null component name is a synonym for directory being searched. */ 446 if (*nm == '\0') { 447 VN_HOLD(dvp); 448 oldtp = parent; 449 } else { 450 error = hyprlofs_dirlookup(parent, nm, &oldtp, cr); 451 } 452 453 if (error == 0) { /* name found */ 454 ASSERT(oldtp); 455 456 rw_enter(&oldtp->hln_rwlock, RW_WRITER); 457 458 /* 459 * if create/read-only an existing directory, allow it 460 */ 461 if ((oldtp->hln_type == VDIR) && (mode & VWRITE)) 462 error = EISDIR; 463 else { 464 error = hyprlofs_taccess(oldtp, mode, cr); 465 } 466 467 if (error) { 468 rw_exit(&oldtp->hln_rwlock); 469 hlnode_rele(oldtp); 470 return (error); 471 } 472 473 vp = HLNTOV(oldtp); 474 rw_exit(&oldtp->hln_rwlock); 475 476 if (vp->v_type == VREG) { 477 hlnode_rele(oldtp); 478 return (EEXIST); 479 } 480 481 vnevent_create(vp, ct); 482 return (0); 483 } 484 485 if (error != ENOENT) 486 return (error); 487 488 rw_enter(&parent->hln_rwlock, RW_WRITER); 489 error = hyprlofs_direnter(tm, parent, nm, DE_CREATE, rvp, vap, NULL, 490 cr); 491 rw_exit(&parent->hln_rwlock); 492 493 return (error); 494 } 495 496 /* 497 * Create an in-memory directory based on the add-entry ioctl name. 498 * If the dir exists, return EEXIST but still also return node in vpp. 499 */ 500 static int 501 hyprlofs_mkdir(vnode_t *dvp, char *nm, vattr_t *va, vnode_t **vpp, cred_t *cr) 502 { 503 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp); 504 hlnode_t *self = NULL; 505 hlfsmount_t *tm = (hlfsmount_t *)VTOHLM(dvp); 506 int error; 507 508 /* 509 * Might be dangling directory. Catch it here, because a ENOENT return 510 * from hyprlofs_dirlookup() is a valid return. 511 */ 512 if (parent->hln_nlink == 0) 513 return (ENOENT); 514 515 error = hyprlofs_dirlookup(parent, nm, &self, cr); 516 if (error == 0) { 517 ASSERT(self); 518 hlnode_rele(self); 519 /* We can't loop in under a looped in directory */ 520 if (self->hln_looped) 521 return (EACCES); 522 *vpp = HLNTOV(self); 523 return (EEXIST); 524 } 525 if (error != ENOENT) 526 return (error); 527 528 rw_enter(&parent->hln_rwlock, RW_WRITER); 529 error = hyprlofs_direnter(tm, parent, nm, DE_MKDIR, (vnode_t *)NULL, 530 va, &self, cr); 531 rw_exit(&parent->hln_rwlock); 532 533 if (error == 0 || error == EEXIST) { 534 hlnode_rele(self); 535 *vpp = HLNTOV(self); 536 } 537 538 return (error); 539 } 540 541 /* 542 * Loop in a file or directory into the namespace. 543 */ 544 static int 545 hyprlofs_add_entry(vnode_t *vp, char *fspath, char *fsname, 546 cred_t *cr, caller_context_t *ct) 547 { 548 int error; 549 char *p, *pnm; 550 vnode_t *realvp, *dvp; 551 vattr_t va; 552 553 /* 554 * Get vnode for the real file/dir. We'll have a hold on realvp which 555 * we won't vn_rele until hyprlofs_inactive. 556 */ 557 if ((error = lookupname(fspath, UIO_SYSSPACE, FOLLOW, NULLVPP, 558 &realvp)) != 0) 559 return (error); 560 561 /* no devices allowed */ 562 if (IS_DEVVP(realvp)) { 563 VN_RELE(realvp); 564 return (ENODEV); 565 } 566 567 /* 568 * realvp may be an AUTOFS node, in which case we perform a VOP_ACCESS 569 * to trigger the mount of the intended filesystem. This causes a 570 * loopback mount of the intended filesystem instead of the AUTOFS 571 * filesystem. 572 */ 573 if ((error = VOP_ACCESS(realvp, 0, 0, cr, NULL)) != 0) { 574 VN_RELE(realvp); 575 return (error); 576 } 577 578 /* 579 * We're interested in the top most filesystem. This is specially 580 * important when fspath is a trigger AUTOFS node, since we're really 581 * interested in mounting the filesystem AUTOFS mounted as result of 582 * the VOP_ACCESS() call not the AUTOFS node itself. 583 */ 584 if (vn_mountedvfs(realvp) != NULL) { 585 if ((error = traverse(&realvp)) != 0) { 586 VN_RELE(realvp); 587 return (error); 588 } 589 } 590 591 va.va_type = VNON; 592 /* 593 * If the target name is a path, make sure we have all of the 594 * intermediate directories, creating them if necessary. 595 */ 596 dvp = vp; 597 pnm = p = fsname; 598 599 /* path cannot be absolute */ 600 if (*p == '/') { 601 VN_RELE(realvp); 602 return (EINVAL); 603 } 604 605 for (p = strchr(pnm, '/'); p != NULL; p = strchr(pnm, '/')) { 606 if (va.va_type == VNON) 607 /* use the top-level dir as the template va for mkdir */ 608 if ((error = VOP_GETATTR(vp, &va, 0, cr, NULL)) != 0) { 609 VN_RELE(realvp); 610 return (error); 611 } 612 613 *p = '\0'; 614 615 /* Path component cannot be empty or relative */ 616 if (pnm[0] == '\0' || (pnm[0] == '.' && pnm[1] == '.')) { 617 VN_RELE(realvp); 618 return (EINVAL); 619 } 620 621 if ((error = hyprlofs_mkdir(dvp, pnm, &va, &dvp, cr)) != 0 && 622 error != EEXIST) { 623 VN_RELE(realvp); 624 return (error); 625 } 626 627 *p = '/'; 628 pnm = p + 1; 629 } 630 631 /* The file name is required */ 632 if (pnm[0] == '\0') { 633 VN_RELE(realvp); 634 return (EINVAL); 635 } 636 637 /* Now use the real file's va as the template va */ 638 if ((error = VOP_GETATTR(realvp, &va, 0, cr, NULL)) != 0) { 639 VN_RELE(realvp); 640 return (error); 641 } 642 643 /* Make the vnode */ 644 error = hyprlofs_loopback(dvp, realvp, pnm, &va, va.va_mode, cr, ct); 645 if (error != 0) 646 VN_RELE(realvp); 647 return (error); 648 } 649 650 /* 651 * Remove a looped in file from the namespace. 652 */ 653 static int 654 hyprlofs_rm_entry(vnode_t *dvp, char *fsname, cred_t *cr, caller_context_t *ct, 655 int flags) 656 { 657 int error; 658 char *p, *pnm; 659 hlnode_t *parent; 660 hlnode_t *fndtp; 661 662 pnm = p = fsname; 663 664 /* path cannot be absolute */ 665 if (*p == '/') 666 return (EINVAL); 667 668 /* 669 * If the target name is a path, get the containing dir and simple 670 * file name. 671 */ 672 parent = (hlnode_t *)VTOHLN(dvp); 673 for (p = strchr(pnm, '/'); p != NULL; p = strchr(pnm, '/')) { 674 *p = '\0'; 675 676 /* Path component cannot be empty or relative */ 677 if (pnm[0] == '\0' || (pnm[0] == '.' && pnm[1] == '.')) 678 return (EINVAL); 679 680 if ((error = hyprlofs_dirlookup(parent, pnm, &fndtp, cr)) != 0) 681 return (error); 682 683 dvp = HLNTOV(fndtp); 684 parent = fndtp; 685 pnm = p + 1; 686 } 687 688 /* The file name is required */ 689 if (pnm[0] == '\0') 690 return (EINVAL); 691 692 /* Remove the entry from the parent dir */ 693 return (hyprlofs_remove(dvp, pnm, cr, ct, flags)); 694 } 695 696 /* 697 * Remove all looped in files from the namespace. 698 */ 699 static int 700 hyprlofs_rm_all(vnode_t *dvp, cred_t *cr, caller_context_t *ct, 701 int flags) 702 { 703 int error = 0; 704 hlnode_t *hp = (hlnode_t *)VTOHLN(dvp); 705 hldirent_t *hdp; 706 707 hlnode_hold(hp); 708 709 /* 710 * There's a window here where someone could have removed 711 * all the entries in the directory after we put a hold on the 712 * vnode but before we grabbed the rwlock. Just return. 713 */ 714 if (hp->hln_dir == NULL) { 715 if (hp->hln_nlink) { 716 panic("empty directory 0x%p", (void *)hp); 717 /*NOTREACHED*/ 718 } 719 goto done; 720 } 721 722 hdp = hp->hln_dir; 723 while (hdp) { 724 hlnode_t *fndhp; 725 726 if (strcmp(hdp->hld_name, ".") == 0 || 727 strcmp(hdp->hld_name, "..") == 0) { 728 hdp = hdp->hld_next; 729 continue; 730 } 731 732 /* This holds the fndhp vnode */ 733 error = hyprlofs_dirlookup(hp, hdp->hld_name, &fndhp, cr); 734 if (error != 0) 735 goto done; 736 hlnode_rele(fndhp); 737 738 if (fndhp->hln_looped == 0) { 739 /* recursively remove contents of this subdir */ 740 if (fndhp->hln_type == VDIR) { 741 vnode_t *tvp = HLNTOV(fndhp); 742 743 error = hyprlofs_rm_all(tvp, cr, ct, flags); 744 if (error != 0) 745 goto done; 746 } 747 } 748 749 /* remove the entry */ 750 error = hyprlofs_remove(dvp, hdp->hld_name, cr, ct, flags); 751 if (error != 0) 752 goto done; 753 754 hdp = hp->hln_dir; 755 } 756 757 done: 758 hlnode_rele(hp); 759 return (error); 760 } 761 762 /* 763 * Get a list of all looped in files in the namespace. 764 */ 765 static int 766 hyprlofs_get_all_entries(vnode_t *dvp, hyprlofs_curr_entry_t *hcp, 767 char *prefix, int *pcnt, int n_max, 768 cred_t *cr, caller_context_t *ct, int flags) 769 { 770 int error = 0; 771 int too_big = 0; 772 int cnt; 773 int len; 774 hlnode_t *hp = (hlnode_t *)VTOHLN(dvp); 775 hldirent_t *hdp; 776 char *path; 777 778 cnt = *pcnt; 779 path = kmem_alloc(MAXPATHLEN, KM_SLEEP); 780 781 hlnode_hold(hp); 782 783 /* 784 * There's a window here where someone could have removed 785 * all the entries in the directory after we put a hold on the 786 * vnode but before we grabbed the rwlock. Just return. 787 */ 788 if (hp->hln_dir == NULL) { 789 if (hp->hln_nlink) { 790 panic("empty directory 0x%p", (void *)hp); 791 /*NOTREACHED*/ 792 } 793 goto done; 794 } 795 796 hdp = hp->hln_dir; 797 while (hdp) { 798 hlnode_t *fndhp; 799 vnode_t *tvp; 800 801 if (strcmp(hdp->hld_name, ".") == 0 || 802 strcmp(hdp->hld_name, "..") == 0) { 803 hdp = hdp->hld_next; 804 continue; 805 } 806 807 /* This holds the fndhp vnode */ 808 error = hyprlofs_dirlookup(hp, hdp->hld_name, &fndhp, cr); 809 if (error != 0) 810 goto done; 811 hlnode_rele(fndhp); 812 813 if (fndhp->hln_looped == 0) { 814 /* recursively get contents of this subdir */ 815 VERIFY(fndhp->hln_type == VDIR); 816 tvp = HLNTOV(fndhp); 817 818 if (*prefix == '\0') 819 (void) strlcpy(path, hdp->hld_name, MAXPATHLEN); 820 else 821 (void) snprintf(path, MAXPATHLEN, "%s/%s", 822 prefix, hdp->hld_name); 823 824 error = hyprlofs_get_all_entries(tvp, hcp, path, 825 &cnt, n_max, cr, ct, flags); 826 827 if (error == E2BIG) { 828 too_big = 1; 829 error = 0; 830 } 831 if (error != 0) 832 goto done; 833 } else { 834 if (cnt < n_max) { 835 char *p; 836 837 if (*prefix == '\0') 838 (void) strlcpy(path, hdp->hld_name, 839 MAXPATHLEN); 840 else 841 (void) snprintf(path, MAXPATHLEN, 842 "%s/%s", prefix, hdp->hld_name); 843 844 len = strlen(path); 845 ASSERT(len <= MAXPATHLEN); 846 if (copyout(path, (void *)(hcp[cnt].hce_name), 847 len)) { 848 error = EFAULT; 849 goto done; 850 } 851 852 tvp = REALVP(HLNTOV(fndhp)); 853 if (tvp->v_path == NULL) { 854 p = "<unknown>"; 855 } else { 856 p = tvp->v_path; 857 } 858 len = strlen(p); 859 ASSERT(len <= MAXPATHLEN); 860 if (copyout(p, (void *)(hcp[cnt].hce_path), 861 len)) { 862 error = EFAULT; 863 goto done; 864 } 865 } 866 867 cnt++; 868 if (cnt > n_max) 869 too_big = 1; 870 } 871 872 hdp = hdp->hld_next; 873 } 874 875 done: 876 hlnode_rele(hp); 877 kmem_free(path, MAXPATHLEN); 878 879 *pcnt = cnt; 880 if (error == 0 && too_big == 1) 881 error = E2BIG; 882 883 return (error); 884 } 885 886 /* 887 * Return a list of all looped in files in the namespace. 888 */ 889 static int 890 hyprlofs_get_all(vnode_t *dvp, intptr_t data, cred_t *cr, caller_context_t *ct, 891 int flags) 892 { 893 int limit, cnt, error; 894 model_t model; 895 hyprlofs_curr_entry_t *e; 896 897 model = get_udatamodel(); 898 899 if (model == DATAMODEL_NATIVE) { 900 hyprlofs_curr_entries_t ebuf; 901 902 if (copyin((void *)data, &ebuf, sizeof (ebuf))) 903 return (EFAULT); 904 limit = ebuf.hce_cnt; 905 e = ebuf.hce_entries; 906 if (limit > MAX_IOCTL_PARAMS) 907 return (EINVAL); 908 909 } else { 910 hyprlofs_curr_entries32_t ebuf32; 911 912 if (copyin((void *)data, &ebuf32, sizeof (ebuf32))) 913 return (EFAULT); 914 915 limit = ebuf32.hce_cnt; 916 e = (hyprlofs_curr_entry_t *)(unsigned long) 917 (ebuf32.hce_entries); 918 if (limit > MAX_IOCTL_PARAMS) 919 return (EINVAL); 920 } 921 922 cnt = 0; 923 error = hyprlofs_get_all_entries(dvp, e, "", &cnt, limit, cr, ct, 924 flags); 925 926 if (error == 0 || error == E2BIG) { 927 if (model == DATAMODEL_NATIVE) { 928 hyprlofs_curr_entries_t ebuf; 929 930 ebuf.hce_cnt = cnt; 931 if (copyout(&ebuf, (void *)data, sizeof (ebuf))) 932 return (EFAULT); 933 934 } else { 935 hyprlofs_curr_entries32_t ebuf32; 936 937 ebuf32.hce_cnt = cnt; 938 if (copyout(&ebuf32, (void *)data, sizeof (ebuf32))) 939 return (EFAULT); 940 } 941 } 942 943 return (error); 944 } 945 946 /* ARGSUSED3 */ 947 static int 948 hyprlofs_remove(vnode_t *dvp, char *nm, cred_t *cr, caller_context_t *ct, 949 int flags) 950 { 951 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp); 952 int error; 953 hlnode_t *hp = NULL; 954 955 /* This holds the hp vnode */ 956 error = hyprlofs_dirlookup(parent, nm, &hp, cr); 957 if (error) 958 return (error); 959 960 ASSERT(hp); 961 rw_enter(&parent->hln_rwlock, RW_WRITER); 962 rw_enter(&hp->hln_rwlock, RW_WRITER); 963 964 error = hyprlofs_dirdelete(parent, hp, nm, DR_REMOVE, cr); 965 966 rw_exit(&hp->hln_rwlock); 967 rw_exit(&parent->hln_rwlock); 968 vnevent_remove(HLNTOV(hp), dvp, nm, ct); 969 970 /* 971 * We've now dropped the dir link so by rele-ing our vnode we should 972 * clean up in hyprlofs_inactive. 973 */ 974 hlnode_rele(hp); 975 976 return (error); 977 } 978 979 /* ARGSUSED4 */ 980 static int 981 hyprlofs_rmdir(vnode_t *dvp, char *nm, vnode_t *cdir, cred_t *cr, 982 caller_context_t *ct, int flags) 983 { 984 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp); 985 hlnode_t *self = NULL; 986 vnode_t *vp; 987 int error = 0; 988 989 /* Return error if removing . or .. */ 990 if (strcmp(nm, ".") == 0) 991 return (EINVAL); 992 if (strcmp(nm, "..") == 0) 993 return (EEXIST); /* Should be ENOTEMPTY */ 994 error = hyprlofs_dirlookup(parent, nm, &self, cr); 995 if (error) 996 return (error); 997 998 rw_enter(&parent->hln_rwlock, RW_WRITER); 999 rw_enter(&self->hln_rwlock, RW_WRITER); 1000 1001 vp = HLNTOV(self); 1002 if (vp == dvp || vp == cdir) { 1003 error = EINVAL; 1004 goto done1; 1005 } 1006 if (self->hln_type != VDIR) { 1007 error = ENOTDIR; 1008 goto done1; 1009 } 1010 1011 /* 1012 * When a dir is looped in, we only remove the in-memory dir, not the 1013 * backing dir. 1014 */ 1015 if (self->hln_looped == 0) { 1016 mutex_enter(&self->hln_tlock); 1017 if (self->hln_nlink > 2) { 1018 mutex_exit(&self->hln_tlock); 1019 error = EEXIST; 1020 goto done1; 1021 } 1022 mutex_exit(&self->hln_tlock); 1023 1024 if (vn_vfswlock(vp)) { 1025 error = EBUSY; 1026 goto done1; 1027 } 1028 if (vn_mountedvfs(vp) != NULL) { 1029 error = EBUSY; 1030 goto done; 1031 } 1032 1033 /* 1034 * Check for an empty directory, i.e. only includes entries for 1035 * "." and ".." 1036 */ 1037 if (self->hln_dirents > 2) { 1038 error = EEXIST; /* SIGH should be ENOTEMPTY */ 1039 /* 1040 * Update atime because checking hln_dirents is 1041 * equivalent to reading the directory 1042 */ 1043 gethrestime(&self->hln_atime); 1044 goto done; 1045 } 1046 1047 error = hyprlofs_dirdelete(parent, self, nm, DR_RMDIR, cr); 1048 } else { 1049 error = hyprlofs_dirdelete(parent, self, nm, DR_REMOVE, cr); 1050 } 1051 1052 done: 1053 if (self->hln_looped == 0) 1054 vn_vfsunlock(vp); 1055 done1: 1056 rw_exit(&self->hln_rwlock); 1057 rw_exit(&parent->hln_rwlock); 1058 vnevent_rmdir(HLNTOV(self), dvp, nm, ct); 1059 1060 /* 1061 * We've now dropped the dir link so by rele-ing our vnode we should 1062 * clean up in hyprlofs_inactive. 1063 */ 1064 hlnode_rele(self); 1065 1066 return (error); 1067 } 1068 1069 static int 1070 hyprlofs_readdir(vnode_t *vp, struct uio *uiop, cred_t *cr, int *eofp, 1071 caller_context_t *ct, int flags) 1072 { 1073 hlnode_t *hp = (hlnode_t *)VTOHLN(vp); 1074 hldirent_t *hdp; 1075 int error = 0; 1076 size_t namelen; 1077 struct dirent64 *dp; 1078 ulong_t offset; 1079 ulong_t total_bytes_wanted; 1080 long outcount = 0; 1081 long bufsize; 1082 int reclen; 1083 caddr_t outbuf; 1084 1085 if (VTOHLN(vp)->hln_looped == 1) 1086 return (VOP_READDIR(REALVP(vp), uiop, cr, eofp, ct, flags)); 1087 1088 if (uiop->uio_loffset >= MAXOFF_T) { 1089 if (eofp) 1090 *eofp = 1; 1091 return (0); 1092 } 1093 /* assuming syscall has already called hln_rwlock */ 1094 ASSERT(RW_READ_HELD(&hp->hln_rwlock)); 1095 1096 if (uiop->uio_iovcnt != 1) 1097 return (EINVAL); 1098 1099 if (vp->v_type != VDIR) 1100 return (ENOTDIR); 1101 1102 /* 1103 * There's a window here where someone could have removed 1104 * all the entries in the directory after we put a hold on the 1105 * vnode but before we grabbed the rwlock. Just return. 1106 */ 1107 if (hp->hln_dir == NULL) { 1108 if (hp->hln_nlink) { 1109 panic("empty directory 0x%p", (void *)hp); 1110 /*NOTREACHED*/ 1111 } 1112 return (0); 1113 } 1114 1115 /* Get space for multiple dir entries */ 1116 total_bytes_wanted = uiop->uio_iov->iov_len; 1117 bufsize = total_bytes_wanted + sizeof (struct dirent64); 1118 outbuf = kmem_alloc(bufsize, KM_SLEEP); 1119 1120 dp = (struct dirent64 *)((uintptr_t)outbuf); 1121 1122 offset = 0; 1123 hdp = hp->hln_dir; 1124 while (hdp) { 1125 namelen = strlen(hdp->hld_name); /* no +1 needed */ 1126 offset = hdp->hld_offset; 1127 if (offset >= uiop->uio_offset) { 1128 reclen = (int)DIRENT64_RECLEN(namelen); 1129 if (outcount + reclen > total_bytes_wanted) { 1130 if (!outcount) 1131 /* Buffer too small for any entries. */ 1132 error = EINVAL; 1133 break; 1134 } 1135 ASSERT(hdp->hld_hlnode != NULL); 1136 1137 /* zero out uninitialized bytes */ 1138 (void) strncpy(dp->d_name, hdp->hld_name, 1139 DIRENT64_NAMELEN(reclen)); 1140 dp->d_reclen = (ushort_t)reclen; 1141 dp->d_ino = (ino64_t)hdp->hld_hlnode->hln_nodeid; 1142 dp->d_off = (offset_t)hdp->hld_offset + 1; 1143 dp = (struct dirent64 *) 1144 ((uintptr_t)dp + dp->d_reclen); 1145 outcount += reclen; 1146 ASSERT(outcount <= bufsize); 1147 } 1148 hdp = hdp->hld_next; 1149 } 1150 1151 if (!error) 1152 error = uiomove(outbuf, outcount, UIO_READ, uiop); 1153 1154 if (!error) { 1155 /* 1156 * If we reached the end of the list our offset should now be 1157 * just past the end. 1158 */ 1159 if (!hdp) { 1160 offset += 1; 1161 if (eofp) 1162 *eofp = 1; 1163 } else if (eofp) 1164 *eofp = 0; 1165 uiop->uio_offset = offset; 1166 } 1167 gethrestime(&hp->hln_atime); 1168 kmem_free(outbuf, bufsize); 1169 return (error); 1170 } 1171 1172 static int 1173 hyprlofs_fsync(vnode_t *vp, int syncflag, cred_t *cr, caller_context_t *ct) 1174 { 1175 if (VTOHLN(vp)->hln_looped == 1) 1176 return (VOP_FSYNC(REALVP(vp), syncflag, cr, ct)); 1177 return (0); 1178 } 1179 1180 /* ARGSUSED */ 1181 static void 1182 hyprlofs_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct) 1183 { 1184 hlnode_t *hp = (hlnode_t *)VTOHLN(vp); 1185 hlfsmount_t *hm = (hlfsmount_t *)VFSTOHLM(vp->v_vfsp); 1186 1187 rw_enter(&hp->hln_rwlock, RW_WRITER); 1188 1189 mutex_enter(&hp->hln_tlock); 1190 mutex_enter(&vp->v_lock); 1191 ASSERT(vp->v_count >= 1); 1192 1193 /* 1194 * If we don't have the last hold or the link count is non-zero, 1195 * there's nothing to do except drop our hold. 1196 */ 1197 if (vp->v_count > 1 || hp->hln_nlink != 0) { 1198 vp->v_count--; 1199 mutex_exit(&vp->v_lock); 1200 mutex_exit(&hp->hln_tlock); 1201 rw_exit(&hp->hln_rwlock); 1202 return; 1203 } 1204 1205 mutex_exit(&vp->v_lock); 1206 mutex_exit(&hp->hln_tlock); 1207 1208 /* release hold on the real vnode now */ 1209 if (hp->hln_looped == 1 && hp->hln_realvp != NULL) 1210 VN_RELE(hp->hln_realvp); 1211 1212 /* Here's our chance to send invalid event while we're between locks */ 1213 vn_invalid(HLNTOV(hp)); 1214 1215 mutex_enter(&hm->hlm_contents); 1216 if (hp->hln_forw == NULL) 1217 hm->hlm_rootnode->hln_back = hp->hln_back; 1218 else 1219 hp->hln_forw->hln_back = hp->hln_back; 1220 hp->hln_back->hln_forw = hp->hln_forw; 1221 mutex_exit(&hm->hlm_contents); 1222 rw_exit(&hp->hln_rwlock); 1223 rw_destroy(&hp->hln_rwlock); 1224 mutex_destroy(&hp->hln_tlock); 1225 vn_free(HLNTOV(hp)); 1226 hyprlofs_memfree(hp, sizeof (hlnode_t)); 1227 } 1228 1229 static int 1230 hyprlofs_fid(vnode_t *vp, struct fid *fidp, caller_context_t *ct) 1231 { 1232 hlnode_t *hp = (hlnode_t *)VTOHLN(vp); 1233 hlfid_t *hfid; 1234 1235 if (VTOHLN(vp)->hln_looped == 1) 1236 return (VOP_FID(REALVP(vp), fidp, ct)); 1237 1238 if (fidp->fid_len < (sizeof (hlfid_t) - sizeof (ushort_t))) { 1239 fidp->fid_len = sizeof (hlfid_t) - sizeof (ushort_t); 1240 return (ENOSPC); 1241 } 1242 1243 hfid = (hlfid_t *)fidp; 1244 bzero(hfid, sizeof (hlfid_t)); 1245 hfid->hlfid_len = (int)sizeof (hlfid_t) - sizeof (ushort_t); 1246 1247 hfid->hlfid_ino = hp->hln_nodeid; 1248 hfid->hlfid_gen = hp->hln_gen; 1249 1250 return (0); 1251 } 1252 1253 static int 1254 hyprlofs_getpage(vnode_t *vp, offset_t off, size_t len, uint_t *protp, 1255 page_t *pl[], size_t plsz, struct seg *seg, caddr_t addr, enum seg_rw rw, 1256 cred_t *cr, caller_context_t *ct) 1257 { 1258 ASSERT(VTOHLN(vp)->hln_looped == 1); 1259 return (VOP_GETPAGE(REALVP(vp), off, len, protp, pl, plsz, seg, addr, 1260 rw, cr, ct)); 1261 } 1262 1263 int 1264 hyprlofs_putpage(vnode_t *vp, offset_t off, size_t len, int flags, 1265 cred_t *cr, caller_context_t *ct) 1266 { 1267 ASSERT(VTOHLN(vp)->hln_looped == 1); 1268 return (VOP_PUTPAGE(REALVP(vp), off, len, flags, cr, ct)); 1269 } 1270 1271 static int 1272 hyprlofs_map(vnode_t *vp, offset_t off, struct as *as, caddr_t *addrp, 1273 size_t len, uchar_t prot, uchar_t maxprot, uint_t flags, cred_t *cr, 1274 caller_context_t *ct) 1275 { 1276 ASSERT(VTOHLN(vp)->hln_looped == 1); 1277 return (VOP_MAP(REALVP(vp), off, as, addrp, len, prot, maxprot, flags, 1278 cr, ct)); 1279 } 1280 1281 static int 1282 hyprlofs_addmap(vnode_t *vp, offset_t off, struct as *as, caddr_t addr, 1283 size_t len, uchar_t prot, uchar_t maxprot, uint_t flags, cred_t *cr, 1284 caller_context_t *ct) 1285 { 1286 ASSERT(VTOHLN(vp)->hln_looped == 1); 1287 return (VOP_ADDMAP(REALVP(vp), off, as, addr, len, prot, maxprot, 1288 flags, cr, ct)); 1289 } 1290 1291 static int 1292 hyprlofs_delmap(vnode_t *vp, offset_t off, struct as *as, caddr_t addr, 1293 size_t len, uint_t prot, uint_t maxprot, uint_t flags, cred_t *cr, 1294 caller_context_t *ct) 1295 { 1296 ASSERT(VTOHLN(vp)->hln_looped == 1); 1297 return (VOP_DELMAP(REALVP(vp), off, as, addr, len, prot, maxprot, 1298 flags, cr, ct)); 1299 } 1300 1301 static int 1302 hyprlofs_space(vnode_t *vp, int cmd, struct flock64 *bfp, int flag, 1303 offset_t offset, cred_t *cr, caller_context_t *ct) 1304 { 1305 ASSERT(VTOHLN(vp)->hln_looped == 1); 1306 return (VOP_SPACE(REALVP(vp), cmd, bfp, flag, offset, cr, ct)); 1307 } 1308 1309 static int 1310 hyprlofs_seek(vnode_t *vp, offset_t ooff, offset_t *noffp, 1311 caller_context_t *ct) 1312 { 1313 if (VTOHLN(vp)->hln_looped == 0) 1314 return ((*noffp < 0 || *noffp > MAXOFFSET_T) ? EINVAL : 0); 1315 1316 return (VOP_SEEK(REALVP(vp), ooff, noffp, ct)); 1317 } 1318 1319 static int 1320 hyprlofs_rwlock(vnode_t *vp, int write_lock, caller_context_t *ct) 1321 { 1322 hlnode_t *hp = VTOHLN(vp); 1323 1324 if (hp->hln_looped == 1) 1325 return (VOP_RWLOCK(REALVP(vp), write_lock, ct)); 1326 1327 if (write_lock) { 1328 rw_enter(&hp->hln_rwlock, RW_WRITER); 1329 } else { 1330 rw_enter(&hp->hln_rwlock, RW_READER); 1331 } 1332 return (write_lock); 1333 } 1334 1335 static void 1336 hyprlofs_rwunlock(vnode_t *vp, int write_lock, caller_context_t *ct) 1337 { 1338 hlnode_t *hp = VTOHLN(vp); 1339 1340 if (hp->hln_looped == 1) { 1341 VOP_RWUNLOCK(REALVP(vp), write_lock, ct); 1342 return; 1343 } 1344 1345 rw_exit(&hp->hln_rwlock); 1346 } 1347 1348 static int 1349 hyprlofs_pathconf(vnode_t *vp, int cmd, ulong_t *valp, cred_t *cr, 1350 caller_context_t *ct) 1351 { 1352 int error; 1353 1354 if (VTOHLN(vp)->hln_looped == 1) 1355 return (VOP_PATHCONF(REALVP(vp), cmd, valp, cr, ct)); 1356 1357 switch (cmd) { 1358 case _PC_XATTR_ENABLED: 1359 case _PC_XATTR_EXISTS: 1360 case _PC_SATTR_ENABLED: 1361 case _PC_SATTR_EXISTS: 1362 error = EINVAL; 1363 break; 1364 case _PC_TIMESTAMP_RESOLUTION: 1365 /* nanosecond timestamp resolution */ 1366 *valp = 1L; 1367 error = 0; 1368 break; 1369 default: 1370 error = fs_pathconf(vp, cmd, valp, cr, ct); 1371 } 1372 return (error); 1373 } 1374 1375 1376 struct vnodeops *hyprlofs_vnodeops; 1377 1378 const fs_operation_def_t hyprlofs_vnodeops_template[] = { 1379 VOPNAME_OPEN, { .vop_open = hyprlofs_open }, 1380 VOPNAME_CLOSE, { .vop_close = hyprlofs_close }, 1381 VOPNAME_READ, { .vop_read = hyprlofs_read }, 1382 VOPNAME_WRITE, { .vop_write = hyprlofs_write }, 1383 VOPNAME_IOCTL, { .vop_ioctl = hyprlofs_ioctl }, 1384 VOPNAME_GETATTR, { .vop_getattr = hyprlofs_getattr }, 1385 VOPNAME_SETATTR, { .vop_setattr = hyprlofs_setattr }, 1386 VOPNAME_ACCESS, { .vop_access = hyprlofs_access }, 1387 VOPNAME_LOOKUP, { .vop_lookup = hyprlofs_lookup }, 1388 VOPNAME_CREATE, { .error = fs_error }, 1389 VOPNAME_REMOVE, { .vop_remove = hyprlofs_remove }, 1390 VOPNAME_LINK, { .error = fs_error }, 1391 VOPNAME_RENAME, { .error = fs_error }, 1392 VOPNAME_MKDIR, { .error = fs_error }, 1393 VOPNAME_RMDIR, { .vop_rmdir = hyprlofs_rmdir }, 1394 VOPNAME_READDIR, { .vop_readdir = hyprlofs_readdir }, 1395 VOPNAME_SYMLINK, { .error = fs_error }, 1396 VOPNAME_READLINK, { .error = fs_error }, 1397 VOPNAME_FSYNC, { .vop_fsync = hyprlofs_fsync }, 1398 VOPNAME_INACTIVE, { .vop_inactive = hyprlofs_inactive }, 1399 VOPNAME_FID, { .vop_fid = hyprlofs_fid }, 1400 VOPNAME_RWLOCK, { .vop_rwlock = hyprlofs_rwlock }, 1401 VOPNAME_RWUNLOCK, { .vop_rwunlock = hyprlofs_rwunlock }, 1402 VOPNAME_SEEK, { .vop_seek = hyprlofs_seek }, 1403 VOPNAME_SPACE, { .vop_space = hyprlofs_space }, 1404 VOPNAME_GETPAGE, { .vop_getpage = hyprlofs_getpage }, 1405 VOPNAME_PUTPAGE, { .vop_putpage = hyprlofs_putpage }, 1406 VOPNAME_MAP, { .vop_map = hyprlofs_map }, 1407 VOPNAME_ADDMAP, { .vop_addmap = hyprlofs_addmap }, 1408 VOPNAME_DELMAP, { .vop_delmap = hyprlofs_delmap }, 1409 VOPNAME_PATHCONF, { .vop_pathconf = hyprlofs_pathconf }, 1410 VOPNAME_VNEVENT, { .vop_vnevent = fs_vnevent_support }, 1411 NULL, NULL 1412 };