text: /* **********************************************************
   * Copyright 2007-2014, 2020 VMware, Inc.  All rights reserved.
   * **********************************************************/
  
  /*
   * vnops.c --
   *
   *      Vnode operations for the vmblock filesystem on FreeBSD.
   */
  
  /*-
   * Copyright (c) 1992, 1993
   *	The Regents of the University of California.  All rights reserved.
   *
   * This code is derived from software contributed to Berkeley by
   * John Heidemann of the UCLA Ficus project.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in the
   *    documentation and/or other materials provided with the distribution.
   *
   * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
   * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
   * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
   * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
   * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
   * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
   * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
   * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   *
   *	@(#)null_vnops.c	8.6 (Berkeley) 5/27/95
   *
   * Ancestors:
   *	@(#)lofs_vnops.c	1.2 (Berkeley) 6/18/92
   *	...and...
   *	@(#)nullfs_vnops.c 1.20 92/07/07 UCLA Ficus project
   *
   * $FreeBSD: src/sys/fs/nullfs/null_vnops.c,v 1.87.2.3 2006/03/13 03:05:26 jeff Exp $
   */
  
  /*
   * NB:  The following is the introductory commentary from FreeBSD's
   * null_vnops.c, which VMBlockFS is heavily based on.  It was kept intact in
   * order to provide a better conceptual introduction to FreeBSD's approach
   * to stackable file systems.
   */
  
  /*
   * Null Layer
   *
   * (See mount_nullfs(8) for more information.)
   *
   * The null layer duplicates a portion of the filesystem name space under
   * a new name.  In this respect, it is similar to the loopback filesystem.
   * It differs from the loopback fs in two respects:  it is implemented
   * using a stackable layers techniques, and its "null-node"s stack above
   * all lower-layer vnodes, not just over directory vnodes.
   *
   * The null layer has two purposes.  First, it serves as a demonstration
   * of layering by proving a layer which does nothing.  (It actually
   * does everything the loopback filesystem does, which is slightly more
   * than nothing.)  Second, the null layer can serve as a prototype layer.
   * Since it provides all necessary layer framework, new filesystem layers
   * can be created very easily be starting with a null layer.
   *
   * The remainder of this man page examines the null layer as a basis
   * for constructing new layers.
   *
   *
   * INSTANTIATING NEW NULL LAYERS
   *
   * New null layers are created with mount_nullfs(8).  Mount_nullfs(8)
   * takes two arguments, the pathname of the lower vfs (target-pn)
   * and the pathname where the null layer will appear in the namespace
   * (alias-pn).  After the null layer is put into place, the contents of
   * target-pn subtree will be aliased under alias-pn.
   *
   *
   * OPERATION OF A NULL LAYER
   *
   * The null layer is the minimum filesystem layer, simply bypassing
   * all possible operations to the lower layer for processing there.
   * The majority of its activity centers on the bypass routine, through
   * which nearly all vnode operations pass.
   *
   * The bypass routine accepts arbitrary vnode operations for handling by
   * the lower layer.  It begins by examing vnode operation arguments and
   * replacing any null-nodes by their lower-layer equivlants.  It then
   * invokes the operation on the lower layer.  Finally, it replaces the
   * null-nodes in the arguments and, if a vnode is return by the operation,
   * stacks a null-node on top of the returned vnode.
   *
   * Although bypass handles most operations, vop_getattr, vop_lock,
   * vop_unlock, vop_inactive, vop_reclaim, and vop_print are
   * not bypassed. Vop_getattr must change the fsid being returned.
   * Vop_lock and vop_unlock must handle any locking for the current
   * vnode as well as pass the lock request down.  Vop_inactive and
   * vop_reclaim are not bypassed so that they can handle freeing null-layer
   * specific data. Vop_print is not bypassed to avoid excessive debugging
   * information.  Also, certain vnode operations change the locking state
   * within the operation (create, mknod, remove, link, rename, mkdir,
   * rmdir, and symlink). Ideally these operations should not change the
   * lock state, but should be changed to let the caller of the function
   * unlock them. Otherwise all intermediate vnode layers (such as union,
   * umapfs, etc) must catch these functions to do the necessary locking
   * at their layer.
   *
   *
   * INSTANTIATING VNODE STACKS
   *
   * Mounting associates the null layer with a lower layer, effect
   * stacking two VFSes.  Vnode stacks are instead created on demand as
   * files are accessed.
   *
   * The initial mount creates a single vnode stack for the root of the
   * new null layer.  All other vnode stacks are created as a result of
   * vnode operations on this or other null vnode stacks.
   *
   * New vnode stacks come into existance as a result of an operation
   * which returns a vnode.  The bypass routine stacks a null-node above
   * the new vnode before returning it to the caller.
   *
   * For example, imagine mounting a null layer with "mount_nullfs
   * /usr/include /dev/layer/null".  Changing directory to /dev/layer/null
   * will assign the root null-node (which was created when the null layer
   * was mounted).  Now consider opening "sys".  A vop_lookup would be
   * done on the root null-node.  This operation would bypass through
   * to the lower layer which would return a vnode representing the UFS
   * "sys".  Null_bypass then builds a null-node aliasing the UFS "sys"
   * and returns this to the caller.  Later operations on the null-node
   * "sys" will repeat this process when constructing other vnode stacks.
   *
   *
   * CREATING OTHER FILE SYSTEM LAYERS
   *
   * One of the easiest ways to construct new filesystem layers is to make a
   * copy of the null layer, rename all files and variables, and then begin
   * modifing the copy.  Sed can be used to easily rename all variables.
   *
   * The umap layer is an example of a layer descended from the null layer.
   *
   *
   * INVOKING OPERATIONS ON LOWER LAYERS
   *
   * There are two techniques to invoke operations on a lower layer when the
   * operation cannot be completely bypassed.  Each method is appropriate
   * in different situations.  In both cases, it is the responsibility of
   * the aliasing layer to make the operation arguments "correct" for the
   * lower layer by mapping a vnode arguments to the lower layer.
   *
   * The first approach is to call the aliasing layer's bypass routine.
   * This method is most suitable when you wish to invoke the operation
   * currently being handled on the lower layer.  It has the advantage that
   * the bypass routine already must do argument mapping.  An example of
   * this is vop_getattr in the null layer.
   *
   * A second approach is to directly invoke vnode operations on the
   * lower layer with the VOP_OPERATIONNAME interface.  The advantage
   * of this method is that it is easy to invoke arbitrary operations on
   * the lower layer.  The disadvantage is that vnode arguments must be
   * manualy mapped.
   */
  
  #include <sys/param.h>
  #include <sys/systm.h>
  #include <sys/conf.h>
  #include <sys/kernel.h>
  #include <sys/lock.h>
  #include <sys/malloc.h>
  #include <sys/mount.h>
  #include <sys/mutex.h>
  #include <sys/namei.h>
  #include <sys/sysctl.h>
  #include <sys/vnode.h>
  #include <sys/proc.h>
  #include <sys/file.h>
  #include <sys/filedesc.h>
  #include <sys/kdb.h>
  #include "compat_freebsd.h"
  #if __FreeBSD_version >= 700055
  #include <sys/priv.h>
  #endif
  
  #include "vmblock_k.h"
  #include "vmblock.h"
  #include "block.h"
  
  #include <vm/vm.h>
  #include <vm/vm_extern.h>
  #include <vm/vm_object.h>
  #include <vm/vnode_pager.h>
  
  
  /*
   * Local functions
   */
  
  static fo_ioctl_t               VMBlockFileIoctl;
  static fo_close_t               VMBlockFileClose;
  static vop_access_t             VMBlockVopAccess;
  static vop_getattr_t            VMBlockVopGetAttr;
  static vop_getwritemount_t      VMBlockVopGetWriteMount;
  static vop_inactive_t           VMBlockVopInactive;
  static vop_ioctl_t              VMBlockVopIoctl;
  static vop_islocked_t           VMBlockVopIsLocked;
  static compat_vop_lock_t        VMBlockVopLock;
  static vop_lookup_t             VMBlockVopLookup;
  static vop_open_t               VMBlockVopOpen;
  static vop_print_t              VMBlockVopPrint;
  static vop_reclaim_t            VMBlockVopReclaim;
  static vop_rename_t             VMBlockVopRename;
  static vop_setattr_t            VMBlockVopSetAttr;
  static vop_unlock_t             VMBlockVopUnlock;
  
  
  /*
   * Local data
   */
  
  /*
   * The following is an ioctl(2) argument wrapper for VMBlockVopIoctl.
   * See the VMBlockFileOps blurb below for details.
   */
  struct VMBlockIoctlArgs {
     struct file  *fileDesc;      // file descriptor receiving ioctl request
     void         *data;          // user's ioctl argument
  };
  
  typedef struct VMBlockIoctlArgs VMBlockIoctlArgs;
  
  /*
   * VMBlockFS vnode operations vector --
   *   Following are the file system's entry points via VFS nodes (vnodes).
   *   See vnode(9) and sys/vnode.h for more information.  For details on
   *   the locking protocol[1], have a look at kern/vnode_if.src.
   *
   * 1.  Describes for which operations a vnode should be locked before 
   *     the operation is called or after it returns.
   */
  
  struct vop_vector VMBlockVnodeOps = {
     .vop_bypass =                  VMBlockVopBypass,
     .vop_access =                  VMBlockVopAccess,
  #if __FreeBSD_version >= 900013
     .vop_advlockpurge =            vop_stdadvlockpurge,
  #endif
     .vop_bmap =                    VOP_EOPNOTSUPP,
     .vop_getattr =                 VMBlockVopGetAttr,
     .vop_getwritemount =           VMBlockVopGetWriteMount,
     .vop_inactive =                VMBlockVopInactive,
     .vop_ioctl =                   VMBlockVopIoctl,
     .vop_islocked =                VMBlockVopIsLocked,
     .COMPAT_VOP_LOCK_OP_ELEMENT =  VMBlockVopLock,
     .vop_lookup =                  VMBlockVopLookup,
     .vop_open =                    VMBlockVopOpen,
     .vop_print =                   VMBlockVopPrint,
     .vop_reclaim =                 VMBlockVopReclaim,
     .vop_rename =                  VMBlockVopRename,
     .vop_setattr =                 VMBlockVopSetAttr,
     .vop_strategy =                VOP_EOPNOTSUPP,
     .vop_unlock =                  VMBlockVopUnlock,
  };
  
  
  
  /*
   * VMBlockFS file descriptor operations vector --
   *   There are a few special cases where we need to control behavior beyond
   *   the file system layer.  For this we define our own fdesc op vector,
   *   install our own handlers for these special cases, and fall back to the
   *   badfileops vnode ops for everything else.
   *
   *   VMBlock instances are keyed on/indexed by the file descriptor that received
   *   the ioctl request[1].  Since the relationship between file descriptors and
   *   vnodes is N:1, we need to intercept ioctl requests at the file descriptor
   *   level, rather than at the vnode level, in order to have a record of which
   *   descriptor received the request.  Similarly, we need to remove VMBlocks
   *   issued on a file descriptor when said descriptor is closed.
   *
   *   NOTICE --
   *     This applies -only- when a user opens the FS mount point directly.  All
   *     other files'/directories' file descriptor operations vectors are left
   *     untouched.
   *
   * 1.  Keying on thread ID/process ID doesn't work because file descriptors
   *     may be shared between threads/processes.  Clients may find blocks
   *     removed unintentionally when the original issuing thread or process
   *     dies, even though the same descriptor is open.
   */
  
  static struct fileops VMBlockFileOps;
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockSetupFileOps --
   *
   *      Sets up secial file operations vector used for root vnode _only_
   *      (see the comment for VMBlockFileOps above).
   *
   * Results:
   *      None.
   *
   * Side effects:
   *      None.
   *
   *-----------------------------------------------------------------------------
   */
  
  void
  VMBlockSetupFileOps(void)
  {
     VMBlockFileOps = badfileops;
     VMBlockFileOps.fo_stat = vnops.fo_stat;
     VMBlockFileOps.fo_flags = vnops.fo_flags;
     VMBlockFileOps.fo_ioctl = VMBlockFileIoctl;
     VMBlockFileOps.fo_close = VMBlockFileClose;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockFileIoctl --
   *
   *      Wrapper for VMBlockVopIoctl.  This is done to provide VMBlockVopIoctl
   *      with information about the file descriptor which received the user's
   *      ioctl request.
   *
   * Results:
   *      Zero on success, otherwise an appropriate system error.
   *
   * Side effects:
   *      None.
   *
   *-----------------------------------------------------------------------------
   */
  
  int
  VMBlockFileIoctl(struct file *fp,       // IN: user's file descriptor
                   u_long command,        // IN: encoded ioctl command
                   void *data,            // IN: opaque data argument
                   struct ucred *cred,    // IN: caller's credentials
                   struct thread *td)     // IN: caller's thread context
  {
     VMBlockIoctlArgs args;
     args.fileDesc = fp;
     args.data = data;
     return vnops.fo_ioctl(fp, command, &args, cred, td);
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockFileClose --
   *
   *      Called when a file descriptor is closed.  Destroy all blocks opened
   *      on this descriptor, then pass off to vn_closefile to handle any other
   *      cleanup.
   *
   * Results:
   *      Zero on success, an appropriate system error otherwise.
   *      (See vn_closefile for more information.)
   *
   * Side effects:
   *      None.
   *
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockFileClose(struct file *fp,       // IN: user's file descriptor
                   struct thread *td)     // IN: caller's thread context
  {
     struct vnode *vp;
     int removed = 0;
  
     vp = MNTTOVMBLOCKMNT(fp->f_vnode->v_mount)->rootVnode;
     removed = BlockRemoveAllBlocks(fp);
  
     VI_LOCK(vp);
     vp->v_usecount -= removed;
     VI_UNLOCK(vp);
  
     return vnops.fo_close(fp, td);
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopBypass --
   *
   *      Default routine for bypassing the VMBlockFS file system layer.
   *
   * Results:
   *      Zero on success, or an appropriate system error otherwise.
   *
   * Side effects:
   *      Parameters passed in via ap->a_desc may be modified by the lower
   *      layer's routines.
   *
   * Original function comment:
   *	This is the 10-Apr-92 bypass routine.
   *	   This version has been optimized for speed, throwing away some
   *	safety checks.  It should still always work, but it's not as
   *	robust to programmer errors.
   *
   *	In general, we map all vnodes going down and unmap them on the way back.
   *	As an exception to this, vnodes can be marked "unmapped" by setting
   *	the Nth bit in operation's vdesc_flags.
   *
   *	Also, some BSD vnode operations have the side effect of vrele'ing
   *	their arguments.  With stacking, the reference counts are held
   *	by the upper node, not the lower one, so we must handle these
   *	side-effects here.  This is not of concern in Sun-derived systems
   *	since there are no such side-effects.
   *
   *	This makes the following assumptions:
   *	- only one returned vpp
   *	- no INOUT vpp's (Sun's vop_open has one of these)
   *	- the vnode operation vector of the first vnode should be used
   *	  to determine what implementation of the op should be invoked
   *	- all mapped vnodes are of our vnode-type (NEEDSWORK:
   *	  problems on rmdir'ing mount points and renaming?)
   *
   *-----------------------------------------------------------------------------
   */
  
  int
  VMBlockVopBypass(struct vop_generic_args *ap)
  /*
  struct vop_generic_args {
     struct vnodeop_desc *a_desc; // IN/OUT: Vnode operation description; incl.
                                  //         pointers to operand vnode(s), user
                                  //         credentials, calling thread context,
                                  //         etc.
  };
  */
  {
     struct vnode **this_vp_p;
     int error;
     struct vnode *old_vps[VDESC_MAX_VPS];
     struct vnode **vps_p[VDESC_MAX_VPS];
     struct vnode ***vppp;
     struct vnodeop_desc *descp = ap->a_desc;
     int reles, i;
  
  #ifdef DIAGNOSTIC
     /*
      * We require at least one vp.
      */
     if (descp->vdesc_vp_offsets == NULL ||
         descp->vdesc_vp_offsets[0] == VDESC_NO_OFFSET) {
        panic ("VMBlockVopBypass: no vp's in map");
     }
  #endif
  
     /*
      * Map the vnodes going in.  Later, we'll invoke the operation based on
      * the first mapped vnode's operation vector.
      */
     reles = descp->vdesc_flags;
     for (i = 0; i < VDESC_MAX_VPS; reles >>= 1, i++) {
        if (descp->vdesc_vp_offsets[i] == VDESC_NO_OFFSET) {
           break;   /* bail out at end of list */
        }
  
        vps_p[i] = this_vp_p =
           VOPARG_OFFSETTO(struct vnode**,descp->vdesc_vp_offsets[i],ap);
  
        /*
         * We're not guaranteed that any but the first vnode are of our type.
         * Check for and don't map any that aren't.  (We must always map first
         * vp or vclean fails.)
         */
        if (i && (*this_vp_p == NULLVP ||
            (*this_vp_p)->v_op != &VMBlockVnodeOps)) {
           old_vps[i] = NULLVP;
        } else {
           old_vps[i] = *this_vp_p;
           *(vps_p[i]) = VMBVPTOLOWERVP(*this_vp_p);
           /*
            * XXX - Several operations have the side effect of vrele'ing their
            * vp's.  We must account for that.  (This should go away in the
            * future.)
            */
           if (reles & VDESC_VP0_WILLRELE) {
              VREF(*this_vp_p);
           }
        }
     }
  
     /*
      * Call the operation on the lower layer with the modified argument
      * structure.
      */
     if (vps_p[0] && *vps_p[0]) {
        error = VCALL(ap);
     } else {
        printf("VMBlockVopBypass: no map for %s\n", descp->vdesc_name);
        error = EINVAL;
     }
  
     /*
      * Maintain the illusion of call-by-value by restoring vnodes in the
      * argument structure to their original value.
      */
     reles = descp->vdesc_flags;
     for (i = 0; i < VDESC_MAX_VPS; reles >>= 1, i++) {
        if (descp->vdesc_vp_offsets[i] == VDESC_NO_OFFSET) {
           break;   /* bail out at end of list */
        }
        if (old_vps[i]) {
           *(vps_p[i]) = old_vps[i];
           if (reles & VDESC_VP0_WILLRELE) {
              vrele(*(vps_p[i]));
           }
        }
     }
  
     /*
      * Map the possible out-going vpp (Assumes that the lower layer always
      * returns a VREF'ed vpp unless it gets an error.)
      */
     if (descp->vdesc_vpp_offset != VDESC_NO_OFFSET && !error) {
        vppp = VOPARG_OFFSETTO(struct vnode***, descp->vdesc_vpp_offset,ap);
        if (*vppp) {
           /* FIXME: set proper name for the vnode */
           error = VMBlockNodeGet(old_vps[0]->v_mount, **vppp, *vppp, NULL);
        }
     }
  
     return error;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopLookup --
   *
   *      "VOP_LOOKUP(9) -- lookup a component of a pathname"
   *
   * Results:
   *      Zero if the component name is found.  EJUSTRETURN if the namei
   *      operation is CREATE or RENAME, we're looking up the final component
   *      name, and said operation would succeed.  Otherwise returns an
   *      appropriate system error.
   *
   * Side effects:
   *      Requested vnode is locked and returned in *ap->a_vpp.
   *
   * Original function comment:
   *	We have to carry on the locking protocol on the null layer vnodes
   *	as we progress through the tree. We also have to enforce read-only
   *	if this layer is mounted read-only.
   *
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopLookup(struct vop_lookup_args *ap)
  /*
  struct vop_lookup_args {
     struct vnode *dvp;           // IN: pointer to searched directory
     struct vnode **vpp;          // OUT: if found, points to requested
                                  //      vnode; else NULL
     struct componentname *cnp;   // IN: directory search context
  };
  */
  {
     struct componentname *cnp = ap->a_cnp;
     COMPAT_THREAD_VAR(td, cnp->cn_thread);
     struct vnode *dvp = ap->a_dvp;
     struct vnode *vp, *ldvp, *lvp;
     BlockHandle blockCookie;
     int flags = cnp->cn_flags;
     int error = 0;
     char *pathname;
     size_t pathname_len;
  
     /*
      * Fail attempts to modify a read-only filesystem w/o bothering with a
      * lower-layer lookup.
      */
     if ((flags & ISLASTCN) && (dvp->v_mount->mnt_flag & MNT_RDONLY) &&
         (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) {
        return EROFS;
     }
  
     /*
      * Before looking in the lower layer, determine whether the search path
      * should be blocked.  If so, do the following:
      *   1.  Make a copy of the block pathname.  (BlockWaitOnFile may make
      *       use of this, and our VMBlockNode may be destroyed while asleep
      *       if user forcibly unmounts file system.)
      *   2.  Bump up hold counts of current VMBlock directory vnode and its
      *       lower layer counterpart.  This makes sure that at least they
      *       aren't purged from memory while we sleep.
      *   3.  Unlock & relock directory vnodes around sleeping.  This prevents
      *       a cascading file system lookup deadlock.  (E.g., we have dvp locked,
      *       but another thread trying to look up dvp will block, holding /its/
      *       dvp's (dvp2) lock, and yet another thread would block looking up
      *       dvp2 while holding its dvp (dvp3), etc.
      *
      * If we find we were forcibly unmounted, fail with EIO.
      */
  
     pathname = uma_zalloc(VMBlockPathnameZone, M_WAITOK);
     if (pathname == NULL) {
        return ENOMEM;
     }
  
     /*
      * FIXME: we need to ensure that vnode always has name set up.
      * Currently VMBlockVopBypass() may produce vnodes without a name.
      */
     pathname_len = strlcpy(pathname,
                            VPTOVMB(dvp)->name ? VPTOVMB(dvp)->name : ".",
                            MAXPATHLEN);
     /*
      * Make sure we have room in the buffer to add our component.
      * + 1 is for separator (slash).
      */
     if (pathname_len + 1 + cnp->cn_namelen >= MAXPATHLEN) {
        error = ENAMETOOLONG;
        goto out;
     }
  
     if ((blockCookie = BlockLookup(pathname, OS_UNKNOWN_BLOCKER)) != NULL) {
        int lkflags = compat_lockstatus(dvp->v_vnlock, td) & LK_TYPE_MASK;
        lvp = VPTOVMB(dvp)->lowerVnode;
        vhold(dvp);
        vhold(lvp);
        COMPAT_VOP_UNLOCK(dvp, 0, td);
  
        error = BlockWaitOnFile(pathname, blockCookie);
  
        COMPAT_VOP_LOCK(dvp, lkflags, td);
        vdrop(lvp);
        vdrop(dvp);
        if (dvp->v_op != &VMBlockVnodeOps) {
           Debug("%s: vmblockfs forcibly unmounted?\n", __func__);
           error = EIO;
        }
  
        if (error) {
           goto out;
        }
     }
  
     /* We already verified that buffer is big enough. */
     pathname[pathname_len] = '/';
     bcopy(cnp->cn_nameptr, &pathname[pathname_len + 1], cnp->cn_namelen);
     pathname[pathname_len + 1 + cnp->cn_namelen] = 0;
  
     /*
      * Although it is possible to call VMBlockVopBypass(), we'll do a direct
      * call to reduce overhead
      */
     ldvp = VMBVPTOLOWERVP(dvp);
     vp = lvp = NULL;
  
     error = VOP_LOOKUP(ldvp, &lvp, cnp);
     if (error == EJUSTRETURN && (flags & ISLASTCN) &&
         (dvp->v_mount->mnt_flag & MNT_RDONLY) &&
         (cnp->cn_nameiop == CREATE || cnp->cn_nameiop == RENAME)) {
        error = EROFS;
     }
  
     if ((error == 0 || error == EJUSTRETURN) && lvp != NULL) {
        /*
         * Per VOP_LOOKUP(9), if looking up the current directory ("."), we bump
         * our vnode's refcount.
         */
        if (ldvp == lvp) {
           *ap->a_vpp = dvp;
           VREF(dvp);
           vrele(lvp);
        } else {
           error = VMBlockNodeGet(dvp->v_mount, lvp, &vp, pathname);
           if (error) {
              /* XXX Cleanup needed... */
              panic("VMBlockNodeGet failed");
           }
           *ap->a_vpp = vp;
           /* The vnode now owns pathname so don't try to free it below. */
           pathname = NULL;
        }
     }
  
  out:
     if (pathname) {
        uma_zfree(VMBlockPathnameZone, pathname);
     }
     return error;
  }
  
  
  /*
   *----------------------------------------------------------------------------
   *
   * VMBlockVopOpen --
   *
   *      "The VOP_OPEN() entry point is called before a file is accessed by a
   *      process..." - VOP_OPEN(9).  If the vnode in question is the file
   *      system's root vnode, allow access only to the superuser.
   *
   * Results:
   *      Zero on success, an appropriate system error otherwise.
   *
   * Side effects:     
   *      None.
   *
   *----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopOpen(struct vop_open_args *ap)
  /*
  struct vop_open_args {
     struct vnode *vp;    // IN: vnode which ioctl issued upon
     int fflag;           // IN: fdesc flags (see fcntl(2))
     struct ucred *cred;  // IN: caller's credentials (usually real uid vs euid)
     struct thread *td;   // IN: calling thread's context      
     FreeBSD <= 6 --
        int fdidx;        // IN: file descriptor number alloc'd to this open()
     FreeBSD >= 7 --
        struct file *fp   // IN: struct associated with this particular open()
  };
  */
  {
     VMBlockMount *mp;
     struct vnode *vp, *ldvp;
     struct file *fp;
     int retval;
  
     vp = ap->a_vp;
     ldvp = VMBVPTOLOWERVP(vp);
  
     mp = MNTTOVMBLOCKMNT(ap->a_vp->v_mount);
     if (ap->a_vp == mp->rootVnode) {
        /*
         * Opening the mount point is a special case.  First, only allow this
         * access to the superuser.  Next, we install a custom fileops vector in
         * order to trap the ioctl() and close() operations.  (See the *FileOps'
         * descriptions for more details.)
         *
         * NB:  Allowing only the superuser to open this directory breaks
         *      readdir() of the filesystem root for non-privileged users.
         *
         * Also, on FreeBSD 8.0 and newer we check for a specific module priv
         * because none of the existing privs seemed to match very well.
         */
        if ((retval = compat_priv_check(ap->a_td, PRIV_DRIVER)) == 0) {
  #if __FreeBSD_version >= 700055
           fp = ap->a_fp;
  #else
           fp = ap->a_td->td_proc->p_fd->fd_ofiles[ap->a_fdidx];
  #endif
           fp->f_ops = &VMBlockFileOps;
        }
     } else {
        /*
         * Pass off to the lower layer.  If lower layer mapped a VM object, copy
         * its reference.
         */
        retval = VMBlockVopBypass(&ap->a_gen);
        if (retval == 0) {
           vp->v_object = ldvp->v_object;
        }
     }
  
     return retval;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopSetAttr --
   *
   *      "VOP_SETATTR(9) -- set attributes on a file or directory."
   *
   *      This version is simpler than the original null_setattr as it only
   *      tests whether the user is attempting an operation in a read-only
   *      file system.  Beyond that, it defers judgment about the validity of
   *      the request to the lower layer via vop_bypass.
   *
   * Results:
   *      Zero on success, else an appropriate system error.
   *
   * Side effects:
   *      None.
   *
   * Original function comment:
   *      Setattr call. Disallow write attempts if the layer is mounted read-
   *      only.
   *
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopSetAttr(struct vop_setattr_args *ap)
  /*
  struct vop_setattr_args {
     struct vnode *vp;    // IN: vnode operand
     struct vattr *vap;   // IN: attributes
     struct ucred *cred;  // IN: caller's credentials
     struct thread *td;   // IN: caller's thread context
  };
  */
  {
     struct vnode *vp = ap->a_vp;
  
     if (vp->v_mount->mnt_flag & MNT_RDONLY) {
        return EROFS;
     }
  
     return VMBlockVopBypass((struct vop_generic_args *)ap);
  }
  
  
  /*
   *----------------------------------------------------------------------------
   *
   * VMBlockVopIoctl --
   *
   *      Handle ioctl(2) requests to add and remove file blocks.  Ioctl
   *      commands are defined in public/vmblock.h.  
   *
   * Results:
   *      Zero on success, otherwise an appropriate error is returned.
   *
   * Side effects
   *      A block may be placed on or removed from a file.  The root vnode's
   *      reference count will be incremented when a block is successfully added,
   *      and it will be decremented when a block is removed.
   *
   *----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopIoctl(struct vop_ioctl_args *ap)      // IN/OUT: ioctl parameters
  /*
  struct vop_ioctl_args {
     struct vnode *vp;    // IN: vnode which ioctl issued upon
     u_long command;      // IN: ioctl command
     caddr_t data;        // IN: ioctl parameter (e.g., pathname)
     int fflag;           // IN: fcntl style flags (no-op?)
     struct ucred *cred;  // IN: caller's credentials (usually real uid vs euid)
     struct thread *td;   // IN: calling thread's context      
  };
  */
  {
     VMBlockIoctlArgs *ioctlArgs = (VMBlockIoctlArgs *)ap->a_data;
     VMBlockMount *mp;
     COMPAT_THREAD_VAR(td, ap->a_td);
     struct vnode *vp = ap->a_vp;
     char *pathbuf = NULL;
     int ret = 0, pathlen;
  
     Debug(VMBLOCK_ENTRY_LOGLEVEL, "VMBlockVopIoctl: entry\n");
  
     /*
      * The operand vnode is passed in unlocked, so test a few things before
      * proceeding.
      *   1.  Make sure we're still dealing with a VMBlock vnode.  Note
      *       that this test -must- come before the next one.  Otherwise v_mount
      *       may be invalid.
      *   2.  Make sure the filesystem isn't being unmounted.
      */
     COMPAT_VOP_LOCK(vp, LK_EXCLUSIVE|LK_RETRY, td);
     if (vp->v_op != &VMBlockVnodeOps ||
         vp->v_mount->mnt_kern_flag & MNTK_UNMOUNT) {
        COMPAT_VOP_UNLOCK(vp, 0, td);
        return EBADF;
     }
  
     /*
      * At this layer/in this file system, only the root vnode handles ioctls,
      * and only the superuser may open the root vnode.  If we're not given
      * the root vnode, simply bypass to the next lower layer.
      */
     mp = MNTTOVMBLOCKMNT(vp->v_mount);
     if (vp != mp->rootVnode) {
        /*
         * VMBlockFileIoctl wraps the user's data in a special structure which
         * includes the user's file descriptor, so we must unwrap the data
         * argument before passing to the lower layer.
         */
        ap->a_data = ioctlArgs->data;
        COMPAT_VOP_UNLOCK(vp, 0, td);
        return VMBlockVopBypass((struct vop_generic_args *)ap);
     }
  
     pathbuf = uma_zalloc(VMBlockPathnameZone, M_WAITOK);
  
     switch (ap->a_command) {
     case VMBLOCK_ADD_FILEBLOCK:
     case VMBLOCK_DEL_FILEBLOCK:
     {
        /*
         * Trim trailing slashes
         */
        pathlen = strlcpy(pathbuf, ioctlArgs->data, MAXPATHLEN);
        pathlen = MIN(pathlen, MAXPATHLEN);
        while (pathlen > 0 && pathbuf[pathlen - 1] == '/') {
           pathbuf[pathlen - 1] = '\0';
           pathlen--;
        }
  
        VMBLOCKDEBUG("%s: %s on %s\n", __func__,
                     (ap->a_command == VMBLOCK_ADD_FILEBLOCK) ? "add" : "del",
                     pathbuf);
  
        /*
         * Don't block the mount point!
         */
        if (!strcmp(VPTOVMB(vp)->name, pathbuf)) {
           ret = EINVAL;
        } else {
           ret = (ap->a_command == VMBLOCK_ADD_FILEBLOCK) ?
                    BlockAddFileBlock(pathbuf, ioctlArgs->fileDesc) :
                    BlockRemoveFileBlock(pathbuf, ioctlArgs->fileDesc);
  
           /*
            * When adding a block, bump the reference count on the root vnode.  If
            * removing a block, decrement the reference count.  Of course, only do
            * so if the action succeeds!
            */
           if (ret == 0) {
              VI_LOCK(vp);
              vp->v_usecount += (ap->a_command == VMBLOCK_ADD_FILEBLOCK) ? 1 : -1;
              VI_UNLOCK(vp);
           }
        }
        break;
     }
  #ifdef VMX86_DEVEL
     case VMBLOCK_LIST_FILEBLOCKS:
        BlockListFileBlocks();
        ret = 0;
        break;
     case VMBLOCK_PURGE_FILEBLOCKS:
        {
           int removed = 0;
           removed = BlockRemoveAllBlocks(OS_UNKNOWN_BLOCKER);
           VI_LOCK(vp);
           vp->v_usecount -= removed;
           VI_UNLOCK(vp);
        }
        ret = 0;
        break;
  #endif
     default:
        Warning("VMBlockVopIoctl: unknown command (%lu) received.\n", ap->a_command);
        ret = EOPNOTSUPP;
     }
  
     COMPAT_VOP_UNLOCK(vp, 0, td);
     if (pathbuf) {
        uma_zfree(VMBlockPathnameZone, pathbuf);
     }
     return ret;
  }
  
  
  /*
   *----------------------------------------------------------------------------
   *
   * VMBlockVopGetAttr --
   *
   *      Query the underlying filesystem for file/directory information.
   *      Also fixup fsid to be ours rather than that of the underlying fs.
   *
   * Results:
   *      Zero on success, an appropriate system error otherwise.
   *
   * Side effects:
   *      None.
   *
   *----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopGetAttr(struct vop_getattr_args *ap)
  /*
  struct vop_getattr_args {
     struct vnode *vp;    // IN: operand vnode
     struct vattr *vap;   // OUT: vnode's parameters
     struct ucred *ucred; // IN: caller's credentials
     struct thread *td;   // IN: caller's thread context
  };
  */
  {
     int error;
  
     if ((error = VMBlockVopBypass((struct vop_generic_args *)ap)) != 0) {
        return error;
     }
  
     ap->a_vap->va_fsid = ap->a_vp->v_mount->mnt_stat.f_fsid.val[0];
     return 0;
  }
  
  
  /*
   *----------------------------------------------------------------------------
   *
   * VMBlockVopAccess --
   *
   *      "VOP_ACCESS(9) -- check access permissions of a file or Unix domain
   *      socket."  We handle this to disallow write access if our layer is,
   *      for whatever reason, mounted read-only.
   *
   * Results:
   *      Zero on success, an appropriate system error otherwise.
   *
   * Side effects:
   *      None.
   *
   *----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopAccess(struct vop_access_args *ap)
  /*
  struct vop_access_args {
     struct vnode *vp;    // IN: operand vnode
     int mode;            // IN: access(2) flags
     struct vattr *vap;   // OUT: vnode's parameters
     struct ucred *ucred; // IN: caller's credentials
     struct thread *td;   // IN: caller's thread context
  };
  */
  {
     struct vnode *vp = ap->a_vp;
     compat_accmode_t mode = ap->compat_a_accmode;
  
     /*
      * Disallow write attempts on read-only layers; unless the file is a
      * socket, fifo, or a block or character device resident on the filesystem.
      */
     if (mode & VWRITE) {
        switch (vp->v_type) {
        case VDIR:
        case VLNK:
        case VREG:
           if (vp->v_mount->mnt_flag & MNT_RDONLY) {
              return EROFS;
           }
           break;
        default:
           break;
        }
     }
     return VMBlockVopBypass((struct vop_generic_args *)ap);
  }
  
  
  /*
   *----------------------------------------------------------------------------
   *
   * VMBlockRename --
   *
   *      "VOP_RENAME(9) -- rename a file."
   *
   * Results:
   *      Zero on success, an appropriate system error otherwise.
   *
   * Side effects:
   *      None.
   *
   * Original function comment:
   *      We handle this to eliminate null FS to lower FS file moving. Don't
   *      know why we don't allow this, possibly we should.
   *----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopRename(struct vop_rename_args *ap)
  /*
  struct vop_rename_args {
     struct vnode *fdvp;          // IN: source directory
     struct vnode *fvp;           // IN: source file
     struct componentname *fcnp;  // IN: source's path lookup state
     struct vnode *tdvp;          // IN: destination directory
     struct vnode *tvp;           // IN: destination file
     struct componentname *tcnp;  // IN: destination's path lookup state
  };
  */
  {
     struct vnode *tdvp = ap->a_tdvp;
     struct vnode *fvp = ap->a_fvp;
     struct vnode *fdvp = ap->a_fdvp;
     struct vnode *tvp = ap->a_tvp;
  
     /* Check for cross-device rename. */
     if ((fvp->v_mount != tdvp->v_mount) ||
         (tvp && (fvp->v_mount != tvp->v_mount))) {
        if (tdvp == tvp) {
           vrele(tdvp);
        } else {
           vput(tdvp);
        }
        if (tvp) {
           vput(tvp);
        }
        vrele(fdvp);
        vrele(fvp);
        return EXDEV;
     }
  
     return VMBlockVopBypass((struct vop_generic_args *)ap);
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopLock --
   *
   *      Acquire a vnode lock.
   *
   * Results:
   *      Zero on success, otherwise an error is returned.
   *
   * Side effects:
   *      Upper & lower layers share a lock, so both vnodes will be considered
   *      locked.
   *
   * Original function comment:
   *      We need to process our own vnode lock and then clear the interlock flag
   *      as it applies only to our vnode, not the vnodes below us on the stack.
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopLock(compat_vop_lock_args *ap)
  /*
  struct vop_lock_args {
     struct vnode *vp;    // IN: vnode operand
     int flags;           // IN: lockmgr(9) flags
     struct thread *td;   // IN: calling thread's context
  };
  */
  {
     struct vnode *vp = ap->a_vp;
     int flags = ap->a_flags;
     COMPAT_THREAD_VAR(td, ap->a_td);
     struct VMBlockNode *nn;
     struct vnode *lvp;
     int error;
  
  
     if ((flags & LK_INTERLOCK) == 0) {
        VI_LOCK(vp);
        ap->a_flags = flags |= LK_INTERLOCK;
     }
     nn = VPTOVMB(vp);
     /*
      * If we're still active we must ask the lower layer to lock as ffs
      * has special lock considerations in it's vop lock. -- FreeBSD
      */
     if (nn != NULL && (lvp = VMBVPTOLOWERVP(vp)) != NULL) {
        VI_LOCK_FLAGS(lvp, MTX_DUPOK);
        VI_UNLOCK(vp);
        /*
         * We have to hold the vnode here to solve a potential reclaim race.
         * If we're forcibly vgone'd while we still have refs, a thread
         * could be sleeping inside the lowervp's vop_lock routine.  When we
         * vgone we will drop our last ref to the lowervp, which would
         * allow it to be reclaimed.  The lowervp could then be recycled,
         * in which case it is not legal to be sleeping in it's VOP.
         * We prevent it from being recycled by holding the vnode here.
         */
        vholdl(lvp);
        error = COMPAT_VOP_LOCK(lvp, flags, td);
  
        /*
         * We might have slept to get the lock and someone might have clean
         * our vnode already, switching vnode lock from one in lowervp
         * to v_lock in our own vnode structure.  Handle this case by
         * reacquiring correct lock in requested mode.
         */
        if (VPTOVMB(vp) == NULL && error == 0) {
           ap->a_flags &= ~(LK_TYPE_MASK | LK_INTERLOCK);
           switch (flags & LK_TYPE_MASK) {
           case LK_SHARED:
              ap->a_flags |= LK_SHARED;
              break;
           case LK_UPGRADE:
           case LK_EXCLUSIVE:
              ap->a_flags |= LK_EXCLUSIVE;
              break;
           default:
              panic("Unsupported lock request %d\n",
                  ap->a_flags);
           }
           COMPAT_VOP_UNLOCK(lvp, 0, td);
           error = vop_stdlock(ap);
        }
        vdrop(lvp);
     } else {
        error = vop_stdlock(ap);
     }
  
     return error;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopUnlock --
   *
   *      Release a vnode lock.
   *
   * Results:
   *      Zero on success, an appropriate system error otherwise.
   *
   * Side effects:
   *      None.
   *
   * Original function comment:
   *      We need to process our own vnode unlock and then clear the interlock
   *      flag as it applies only to our vnode, not the vnodes below us on
   *      the stack.
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopUnlock(struct vop_unlock_args *ap)
  /*
  struct vop_unlock_args {
     struct vnode *vp;    // IN: vnode operand
     int flags;           // IN: lock request flags (see lockmgr(9))
     struct thread *td;   // IN: calling thread's context
  };
  */
  {
     struct vnode *vp = ap->a_vp;
  #if __FreeBSD_version < 1300074
     int flags = ap->a_flags;
  #endif
     COMPAT_THREAD_VAR(td, ap->a_td);
     struct VMBlockNode *nn;
     struct vnode *lvp;
     int error;
  
  #if __FreeBSD_version < 1300074
     /*
      * If caller already holds interlock, drop it.  (Per VOP_UNLOCK() API.)
      * Also strip LK_INTERLOCK from flags passed to lower layer.
      */
     if ((flags & LK_INTERLOCK) != 0) {
        VI_UNLOCK(vp);
        ap->a_flags = flags &= ~LK_INTERLOCK;
     }
  #endif
     nn = VPTOVMB(vp);
     if (nn != NULL && (lvp = VMBVPTOLOWERVP(vp)) != NULL) {
  #if __FreeBSD_version < 1300074
        error = COMPAT_VOP_UNLOCK(lvp, flags, td);
  #else
        error = COMPAT_VOP_UNLOCK(lvp, 0, td);
  #endif
     } else {
        error = vop_stdunlock(ap);
     }
  
     return error;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopIsLocked --
   *
   *      Test whether a vnode is locked.
   *
   * Results:
   *      Zero if locked, non-zero otherwise.
   *
   * Side effects:
   *      None.
   *
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopIsLocked(struct vop_islocked_args *ap)
  /*
  struct vop_islocked_args {
     struct vnode *vp;    // IN: vnode operand 
     struct thread *td;   // IN: calling thread's context
  };
  */
  {
     struct vnode *vp = ap->a_vp;
     COMPAT_THREAD_VAR(td, ap->a_td);
  
     return compat_lockstatus(vp->v_vnlock, td);
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopInactive --
   *
   *      "VOP_INACTIVE() is called when the kernel is no longer using the vnode.
   *      This may be because the reference count reaches zero or it may be that
   *      the file system is being forcibly unmounted while there are open files.
   *      It can be used to reclaim space for `open but deleted' files."
   *
   * Results:
   *      Zero.
   *
   * Side effects:
   *      If this vnode's reference is zero, vrecycle() will handle induce
   *      cleanup.
   *
   * Original function comment:
   *	There is no way to tell that someone issued remove/rmdir operation
   *	on the underlying filesystem. For now we just have to release lowevrp
   *	as soon as possible.
   *
   *	Note, we can't release any resources nor remove vnode from hash before
   *	appropriate VXLOCK stuff is is done because other process can find
   *	this vnode in hash during inactivation and may be sitting in vget()
   *	and waiting for VMBlockVopInactive to unlock vnode. Thus we will do
   *	all those in VOP_RECLAIM.
   *
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopInactive(struct vop_inactive_args *ap)
  /*
  struct vop_inactive_args {
     struct vnode *vp;    // IN: vnode operand 
     struct thread *td;   // IN: calling thread's context
  };
  */
  {
     struct vnode *vp = ap->a_vp;
  #if __FreeBSD_version < 1000000
     struct thread *td = ap->a_td;
  #endif
  
     vp->v_object = NULL;
  
     /*
      * If this is the last reference, then free up the vnode so as not to
      * tie up the lower vnode.
      */
  #if __FreeBSD_version < 1000000
      vrecycle(vp, td);
  #else
     vrecycle(vp);
  #endif
     return 0;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopReclaim --
   *
   *      "VOP_RECLAIM() is called when a vnode is being reused for a
   *      different file system.  Any file system specific resources
   *      associated with the vnode should be freed."
   *
   * Results:
   *      Returns zero.
   *
   * Side effects:
   *      If node is an associate VMBlockNode, it's removed from
   *      the VMBlockNode hash and freed.  Reference to the lower vnode, if
   *      it exists, is also dropped.      
   *
   * Original function comment:
   *      Now, the VXLOCK is in force and we're free to destroy the null vnode.
   *
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopReclaim(struct vop_reclaim_args *ap)
  /*
  struct vop_reclaim_args {
     struct vnode *vp;    // IN: vnode operand 
     struct thread *td;   // IN: calling thread's context
  };
  */
  {
     struct vnode *vp = ap->a_vp;
     struct VMBlockNode *xp = VPTOVMB(vp);
     struct vnode *lowervp = xp->lowerVnode;
  
     KASSERT(lowervp != NULL, ("reclaiming node with no lower vnode"));
  
     VMBlockHashRem(xp);
  
     /*
      * Use the interlock to protect the clearing of v_data to
      * prevent faults in VMBlockVopLock().
      */
     VI_LOCK(vp);
     vp->v_data = NULL;
     vp->v_object = NULL;
  
     /*
      * Reassign lock pointer to this vnode's lock.  (Originally assigned
      * to the lower layer's lock.)
      */
     vp->v_vnlock = &vp->v_lock;
     compat_lockmgr(vp->v_vnlock, LK_EXCLUSIVE|LK_INTERLOCK, VI_MTX(vp), curthread);
     vput(lowervp);
  
     /*
      * Clean up VMBlockNode attachment.
      */
     if (xp->name) {
        uma_zfree(VMBlockPathnameZone, xp->name);
     }
     free(xp, M_VMBLOCKFSNODE);
  
     return 0;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopPrint --
   *
   *      "VOP_PRINT -- print debugging information"
   *
   * Results:
   *      Zero.  Always.
   *
   * Side effects:
   *      None.
   *
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopPrint(struct vop_print_args *ap)
  /*
  struct vop_print_args {
     struct vnode *vp;    // IN: vnode operand
  };
  */
  {
     struct vnode *vp = ap->a_vp;
     printf("\tvp=%p, lowervp=%p\n", vp, VMBVPTOLOWERVP(vp));
     return 0;
  }
  
  
  /*
   *-----------------------------------------------------------------------------
   *
   * VMBlockVopGetWriteMount --
   *
   *      When the caller wishes to begin a write operation, we need to bump
   *      the count of write operations on the destination file system.  This
   *      routine passes the request down.  "Real" file systems will usually
   *      call vop_stdgetwritemount().
   *
   * Results:
   *      Zero.
   *
   * Side effects:
   *      None.
   *
   *-----------------------------------------------------------------------------
   */
  
  static int
  VMBlockVopGetWriteMount(struct vop_getwritemount_args *ap)
  /*
  struct vop_getwritemount_args {
     struct vnode *vp;    // IN: vnode operand
     struct mount **mpp;  // OUT: pointer to filesystem where write operation
                          //      will actually occur
  };
  */
  {
     struct VMBlockNode *xp;
     struct vnode *lowervp;
     struct vnode *vp;
  
     vp = ap->a_vp;
     VI_LOCK(vp);
     xp = VPTOVMB(vp);
     if (xp && (lowervp = xp->lowerVnode)) {
        VI_LOCK_FLAGS(lowervp, MTX_DUPOK);
        VI_UNLOCK(vp);
        vholdl(lowervp);
        VI_UNLOCK(lowervp);
        VOP_GETWRITEMOUNT(lowervp, ap->a_mpp);
        vdrop(lowervp);
     } else {
        VI_UNLOCK(vp);
        *(ap->a_mpp) = NULL;
     }
     return 0;
  }
  
  