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