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