int
nfs_nget(struct mount *mp, nfsfh_t *fhp, int fhsize, struct nfsnode *npp,
    int flags)
{
	struct fh_match fhm = { .fhm_fhp = fhp, .fhm_fhsize = fhsize };
	struct vnode *vp = NULL, *vp0 = NULL;
	static const struct nfsnode zero_nfsnode;
	struct nfsnode *np;
	int error;

	/*
	 * Try to get an existing vnode for this file handle.
	 */
	error = vinotree_get(nmp->nm_inotree, &fhm, VWAIT_INTR, &vp);
	KASSERT((vp == NULL) == (error == 0));
	if (error != ENOENT)
		goto out;

	/*
	 * Create a new vnode for it instead.
	 */
	error = getnewvnode(VT_NFS, mp, nfsv2_vnodeop_p, NULL, &vp);
	if (error)
		goto out;

	/*
	 * Create an nfsnode structure for it and fill it in just
	 * enough for a lookup.
	 */
	np = pool_get(&nfs_node_pool, PR_WAITOK);
	*np = zero_nfsnode;

	np->n_vnode = vp;
	if (fhsize > NFS_SMALLFH)
		np->n_fhp = kmem_alloc(fhsize, KM_SLEEP);
	else
		np->n_fhp = &np->n_fh;
	(void)memcpy(np->n_fhp, fhp, fhsize);
	np->n_fhsize = fhsize;

	/*
	 * Try to put this vnode in the table for the file handle.  If
	 * someone else beat us to it, release ours and use theirs
	 * instead.
	 */
	error = vinotree_intern(nmp->nm_inotree, &np->n_inoent, VWAIT_INTR,
	    &vp0);
	KASSERT((vp0 == NULL) == (error == 0));
	if (vp0 != vp) {
		if (fhsize > NFS_SMALLFH)
			kmem_free(np->n_fhp, fhsize);
		pool_put(&nfs_node_pool, np);
		ungetnewvnode(vp);
		vp = vp0;
		goto out;
	}

	/*
	 * Initialize the new vnode and nfsnode.
	 */
	vp->v_data = np;
	genfs_node_init(vp, &nfs_genfsops);

	np->n_accstamp = -1;
	np->n_vattr = pool_get(&nfs_vattr_pool, PR_WAITOK);
	np->n_rcred = curlwp->l_cred;
	kauth_cred_hold(np->n_rcred);
	np->n_wcred = curlwp->l_cred;
	kauth_cred_hold(np->n_wcred);

	/*
	 * Invalidate the attribute cache so that anyone trying to get
	 * the size will caues it to be filled in.  Pretend the size is
	 * zero for now (XXX why?).
	 */
	NFS_INVALIDATE_ATTRCACHE(np);
	uvm_vnp_setsize(vp, 0);

	/*
	 * All set.  Mark it ready to go and wake anyone waiting.
	 */
	vready(vp);
	error = 0;

out:	*npp = (vp == NULL? NULL : vp->v_data);
	return error;
}

int
nfs_clean(void *v)
{
	struct vop_clean_args /* {
		struct vnode *a_vp;
	} */ *ap = v;
	struct vnode *vp = ap->a_vp;

	return 0;
}

int
nfs_destroy(void *v)
{
	struct vop_destroy_args /* {
		struct vnode *a_vp;
	} */ *ap = v;
	struct vnode *vp = ap->a_vp;
	struct nfsnode *np = VTONFS(vp);
	struct nfsmount *nmp = VFSTONFS(vp->v_mount);

	vinotree_remove(nmp->nm_vinotree, &np->n_vinoent);

	switch (vp->v_type) {
	case VREG:
		mutex_destroy(&np->n_commitlock);
		break;
	case VDIR:
		if (np->n_dircache == NULL)
			break;
		nfs_invaldircache(vp, NFS_INVALDIRCACHE_FORCE);
		hashdone(np->n_dircache, HASH_LIST, nfsdirhashmask);
		break;
	}
	KASSERT(np->n_dirgens == NULL);

	if (np->n_wcred)
		kauth_cred_free(np->n_wcred);
	if (np->n_rcred)
		kauth_cred_free(np->n_rcred);

	pool_put(&nfs_vattr_pool, np->n_vattr);

	genfs_node_destroy(vp);
	vp->v_data = NULL;

	if (np->n_fhsize > NFS_SMALLFH)
		kmem_free(np->n_fhp, np->n_fhsize);
	pool_put(&nfs_node_pool, np);

	return 0;
}