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, &realvp)) 558 return (error); 559 560 /* no devices allowed */ 561 if (IS_DEVVP(realvp)) { 562 VN_RELE(realvp); 563 return (ENODEV); 564 } 565 566 /* 567 * realvp may be an AUTOFS node, in which case we perform a VOP_ACCESS 568 * to trigger the mount of the intended filesystem. This causes a 569 * loopback mount of the intended filesystem instead of the AUTOFS 570 * filesystem. 571 */ 572 if ((error = VOP_ACCESS(realvp, 0, 0, cr, NULL)) != 0) { 573 VN_RELE(realvp); 574 return (error); 575 } 576 577 /* 578 * We're interested in the top most filesystem. This is specially 579 * important when fspath is a trigger AUTOFS node, since we're really 580 * interested in mounting the filesystem AUTOFS mounted as result of 581 * the VOP_ACCESS() call not the AUTOFS node itself. 582 */ 583 if (vn_mountedvfs(realvp) != NULL) { 584 if (error = traverse(&realvp)) { 585 VN_RELE(realvp); 586 return (error); 587 } 588 } 589 590 va.va_type = VNON; 591 /* 592 * If the target name is a path, make sure we have all of the 593 * intermediate directories, creating them if necessary. 594 */ 595 dvp = vp; 596 pnm = p = fsname; 597 598 /* path cannot be absolute */ 599 if (*p == '/') { 600 VN_RELE(realvp); 601 return (EINVAL); 602 } 603 604 for (p = strchr(pnm, '/'); p != NULL; p = strchr(pnm, '/')) { 605 if (va.va_type == VNON) 606 /* use the top-level dir as the template va for mkdir */ 607 if ((error = VOP_GETATTR(vp, &va, 0, cr, NULL)) != 0) { 608 VN_RELE(realvp); 609 return (error); 610 } 611 612 *p = '\0'; 613 614 /* Path component cannot be empty or relative */ 615 if (pnm[0] == '\0' || (pnm[0] == '.' && pnm[1] == '.')) { 616 VN_RELE(realvp); 617 return (EINVAL); 618 } 619 620 if ((error = hyprlofs_mkdir(dvp, pnm, &va, &dvp, cr)) != 0 && 621 error != EEXIST) { 622 VN_RELE(realvp); 623 return (error); 624 } 625 626 *p = '/'; 627 pnm = p + 1; 628 } 629 630 /* The file name is required */ 631 if (pnm[0] == '\0') { 632 VN_RELE(realvp); 633 return (EINVAL); 634 } 635 636 /* Now use the real file's va as the template va */ 637 if ((error = VOP_GETATTR(realvp, &va, 0, cr, NULL)) != 0) { 638 VN_RELE(realvp); 639 return (error); 640 } 641 642 /* Make the vnode */ 643 error = hyprlofs_loopback(dvp, realvp, pnm, &va, va.va_mode, cr, ct); 644 if (error != 0) 645 VN_RELE(realvp); 646 return (error); 647 } 648 649 /* 650 * Remove a looped in file from the namespace. 651 */ 652 static int 653 hyprlofs_rm_entry(vnode_t *dvp, char *fsname, cred_t *cr, caller_context_t *ct, 654 int flags) 655 { 656 int error; 657 char *p, *pnm; 658 hlnode_t *parent; 659 hlnode_t *fndtp; 660 661 pnm = p = fsname; 662 663 /* path cannot be absolute */ 664 if (*p == '/') 665 return (EINVAL); 666 667 /* 668 * If the target name is a path, get the containing dir and simple 669 * file name. 670 */ 671 parent = (hlnode_t *)VTOHLN(dvp); 672 for (p = strchr(pnm, '/'); p != NULL; p = strchr(pnm, '/')) { 673 *p = '\0'; 674 675 /* Path component cannot be empty or relative */ 676 if (pnm[0] == '\0' || (pnm[0] == '.' && pnm[1] == '.')) 677 return (EINVAL); 678 679 if ((error = hyprlofs_dirlookup(parent, pnm, &fndtp, cr)) != 0) 680 return (error); 681 682 dvp = HLNTOV(fndtp); 683 parent = fndtp; 684 pnm = p + 1; 685 } 686 687 /* The file name is required */ 688 if (pnm[0] == '\0') 689 return (EINVAL); 690 691 /* Remove the entry from the parent dir */ 692 return (hyprlofs_remove(dvp, pnm, cr, ct, flags)); 693 } 694 695 /* 696 * Remove all looped in files from the namespace. 697 */ 698 static int 699 hyprlofs_rm_all(vnode_t *dvp, cred_t *cr, caller_context_t *ct, 700 int flags) 701 { 702 int error = 0; 703 hlnode_t *hp = (hlnode_t *)VTOHLN(dvp); 704 hldirent_t *hdp; 705 706 hlnode_hold(hp); 707 708 /* 709 * There's a window here where someone could have removed 710 * all the entries in the directory after we put a hold on the 711 * vnode but before we grabbed the rwlock. Just return. 712 */ 713 if (hp->hln_dir == NULL) { 714 if (hp->hln_nlink) { 715 panic("empty directory 0x%p", (void *)hp); 716 /*NOTREACHED*/ 717 } 718 goto done; 719 } 720 721 hdp = hp->hln_dir; 722 while (hdp) { 723 hlnode_t *fndhp; 724 725 if (strcmp(hdp->hld_name, ".") == 0 || 726 strcmp(hdp->hld_name, "..") == 0) { 727 hdp = hdp->hld_next; 728 continue; 729 } 730 731 /* This holds the fndhp vnode */ 732 error = hyprlofs_dirlookup(hp, hdp->hld_name, &fndhp, cr); 733 if (error != 0) 734 goto done; 735 hlnode_rele(fndhp); 736 737 if (fndhp->hln_looped == 0) { 738 /* recursively remove contents of this subdir */ 739 if (fndhp->hln_type == VDIR) { 740 vnode_t *tvp = HLNTOV(fndhp); 741 742 error = hyprlofs_rm_all(tvp, cr, ct, flags); 743 if (error != 0) 744 goto done; 745 } 746 } 747 748 /* remove the entry */ 749 error = hyprlofs_remove(dvp, hdp->hld_name, cr, ct, flags); 750 if (error != 0) 751 goto done; 752 753 hdp = hp->hln_dir; 754 } 755 756 done: 757 hlnode_rele(hp); 758 return (error); 759 } 760 761 /* 762 * Get a list of all looped in files in the namespace. 763 */ 764 static int 765 hyprlofs_get_all_entries(vnode_t *dvp, hyprlofs_curr_entry_t *hcp, 766 char *prefix, int *pcnt, int n_max, 767 cred_t *cr, caller_context_t *ct, int flags) 768 { 769 int error = 0; 770 int too_big = 0; 771 int cnt; 772 int len; 773 hlnode_t *hp = (hlnode_t *)VTOHLN(dvp); 774 hldirent_t *hdp; 775 char *path; 776 777 cnt = *pcnt; 778 path = kmem_alloc(MAXPATHLEN, KM_SLEEP); 779 780 hlnode_hold(hp); 781 782 /* 783 * There's a window here where someone could have removed 784 * all the entries in the directory after we put a hold on the 785 * vnode but before we grabbed the rwlock. Just return. 786 */ 787 if (hp->hln_dir == NULL) { 788 if (hp->hln_nlink) { 789 panic("empty directory 0x%p", (void *)hp); 790 /*NOTREACHED*/ 791 } 792 goto done; 793 } 794 795 hdp = hp->hln_dir; 796 while (hdp) { 797 hlnode_t *fndhp; 798 vnode_t *tvp; 799 800 if (strcmp(hdp->hld_name, ".") == 0 || 801 strcmp(hdp->hld_name, "..") == 0) { 802 hdp = hdp->hld_next; 803 continue; 804 } 805 806 /* This holds the fndhp vnode */ 807 error = hyprlofs_dirlookup(hp, hdp->hld_name, &fndhp, cr); 808 if (error != 0) 809 goto done; 810 hlnode_rele(fndhp); 811 812 if (fndhp->hln_looped == 0) { 813 /* recursively get contents of this subdir */ 814 VERIFY(fndhp->hln_type == VDIR); 815 tvp = HLNTOV(fndhp); 816 817 if (*prefix == '\0') 818 (void) strlcpy(path, hdp->hld_name, MAXPATHLEN); 819 else 820 (void) snprintf(path, MAXPATHLEN, "%s/%s", 821 prefix, hdp->hld_name); 822 823 error = hyprlofs_get_all_entries(tvp, hcp, path, 824 &cnt, n_max, cr, ct, flags); 825 826 if (error == E2BIG) { 827 too_big = 1; 828 error = 0; 829 } 830 if (error != 0) 831 goto done; 832 } else { 833 if (cnt < n_max) { 834 char *p; 835 836 if (*prefix == '\0') 837 (void) strlcpy(path, hdp->hld_name, 838 MAXPATHLEN); 839 else 840 (void) snprintf(path, MAXPATHLEN, 841 "%s/%s", prefix, hdp->hld_name); 842 843 len = strlen(path); 844 ASSERT(len <= MAXPATHLEN); 845 if (copyout(path, (void *)(hcp[cnt].hce_name), 846 len)) { 847 error = EFAULT; 848 goto done; 849 } 850 851 tvp = REALVP(HLNTOV(fndhp)); 852 if (tvp->v_path == NULL) { 853 p = "<unknown>"; 854 } else { 855 p = tvp->v_path; 856 } 857 len = strlen(p); 858 ASSERT(len <= MAXPATHLEN); 859 if (copyout(p, (void *)(hcp[cnt].hce_path), 860 len)) { 861 error = EFAULT; 862 goto done; 863 } 864 } 865 866 cnt++; 867 if (cnt > n_max) 868 too_big = 1; 869 } 870 871 hdp = hdp->hld_next; 872 } 873 874 done: 875 hlnode_rele(hp); 876 kmem_free(path, MAXPATHLEN); 877 878 *pcnt = cnt; 879 if (error == 0 && too_big == 1) 880 error = E2BIG; 881 882 return (error); 883 } 884 885 /* 886 * Return a list of all looped in files in the namespace. 887 */ 888 static int 889 hyprlofs_get_all(vnode_t *dvp, intptr_t data, cred_t *cr, caller_context_t *ct, 890 int flags) 891 { 892 int limit, cnt, error; 893 model_t model; 894 hyprlofs_curr_entry_t *e; 895 896 model = get_udatamodel(); 897 898 if (model == DATAMODEL_NATIVE) { 899 hyprlofs_curr_entries_t ebuf; 900 901 if (copyin((void *)data, &ebuf, sizeof (ebuf))) 902 return (EFAULT); 903 limit = ebuf.hce_cnt; 904 e = ebuf.hce_entries; 905 if (limit > MAX_IOCTL_PARAMS) 906 return (EINVAL); 907 908 } else { 909 hyprlofs_curr_entries32_t ebuf32; 910 911 if (copyin((void *)data, &ebuf32, sizeof (ebuf32))) 912 return (EFAULT); 913 914 limit = ebuf32.hce_cnt; 915 e = (hyprlofs_curr_entry_t *)(unsigned long) 916 (ebuf32.hce_entries); 917 if (limit > MAX_IOCTL_PARAMS) 918 return (EINVAL); 919 } 920 921 cnt = 0; 922 error = hyprlofs_get_all_entries(dvp, e, "", &cnt, limit, cr, ct, 923 flags); 924 925 if (error == 0 || error == E2BIG) { 926 if (model == DATAMODEL_NATIVE) { 927 hyprlofs_curr_entries_t ebuf; 928 929 ebuf.hce_cnt = cnt; 930 if (copyout(&ebuf, (void *)data, sizeof (ebuf))) 931 return (EFAULT); 932 933 } else { 934 hyprlofs_curr_entries32_t ebuf32; 935 936 ebuf32.hce_cnt = cnt; 937 if (copyout(&ebuf32, (void *)data, sizeof (ebuf32))) 938 return (EFAULT); 939 } 940 } 941 942 return (error); 943 } 944 945 /* ARGSUSED3 */ 946 static int 947 hyprlofs_remove(vnode_t *dvp, char *nm, cred_t *cr, caller_context_t *ct, 948 int flags) 949 { 950 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp); 951 int error; 952 hlnode_t *hp = NULL; 953 954 /* This holds the hp vnode */ 955 error = hyprlofs_dirlookup(parent, nm, &hp, cr); 956 if (error) 957 return (error); 958 959 ASSERT(hp); 960 rw_enter(&parent->hln_rwlock, RW_WRITER); 961 rw_enter(&hp->hln_rwlock, RW_WRITER); 962 963 error = hyprlofs_dirdelete(parent, hp, nm, DR_REMOVE, cr); 964 965 rw_exit(&hp->hln_rwlock); 966 rw_exit(&parent->hln_rwlock); 967 vnevent_remove(HLNTOV(hp), dvp, nm, ct); 968 969 /* 970 * We've now dropped the dir link so by rele-ing our vnode we should 971 * clean up in hyprlofs_inactive. 972 */ 973 hlnode_rele(hp); 974 975 return (error); 976 } 977 978 /* ARGSUSED4 */ 979 static int 980 hyprlofs_rmdir(vnode_t *dvp, char *nm, vnode_t *cdir, cred_t *cr, 981 caller_context_t *ct, int flags) 982 { 983 hlnode_t *parent = (hlnode_t *)VTOHLN(dvp); 984 hlnode_t *self = NULL; 985 vnode_t *vp; 986 int error = 0; 987 988 /* Return error if removing . or .. */ 989 if (strcmp(nm, ".") == 0) 990 return (EINVAL); 991 if (strcmp(nm, "..") == 0) 992 return (EEXIST); /* Should be ENOTEMPTY */ 993 error = hyprlofs_dirlookup(parent, nm, &self, cr); 994 if (error) 995 return (error); 996 997 rw_enter(&parent->hln_rwlock, RW_WRITER); 998 rw_enter(&self->hln_rwlock, RW_WRITER); 999 1000 vp = HLNTOV(self); 1001 if (vp == dvp || vp == cdir) { 1002 error = EINVAL; 1003 goto done1; 1004 } 1005 if (self->hln_type != VDIR) { 1006 error = ENOTDIR; 1007 goto done1; 1008 } 1009 1010 /* 1011 * When a dir is looped in, we only remove the in-memory dir, not the 1012 * backing dir. 1013 */ 1014 if (self->hln_looped == 0) { 1015 mutex_enter(&self->hln_tlock); 1016 if (self->hln_nlink > 2) { 1017 mutex_exit(&self->hln_tlock); 1018 error = EEXIST; 1019 goto done1; 1020 } 1021 mutex_exit(&self->hln_tlock); 1022 1023 if (vn_vfswlock(vp)) { 1024 error = EBUSY; 1025 goto done1; 1026 } 1027 if (vn_mountedvfs(vp) != NULL) { 1028 error = EBUSY; 1029 goto done; 1030 } 1031 1032 /* 1033 * Check for an empty directory, i.e. only includes entries for 1034 * "." and ".." 1035 */ 1036 if (self->hln_dirents > 2) { 1037 error = EEXIST; /* SIGH should be ENOTEMPTY */ 1038 /* 1039 * Update atime because checking hln_dirents is 1040 * equivalent to reading the directory 1041 */ 1042 gethrestime(&self->hln_atime); 1043 goto done; 1044 } 1045 1046 error = hyprlofs_dirdelete(parent, self, nm, DR_RMDIR, cr); 1047 } else { 1048 error = hyprlofs_dirdelete(parent, self, nm, DR_REMOVE, cr); 1049 } 1050 1051 done: 1052 if (self->hln_looped == 0) 1053 vn_vfsunlock(vp); 1054 done1: 1055 rw_exit(&self->hln_rwlock); 1056 rw_exit(&parent->hln_rwlock); 1057 vnevent_rmdir(HLNTOV(self), dvp, nm, ct); 1058 1059 /* 1060 * We've now dropped the dir link so by rele-ing our vnode we should 1061 * clean up in hyprlofs_inactive. 1062 */ 1063 hlnode_rele(self); 1064 1065 return (error); 1066 } 1067 1068 static int 1069 hyprlofs_readdir(vnode_t *vp, struct uio *uiop, cred_t *cr, int *eofp, 1070 caller_context_t *ct, int flags) 1071 { 1072 hlnode_t *hp = (hlnode_t *)VTOHLN(vp); 1073 hldirent_t *hdp; 1074 int error = 0; 1075 size_t namelen; 1076 struct dirent64 *dp; 1077 ulong_t offset; 1078 ulong_t total_bytes_wanted; 1079 long outcount = 0; 1080 long bufsize; 1081 int reclen; 1082 caddr_t outbuf; 1083 1084 if (VTOHLN(vp)->hln_looped == 1) 1085 return (VOP_READDIR(REALVP(vp), uiop, cr, eofp, ct, flags)); 1086 1087 if (uiop->uio_loffset >= MAXOFF_T) { 1088 if (eofp) 1089 *eofp = 1; 1090 return (0); 1091 } 1092 /* assuming syscall has already called hln_rwlock */ 1093 ASSERT(RW_READ_HELD(&hp->hln_rwlock)); 1094 1095 if (uiop->uio_iovcnt != 1) 1096 return (EINVAL); 1097 1098 if (vp->v_type != VDIR) 1099 return (ENOTDIR); 1100 1101 /* 1102 * There's a window here where someone could have removed 1103 * all the entries in the directory after we put a hold on the 1104 * vnode but before we grabbed the rwlock. Just return. 1105 */ 1106 if (hp->hln_dir == NULL) { 1107 if (hp->hln_nlink) { 1108 panic("empty directory 0x%p", (void *)hp); 1109 /*NOTREACHED*/ 1110 } 1111 return (0); 1112 } 1113 1114 /* Get space for multiple dir entries */ 1115 total_bytes_wanted = uiop->uio_iov->iov_len; 1116 bufsize = total_bytes_wanted + sizeof (struct dirent64); 1117 outbuf = kmem_alloc(bufsize, KM_SLEEP); 1118 1119 dp = (struct dirent64 *)((uintptr_t)outbuf); 1120 1121 offset = 0; 1122 hdp = hp->hln_dir; 1123 while (hdp) { 1124 namelen = strlen(hdp->hld_name); /* no +1 needed */ 1125 offset = hdp->hld_offset; 1126 if (offset >= uiop->uio_offset) { 1127 reclen = (int)DIRENT64_RECLEN(namelen); 1128 if (outcount + reclen > total_bytes_wanted) { 1129 if (!outcount) 1130 /* Buffer too small for any entries. */ 1131 error = EINVAL; 1132 break; 1133 } 1134 ASSERT(hdp->hld_hlnode != NULL); 1135 1136 /* zero out uninitialized bytes */ 1137 (void) strncpy(dp->d_name, hdp->hld_name, 1138 DIRENT64_NAMELEN(reclen)); 1139 dp->d_reclen = (ushort_t)reclen; 1140 dp->d_ino = (ino64_t)hdp->hld_hlnode->hln_nodeid; 1141 dp->d_off = (offset_t)hdp->hld_offset + 1; 1142 dp = (struct dirent64 *) 1143 ((uintptr_t)dp + dp->d_reclen); 1144 outcount += reclen; 1145 ASSERT(outcount <= bufsize); 1146 } 1147 hdp = hdp->hld_next; 1148 } 1149 1150 if (!error) 1151 error = uiomove(outbuf, outcount, UIO_READ, uiop); 1152 1153 if (!error) { 1154 /* 1155 * If we reached the end of the list our offset should now be 1156 * just past the end. 1157 */ 1158 if (!hdp) { 1159 offset += 1; 1160 if (eofp) 1161 *eofp = 1; 1162 } else if (eofp) 1163 *eofp = 0; 1164 uiop->uio_offset = offset; 1165 } 1166 gethrestime(&hp->hln_atime); 1167 kmem_free(outbuf, bufsize); 1168 return (error); 1169 } 1170 1171 static int 1172 hyprlofs_fsync(vnode_t *vp, int syncflag, cred_t *cr, caller_context_t *ct) 1173 { 1174 if (VTOHLN(vp)->hln_looped == 1) 1175 return (VOP_FSYNC(REALVP(vp), syncflag, cr, ct)); 1176 return (0); 1177 } 1178 1179 /* ARGSUSED */ 1180 static void 1181 hyprlofs_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct) 1182 { 1183 hlnode_t *hp = (hlnode_t *)VTOHLN(vp); 1184 hlfsmount_t *hm = (hlfsmount_t *)VFSTOHLM(vp->v_vfsp); 1185 1186 rw_enter(&hp->hln_rwlock, RW_WRITER); 1187 1188 mutex_enter(&hp->hln_tlock); 1189 mutex_enter(&vp->v_lock); 1190 ASSERT(vp->v_count >= 1); 1191 1192 /* 1193 * If we don't have the last hold or the link count is non-zero, 1194 * there's nothing to do except drop our hold. 1195 */ 1196 if (vp->v_count > 1 || hp->hln_nlink != 0) { 1197 vp->v_count--; 1198 mutex_exit(&vp->v_lock); 1199 mutex_exit(&hp->hln_tlock); 1200 rw_exit(&hp->hln_rwlock); 1201 return; 1202 } 1203 1204 mutex_exit(&vp->v_lock); 1205 mutex_exit(&hp->hln_tlock); 1206 1207 /* release hold on the real vnode now */ 1208 if (hp->hln_looped == 1 && hp->hln_realvp != NULL) 1209 VN_RELE(hp->hln_realvp); 1210 1211 /* Here's our chance to send invalid event while we're between locks */ 1212 vn_invalid(HLNTOV(hp)); 1213 1214 mutex_enter(&hm->hlm_contents); 1215 if (hp->hln_forw == NULL) 1216 hm->hlm_rootnode->hln_back = hp->hln_back; 1217 else 1218 hp->hln_forw->hln_back = hp->hln_back; 1219 hp->hln_back->hln_forw = hp->hln_forw; 1220 mutex_exit(&hm->hlm_contents); 1221 rw_exit(&hp->hln_rwlock); 1222 rw_destroy(&hp->hln_rwlock); 1223 mutex_destroy(&hp->hln_tlock); 1224 vn_free(HLNTOV(hp)); 1225 hyprlofs_memfree(hp, sizeof (hlnode_t)); 1226 } 1227 1228 static int 1229 hyprlofs_fid(vnode_t *vp, struct fid *fidp, caller_context_t *ct) 1230 { 1231 hlnode_t *hp = (hlnode_t *)VTOHLN(vp); 1232 hlfid_t *hfid; 1233 1234 if (VTOHLN(vp)->hln_looped == 1) 1235 return (VOP_FID(REALVP(vp), fidp, ct)); 1236 1237 if (fidp->fid_len < (sizeof (hlfid_t) - sizeof (ushort_t))) { 1238 fidp->fid_len = sizeof (hlfid_t) - sizeof (ushort_t); 1239 return (ENOSPC); 1240 } 1241 1242 hfid = (hlfid_t *)fidp; 1243 bzero(hfid, sizeof (hlfid_t)); 1244 hfid->hlfid_len = (int)sizeof (hlfid_t) - sizeof (ushort_t); 1245 1246 hfid->hlfid_ino = hp->hln_nodeid; 1247 hfid->hlfid_gen = hp->hln_gen; 1248 1249 return (0); 1250 } 1251 1252 static int 1253 hyprlofs_getpage(vnode_t *vp, offset_t off, size_t len, uint_t *protp, 1254 page_t *pl[], size_t plsz, struct seg *seg, caddr_t addr, enum seg_rw rw, 1255 cred_t *cr, caller_context_t *ct) 1256 { 1257 ASSERT(VTOHLN(vp)->hln_looped == 1); 1258 return (VOP_GETPAGE(REALVP(vp), off, len, protp, pl, plsz, seg, addr, 1259 rw, cr, ct)); 1260 } 1261 1262 int 1263 hyprlofs_putpage(vnode_t *vp, offset_t off, size_t len, int flags, 1264 cred_t *cr, caller_context_t *ct) 1265 { 1266 ASSERT(VTOHLN(vp)->hln_looped == 1); 1267 return (VOP_PUTPAGE(REALVP(vp), off, len, flags, cr, ct)); 1268 } 1269 1270 static int 1271 hyprlofs_map(vnode_t *vp, offset_t off, struct as *as, caddr_t *addrp, 1272 size_t len, uchar_t prot, uchar_t maxprot, uint_t flags, cred_t *cr, 1273 caller_context_t *ct) 1274 { 1275 ASSERT(VTOHLN(vp)->hln_looped == 1); 1276 return (VOP_MAP(REALVP(vp), off, as, addrp, len, prot, maxprot, flags, 1277 cr, ct)); 1278 } 1279 1280 static int 1281 hyprlofs_addmap(vnode_t *vp, offset_t off, struct as *as, caddr_t addr, 1282 size_t len, uchar_t prot, uchar_t maxprot, uint_t flags, cred_t *cr, 1283 caller_context_t *ct) 1284 { 1285 ASSERT(VTOHLN(vp)->hln_looped == 1); 1286 return (VOP_ADDMAP(REALVP(vp), off, as, addr, len, prot, maxprot, 1287 flags, cr, ct)); 1288 } 1289 1290 static int 1291 hyprlofs_delmap(vnode_t *vp, offset_t off, struct as *as, caddr_t addr, 1292 size_t len, uint_t prot, uint_t maxprot, uint_t flags, cred_t *cr, 1293 caller_context_t *ct) 1294 { 1295 ASSERT(VTOHLN(vp)->hln_looped == 1); 1296 return (VOP_DELMAP(REALVP(vp), off, as, addr, len, prot, maxprot, 1297 flags, cr, ct)); 1298 } 1299 1300 static int 1301 hyprlofs_space(vnode_t *vp, int cmd, struct flock64 *bfp, int flag, 1302 offset_t offset, cred_t *cr, caller_context_t *ct) 1303 { 1304 ASSERT(VTOHLN(vp)->hln_looped == 1); 1305 return (VOP_SPACE(REALVP(vp), cmd, bfp, flag, offset, cr, ct)); 1306 } 1307 1308 static int 1309 hyprlofs_seek(vnode_t *vp, offset_t ooff, offset_t *noffp, 1310 caller_context_t *ct) 1311 { 1312 if (VTOHLN(vp)->hln_looped == 0) 1313 return ((*noffp < 0 || *noffp > MAXOFFSET_T) ? EINVAL : 0); 1314 1315 return (VOP_SEEK(REALVP(vp), ooff, noffp, ct)); 1316 } 1317 1318 static int 1319 hyprlofs_rwlock(vnode_t *vp, int write_lock, caller_context_t *ct) 1320 { 1321 hlnode_t *hp = VTOHLN(vp); 1322 1323 if (hp->hln_looped == 1) 1324 return (VOP_RWLOCK(REALVP(vp), write_lock, ct)); 1325 1326 if (write_lock) { 1327 rw_enter(&hp->hln_rwlock, RW_WRITER); 1328 } else { 1329 rw_enter(&hp->hln_rwlock, RW_READER); 1330 } 1331 return (write_lock); 1332 } 1333 1334 static void 1335 hyprlofs_rwunlock(vnode_t *vp, int write_lock, caller_context_t *ct) 1336 { 1337 hlnode_t *hp = VTOHLN(vp); 1338 1339 if (hp->hln_looped == 1) { 1340 VOP_RWUNLOCK(REALVP(vp), write_lock, ct); 1341 return; 1342 } 1343 1344 rw_exit(&hp->hln_rwlock); 1345 } 1346 1347 static int 1348 hyprlofs_pathconf(vnode_t *vp, int cmd, ulong_t *valp, cred_t *cr, 1349 caller_context_t *ct) 1350 { 1351 int error; 1352 1353 if (VTOHLN(vp)->hln_looped == 1) 1354 return (VOP_PATHCONF(REALVP(vp), cmd, valp, cr, ct)); 1355 1356 switch (cmd) { 1357 case _PC_XATTR_ENABLED: 1358 case _PC_XATTR_EXISTS: 1359 case _PC_SATTR_ENABLED: 1360 case _PC_SATTR_EXISTS: 1361 error = EINVAL; 1362 break; 1363 case _PC_TIMESTAMP_RESOLUTION: 1364 /* nanosecond timestamp resolution */ 1365 *valp = 1L; 1366 error = 0; 1367 break; 1368 default: 1369 error = fs_pathconf(vp, cmd, valp, cr, ct); 1370 } 1371 return (error); 1372 } 1373 1374 1375 struct vnodeops *hyprlofs_vnodeops; 1376 1377 const fs_operation_def_t hyprlofs_vnodeops_template[] = { 1378 VOPNAME_OPEN, { .vop_open = hyprlofs_open }, 1379 VOPNAME_CLOSE, { .vop_close = hyprlofs_close }, 1380 VOPNAME_READ, { .vop_read = hyprlofs_read }, 1381 VOPNAME_WRITE, { .vop_write = hyprlofs_write }, 1382 VOPNAME_IOCTL, { .vop_ioctl = hyprlofs_ioctl }, 1383 VOPNAME_GETATTR, { .vop_getattr = hyprlofs_getattr }, 1384 VOPNAME_SETATTR, { .vop_setattr = hyprlofs_setattr }, 1385 VOPNAME_ACCESS, { .vop_access = hyprlofs_access }, 1386 VOPNAME_LOOKUP, { .vop_lookup = hyprlofs_lookup }, 1387 VOPNAME_CREATE, { .error = fs_error }, 1388 VOPNAME_REMOVE, { .vop_remove = hyprlofs_remove }, 1389 VOPNAME_LINK, { .error = fs_error }, 1390 VOPNAME_RENAME, { .error = fs_error }, 1391 VOPNAME_MKDIR, { .error = fs_error }, 1392 VOPNAME_RMDIR, { .vop_rmdir = hyprlofs_rmdir }, 1393 VOPNAME_READDIR, { .vop_readdir = hyprlofs_readdir }, 1394 VOPNAME_SYMLINK, { .error = fs_error }, 1395 VOPNAME_READLINK, { .error = fs_error }, 1396 VOPNAME_FSYNC, { .vop_fsync = hyprlofs_fsync }, 1397 VOPNAME_INACTIVE, { .vop_inactive = hyprlofs_inactive }, 1398 VOPNAME_FID, { .vop_fid = hyprlofs_fid }, 1399 VOPNAME_RWLOCK, { .vop_rwlock = hyprlofs_rwlock }, 1400 VOPNAME_RWUNLOCK, { .vop_rwunlock = hyprlofs_rwunlock }, 1401 VOPNAME_SEEK, { .vop_seek = hyprlofs_seek }, 1402 VOPNAME_SPACE, { .vop_space = hyprlofs_space }, 1403 VOPNAME_GETPAGE, { .vop_getpage = hyprlofs_getpage }, 1404 VOPNAME_PUTPAGE, { .vop_putpage = hyprlofs_putpage }, 1405 VOPNAME_MAP, { .vop_map = hyprlofs_map }, 1406 VOPNAME_ADDMAP, { .vop_addmap = hyprlofs_addmap }, 1407 VOPNAME_DELMAP, { .vop_delmap = hyprlofs_delmap }, 1408 VOPNAME_PATHCONF, { .vop_pathconf = hyprlofs_pathconf }, 1409 VOPNAME_VNEVENT, { .vop_vnevent = fs_vnevent_support }, 1410 NULL, NULL 1411 };