Subversion Repositories planix.SVN

Rev

Blame | Last modification | View Log | RSS feed

/*
 * Copyright (c) 1989, 1993
 *      The Regents of the University of California.  All rights reserved.
 *
 * This code is derived from software contributed to Berkeley by
 * Rick Macklem at The University of Guelph.
 *
 * 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.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by the University of
 *      California, Berkeley and its contributors.
 * 4. Neither the name of the University nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * 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.
 *
 *      @(#)u9fs_vnops.c        8.16 (Berkeley) 5/27/95
 * $Id: u9fs_vnops.c,v 1.116.2.3 1999/02/13 08:03:47 dillon Exp $
 */


/*
 * vnode op calls for 9FS
 */

#include "opt_inet.h"

#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/resourcevar.h>
#include <sys/proc.h>
#include <sys/mount.h>
#include <sys/buf.h>
#include <sys/malloc.h>
#include <sys/mbuf.h>
#include <sys/namei.h>
#include <sys/socket.h>
#include <sys/vnode.h>
#include <sys/dirent.h>
#include <sys/fcntl.h>
#include <sys/lockf.h>
#include <sys/stat.h>
#include <sys/sysctl.h>

#include <vm/vm.h>
#include <vm/vm_extern.h>
#include <vm/vm_zone.h>
#include <vm/vm_prot.h>
#include <vm/vm_page.h>
#include <vm/vm_object.h>
#include <vm/vm_pager.h>
#include <vm/vnode_pager.h>

#include <net/if.h>
#include <netinet/in.h>
#include <netinet/in_var.h>

#include <9fs/bitstring.h>
#include <9fs/9p.h>
#include <9fs/9auth.h>
#include <9fs/9fs.h>

#define u9fs_poll vop_nopoll
static  int     u9fs_lookup __P((struct vop_lookup_args *));
static  int     u9fs_create __P((struct vop_create_args *));
static  int     u9fs_mknod __P((struct vop_mknod_args *));
static  int     u9fs_open __P((struct vop_open_args *));
static  int     u9fs_close __P((struct vop_close_args *));
static  int     u9fs_access __P((struct vop_access_args *));
static  int     u9fs_getattr __P((struct vop_getattr_args *));
static  int     u9fs_setattr __P((struct vop_setattr_args *));
static  int     u9fs_read __P((struct vop_read_args *));
static  int     u9fs_mmap __P((struct vop_mmap_args *));
static  int     u9fs_fsync __P((struct vop_fsync_args *));
static  int     u9fs_remove __P((struct vop_remove_args *));
static  int     u9fs_link __P((struct vop_link_args *));
static  int     u9fs_rename __P((struct vop_rename_args *));
static  int     u9fs_mkdir __P((struct vop_mkdir_args *));
static  int     u9fs_rmdir __P((struct vop_rmdir_args *));
static  int     u9fs_symlink __P((struct vop_symlink_args *));
static  int     u9fs_readdir __P((struct vop_readdir_args *));
static  int     u9fs_bmap __P((struct vop_bmap_args *));
static  int     u9fs_strategy __P((struct vop_strategy_args *));
static int      u9fs_readlink __P((struct vop_readlink_args *));
static int      u9fs_print __P((struct vop_print_args *));
static int      u9fs_advlock __P((struct vop_advlock_args *));
static int      u9fs_bwrite __P((struct vop_bwrite_args *));
static int   u9fs_abortop __P((struct vop_abortop_args *));
static int   u9fs_getpages __P((struct vop_getpages_args *));
static int   u9fs_putpages __P((struct vop_putpages_args *));
static int   u9fs_inactive __P((struct vop_inactive_args *));
static int   u9fs_reclaim __P((struct vop_reclaim_args *));
static int   u9fs_write __P((struct vop_write_args *));

/*
 * Global vfs data structures for u9fs
 */
vop_t **u9fs_vnodeop_p;
static struct vnodeopv_entry_desc u9fs_vnodeop_entries[] = {
        { &vop_default_desc,            (vop_t *) vop_defaultop },
        { &vop_abortop_desc,            (vop_t *) u9fs_abortop },
        { &vop_access_desc,             (vop_t *) u9fs_access },
        { &vop_advlock_desc,            (vop_t *) u9fs_advlock },
        { &vop_bmap_desc,               (vop_t *) u9fs_bmap },
        { &vop_bwrite_desc,             (vop_t *) u9fs_bwrite },
        { &vop_close_desc,              (vop_t *) u9fs_close },
        { &vop_create_desc,             (vop_t *) u9fs_create },
        { &vop_fsync_desc,              (vop_t *) u9fs_fsync },
        { &vop_getattr_desc,            (vop_t *) u9fs_getattr },
        { &vop_getpages_desc,           (vop_t *) u9fs_getpages },
        { &vop_putpages_desc,           (vop_t *) u9fs_putpages },
        { &vop_inactive_desc,           (vop_t *) u9fs_inactive },
        { &vop_lease_desc,              (vop_t *) vop_null },
        { &vop_link_desc,               (vop_t *) u9fs_link },
        { &vop_lock_desc,               (vop_t *) vop_sharedlock },
        { &vop_lookup_desc,             (vop_t *) u9fs_lookup },
        { &vop_mkdir_desc,              (vop_t *) u9fs_mkdir },
        { &vop_mknod_desc,              (vop_t *) u9fs_mknod },
        { &vop_mmap_desc,               (vop_t *) u9fs_mmap },
        { &vop_open_desc,               (vop_t *) u9fs_open },
        { &vop_poll_desc,               (vop_t *) vop_nopoll },
        { &vop_print_desc,              (vop_t *) u9fs_print },
        { &vop_read_desc,               (vop_t *) u9fs_read },
        { &vop_readdir_desc,            (vop_t *) u9fs_readdir },
        { &vop_readlink_desc,           (vop_t *) u9fs_readlink },
        { &vop_reclaim_desc,            (vop_t *) u9fs_reclaim },
        { &vop_remove_desc,             (vop_t *) u9fs_remove },
        { &vop_rename_desc,             (vop_t *) u9fs_rename },
        { &vop_rmdir_desc,              (vop_t *) u9fs_rmdir },
        { &vop_setattr_desc,            (vop_t *) u9fs_setattr },
        { &vop_strategy_desc,           (vop_t *) u9fs_strategy },
        { &vop_symlink_desc,            (vop_t *) u9fs_symlink },
        { &vop_write_desc,              (vop_t *) u9fs_write },
        { NULL, NULL }
};
static struct vnodeopv_desc u9fs_vnodeop_opv_desc =
        { &u9fs_vnodeop_p, u9fs_vnodeop_entries };
VNODEOP_SET(u9fs_vnodeop_opv_desc);

extern vm_zone_t u9fsnode_zone;

static int u9fs_trunc(struct vnode * vp, struct ucred * cred, struct proc * p);
static void u9fs_free_fid __P((u_short fid, struct u9fsmount * nmp, struct proc * p));
static void u9fs_updtcache __P((struct u9fsnode *, struct u9fsreq *));

#define     DIRHDSIZ        (sizeof (struct dirent) - (MAXNAMLEN + 1))

/* open returns a qid for cache consistent check */
static void
u9fs_updtcache(struct u9fsnode * np, struct u9fsreq * rep)
{
  if( rep->r_type != Rerror )
    np->n_dir.dir_qid = rep->r_qid;
}

static int
u9fs_trunc(vp, cred, p)
     register struct vnode * vp;
     struct ucred * cred;
     struct proc * p;
{
  struct u9fsnode *np = VTOU9FS(vp);
  struct u9fsmount * nmp = VFSTOU9FS(vp->v_mount);
  int error;
  u_short newfid;
  struct u9fsreq req, rep;
  u_char mode;

  /*
   * Disallow write attempts on filesystems mounted read-only;
   * unless the file is a socket, fifo, or a block or character
   * device resident on the filesystem.
   */
  if ( (vp->v_mount->mnt_flag & MNT_RDONLY)) {
    switch (vp->v_type) {
    case VREG:
    case VDIR:
    case VLNK:
      return (EROFS);
    default:
      break;
    }
  }
  mode = U9P_MODE_WR | U9P_MODE_TRUNC;
  bzero(&req, sizeof(req));
  req.r_nmp = nmp;
  req.r_procp = p;
  newfid = u9fs_id_new(nmp->nm_fids);
  req.r_type = Tclone;
  req.r_fid  = np->n_fid;
  req.r_newfid = newfid;
  error = u9fs_request(&req, &rep, 1);
  if( error )
    return error;
  req.r_type = Topen;
  req.r_fid = newfid;
  req.r_mode = mode;
  error = u9fs_request(&req, &rep, 1);
  if( !error )
    u9fs_vinvalbuf(vp, 0, cred, p, 0);
  if( error || np->n_wrfid ) {
    u9fs_free_fid(newfid, nmp, p);
    return error;
  }

  if( !U9P_PERM_EXCL(np->n_dir.dir_mode))
    np->n_wrfid = newfid;
  else
    u9fs_free_fid(newfid, nmp, p);

  return (0);  
}

/*
 * u9fs access vnode op.
 */
static int
u9fs_access(ap)
        struct vop_access_args /* {
                struct vnode *a_vp;
                int  a_mode;
                struct ucred *a_cred;
                struct proc *a_p;
        } */ *ap;
{
  register struct vnode *vp = ap->a_vp;
  struct u9fsnode *np = VTOU9FS(vp);
  struct u9fsmount * nmp = VFSTOU9FS(vp->v_mount);
  struct proc * p = ap->a_p;
  int error, a_mode = ap->a_mode;
  u_short * fidp = 0, *fidp2 = 0, newfid;
  struct u9fsreq req, rep;
  u_char mode;
  struct ucred * cred = ap->a_cred;

  /* XXX: for the moment, only the authenticator has access */
  if( cred->cr_uid != nmp->nm_authuid )
    return (EPERM);

  /*
   * Disallow write attempts on filesystems mounted read-only;
   * unless the file is a socket, fifo, or a block or character
   * device resident on the filesystem.
   */
  if ((a_mode & VWRITE) && (vp->v_mount->mnt_flag & MNT_RDONLY)) {
    switch (vp->v_type) {
    case VREG:
    case VDIR:
    case VLNK:
      return (EROFS);
    default:
      break;
    }
  }

  /* we cant open an exclusive open file here */
  if( U9P_PERM_EXCL(np->n_dir.dir_mode) )
    return 0;

  /* check permission by actually opening it */
  /* translate mode */
  mode = 0;
  if( a_mode & VREAD ) {
    fidp = &np->n_rdfid;
    mode = U9P_MODE_RD;
  }
  if( a_mode & VWRITE ) {
    fidp = &np->n_wrfid;
    mode = U9P_MODE_WR;
  }
  if( (a_mode & (VREAD|VWRITE)) == (VREAD|VWRITE) ) {
    fidp2 = &np->n_rdfid;
    mode = U9P_MODE_RDWR;
  }

  if( a_mode & VEXEC ) {
    fidp = &np->n_rdfid;
    if( vp->v_type == VREG )
      mode = U9P_MODE_EX;
  }

  if( fidp2 == 0 )
    fidp2 = fidp;

  /* open fid mode */
  bzero(&req, sizeof(req));
  req.r_nmp = nmp;
  req.r_procp = p;
  newfid = u9fs_id_new(nmp->nm_fids);
  req.r_type = Tclone;
  req.r_fid  = np->n_fid;
  req.r_newfid = newfid;
  error = u9fs_request(&req, &rep, 1);
  if( error )
    return error;
  req.r_type = Topen;
  req.r_fid = newfid;
  req.r_mode = mode;
  error = u9fs_request(&req, &rep, 1);
  u9fs_updtcache(np, &rep);
  if( error || (*fidp && *fidp2 ) ) {
    u9fs_free_fid(newfid, nmp, p);
    return error;
  }

  *fidp = *fidp2 = newfid;

  return (0);
}

/*
 * u9fs open vnode op
 * Check to see if the type is ok
 * and that deletion is not in progress.
 * For paged in text files, you will need to flush the page cache
 * if consistency is lost.
 */
/* ARGSUSED */
static int
u9fs_open(ap)
        struct vop_open_args /* {
                struct vnode *a_vp;
                int  a_mode;
                struct ucred *a_cred;
                struct proc *a_p;
        } */ *ap;
{
  register struct vnode *vp = ap->a_vp;
  struct u9fsnode *np = VTOU9FS(vp);
  int error=0, a_mode = ap->a_mode;
  u_short * fidp = 0, *fidp2 = 0, newfid;
  struct u9fsmount * nmp = VFSTOU9FS(vp->v_mount);
  struct proc * p = ap->a_p;
  struct u9fsreq req, rep;
  u_char mode;
  struct ucred * cred = ap->a_cred;

  /* assume access permissions have been checked via VOP_ACCESS */
  /* the file is actually opened except the rdwr case */

  if( a_mode & (O_EXCL|O_SHLOCK|O_EXLOCK) ) {
#if 0  /* XXX: what can we do here? */
    return (EOPNOTSUPP);
#endif
  }

  /* translate mode */
  mode = 0;
  if( a_mode & FREAD ) {
    fidp = &np->n_rdfid;
    mode = U9P_MODE_RD;
  }
  if( a_mode & FWRITE ) {
    fidp = &np->n_wrfid;
    mode = U9P_MODE_WR;
  }
  if( (a_mode & (FREAD|FWRITE)) == (FREAD|FWRITE) ) {
    fidp2 = & np->n_rdfid;
    mode = U9P_MODE_RDWR;
  }
  if( fidp2 == 0)
    fidp2 = fidp;

  if( U9P_PERM_EXCL(np->n_dir.dir_mode) ) {
    if( *fidp || *fidp2 )
      return ENOLCK;

    /* open fid mode */
    bzero(&req, sizeof(req));
    req.r_nmp = nmp;
    req.r_procp = p;
    newfid = u9fs_id_new(nmp->nm_fids);
    req.r_type = Tclone;
    req.r_fid  = np->n_fid;
    req.r_newfid = newfid;
    error = u9fs_request(&req, &rep, 1);
    if( error )
      return error;
    req.r_type = Topen;
    req.r_fid = newfid;
    req.r_mode = mode;
    error = u9fs_request(&req, &rep, 1);
    if( error ) {
      u9fs_free_fid(newfid, nmp, p);
      return error;
    }
    u9fs_updtcache(np, &rep);

    *fidp = *fidp2 = newfid;
  }

  if( *fidp == 0 )
    panic("open");

  if( *fidp2 == 0 ) {
    /* open fid mode */
    bzero(&req, sizeof(req));
    req.r_nmp = nmp;
    req.r_procp = p;
    newfid = u9fs_id_new(nmp->nm_fids);
    req.r_type = Tclone;
    req.r_fid  = np->n_fid;
    req.r_newfid = newfid;
    error = u9fs_request(&req, &rep, 1);
    if( error )
      return error;
    req.r_type = Topen;
    req.r_fid = newfid;
    req.r_mode = mode;
    error = u9fs_request(&req, &rep, 1);
    if( error ) {
      u9fs_free_fid(newfid, nmp, p);
      return error;
    }
    u9fs_updtcache(np, &rep);
    *fidp2 = newfid;
  }

  if( np->n_qid.vers != np->n_dir.dir_qid.vers ) /* content changed */
    u9fs_vinvalbuf(vp, 0, cred, p, 0);

  return 0;
}

/*
 * u9fs close vnode op
 * What an U9FS client should do upon close after writing is a debatable issue.
 * Most U9FS clients push delayed writes to the server upon close, basically for
 * two reasons:
 * 1 - So that any write errors may be reported back to the client process
 *     doing the close system call. By far the two most likely errors are
 *     U9FSERR_NOSPC and U9FSERR_DQUOT to indicate space allocation failure.
 * 2 - To put a worst case upper bound on cache inconsistency between
 *     multiple clients for the file.
 * There is also a consistency problem for Version 2 of the protocol w.r.t.
 * not being able to tell if other clients are writing a file concurrently,
 * since there is no way of knowing if the changed modify time in the reply
 * is only due to the write for this client.
 * (U9FS Version 3 provides weak cache consistency data in the reply that
 *  should be sufficient to detect and handle this case.)
 *
 * The current code does the following:
 * for U9FS Version 2 - play it safe and flush/invalidate all dirty buffers
 * for U9FS Version 3 - flush dirty buffers to the server but don't invalidate
 *                     or commit them (this satisfies 1 and 2 except for the
 *                     case where the server crashes after this close but
 *                     before the commit RPC, which is felt to be "good
 *                     enough". Changing the last argument to u9fs_flush() to
 *                     a 1 would force a commit operation, if it is felt a
 *                     commit is necessary now.
 * for NQU9FS         - do nothing now, since 2 is dealt with via leases and
 *                     1 should be dealt with via an fsync() system call for
 *                     cases where write errors are important.
 */
/* ARGSUSED */
static int
u9fs_close(ap)
        struct vop_close_args /* {
                struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                int  a_fflag;
                struct ucred *a_cred;
                struct proc *a_p;
        } */ *ap;
{ 
  int fflag = ap->a_fflag;
  struct vnode * vp = ap->a_vp;
  struct u9fsnode * np = VTOU9FS(vp);
  struct u9fsmount * nmp = VFSTOU9FS(vp->v_mount);
  struct proc * p = ap->a_p;

  if( U9P_PERM_EXCL(np->n_dir.dir_mode) ) {
    if( (fflag & FREAD) ) {
      u9fs_free_fid(np->n_rdfid, nmp, p);
      np->n_rdfid = 0;
    }

    if( (fflag & FWRITE) == FWRITE ) {
      u9fs_free_fid(np->n_wrfid, nmp, p);
      np->n_wrfid = 0;
    }
    
    if( (fflag & (FREAD|FWRITE)) == (FREAD|FWRITE) )
      np->n_wrfid = 0;
  }

  return 0;
}

/*
 * u9fs getattr call from vfs.
 */
static int
u9fs_getattr(ap)
        struct vop_getattr_args /* {
                struct vnode *a_vp;
                struct vattr *a_vap;
                struct ucred *a_cred;
                struct proc *a_p;
        } */ *ap;
{
  register struct vnode *vp = ap->a_vp;
  register struct u9fsnode *np = VTOU9FS(vp);
  int error = 0;
  struct u9fsreq req, rep;
  struct u9fsmount * nmp = VFSTOU9FS(vp->v_mount);
  struct u9fsdir * dir;
  struct vattr * vap = ap->a_vap;
  
  /*
   * Update local times for special files.
   */
  if (np->n_flag & (NACC | NUPD))
    np->n_flag |= NCHG;
#if 0
  /*
   * First look in the cache.
   */
  if (u9fs_getattrcache(vp, ap->a_vap) == 0)
    return (0);
#endif
  if( np->n_fid == 0 )
    panic("u9fs_getattr");

  /* stat fid */
  bzero(&req, sizeof(req));
  req.r_nmp = nmp;
  req.r_procp = ap->a_p;
  req.r_type = Tstat;
  req.r_fid = np->n_fid;
  error = u9fs_request(& req, & rep, 1);
  if( error )
    return error;

  /* fill in vattr */
  dir = & np->n_dir;
  u9p_m2d(rep.r_stat, dir);

  bzero(vap, sizeof(*vap));
  /* the plan9 file system has no other types. */
  /* XXX: we have not delt with devices yet */
  if( U9P_PERM_CHDIR(dir->dir_mode) )
    vap->va_type = VDIR;
  else
    vap->va_type = VREG;

  vap->va_mode  = U9P_PERM_ALL(dir->dir_mode);
  vap->va_nlink = 1;
  vap->va_uid = u9fs_name2uid(dir->dir_uid);
  vap->va_gid = u9fs_name2uid(dir->dir_gid);
  vap->va_fsid = vp->v_mount->mnt_stat.f_fsid.val[0];
  vap->va_fileid = dir->dir_qid.path;
  vap->va_size = np->n_size = dir->dir_length;
  vap->va_blocksize = PAGE_SIZE;
  vap->va_atime.tv_sec = dir->dir_atime;
  vap->va_atime.tv_nsec = 0;
  vap->va_mtime.tv_sec = dir->dir_mtime;
  vap->va_mtime.tv_nsec = 0;
  vap->va_ctime.tv_sec = dir->dir_mtime;
  vap->va_ctime.tv_nsec = dir->dir_mtime;
  vap->va_gen = VNOVAL;
  vap->va_flags = 0;
  vap->va_bytes = vap->va_size;
  vap->va_filerev = dir->dir_qid.vers;

  vp->v_type = vap->va_type;
  vp->v_tag = VT_U9FS;

  return (error);
}

/*
 * u9fs setattr call.
 */
static int
u9fs_setattr(ap)
        struct vop_setattr_args /* {
                struct vnodeop_desc *a_desc;
                struct vnode *a_vp;
                struct vattr *a_vap;
                struct ucred *a_cred;
                struct proc *a_p;
        } */ *ap;
{
  register struct vnode *vp = ap->a_vp;
  register struct u9fsnode *np = VTOU9FS(vp);
  register struct vattr *vap = ap->a_vap;
  int error = 0;
  struct u9fsmount * nmp = VFSTOU9FS(vp->v_mount);
  struct u9fsdir dir;
  struct u9fsuser * u9p;
  struct vattr attr;
  struct u9fsreq req, rep;

  if( vp->v_mount->mnt_flag & MNT_RDONLY )
    return (EROFS);

  if( vap->va_nlink != VNOVAL || vap->va_uid != VNOVAL ||
      vap->va_fsid != VNOVAL || vap->va_fileid != VNOVAL ||
#if 0
      vap->va_size != VNOVAL || vap->va_blocksize != VNOVAL ||
#endif
      vap->va_atime.tv_sec != VNOVAL || vap->va_ctime.tv_sec != VNOVAL ||
      vap->va_gen != VNOVAL ||
      vap->va_flags != VNOVAL || vap->va_bytes != VNOVAL ) {
#if 0
    printf("%d %d %d %d %d %d %d %d %d %d %d\n", vap->va_nlink, vap->va_uid, vap->va_fsid, 
               vap->va_fileid, vap->va_size, vap->va_blocksize, 
               vap->va_atime.tv_sec, vap->va_ctime.tv_sec, vap->va_gen, 
               vap->va_flags, vap->va_bytes);
    printf("unsupported setattr\n");
    /* touch tries to change ctime first. 
     * if fails, it touches the first byte
    */
#endif
    return (EOPNOTSUPP);
  }

  if( vap->va_size == 0 )
    u9fs_trunc(vp, ap->a_cred, ap->a_p);

  bcopy(&np->n_dir, &dir, sizeof(dir));
  if( vap->va_mode  != (mode_t)VNOVAL ) {
    dir.dir_mode = U9P_PERM_NONPERM(dir.dir_mode)|U9P_PERM_ALL(vap->va_mode);
  }
  if( vap->va_gid != VNOVAL ) {
    if( (u9p = u9fs_finduser(vap->va_gid)) == 0 )
      return (EINVAL);
    strncpy(u9p->u_name, dir.dir_gid, U9FS_NAMELEN);
  }
  if( vap->va_mtime.tv_sec != VNOVAL ) {
    dir.dir_mtime = vap->va_mtime.tv_sec;
  }

  /* stat fid */
  bzero(&req, sizeof(req));
  req.r_nmp = nmp;
  req.r_procp = ap->a_p;
  req.r_type = Twstat;
  req.r_fid = np->n_fid;
  u9p_d2m(&dir, req.r_stat);
  error = u9fs_request(& req, & rep, 1);
  if( error )
    return error;
  VOP_GETATTR(vp, &attr, ap->a_cred, ap->a_p);

  return 0;
}

/*
 * u9fs lookup call, one step at a time...
 * First look in cache
 * If not found, unlock the directory u9fsnode and do the rpc
 */
static int
u9fs_lookup(ap)
        struct vop_lookup_args /* {
                struct vnodeop_desc *a_desc;
                struct vnode *a_dvp;
                struct vnode **a_vpp;
                struct componentname *a_cnp;
        } */ *ap;
{
        struct componentname *cnp = ap->a_cnp;
        struct vnode *dvp = ap->a_dvp;
        struct vnode **vpp = ap->a_vpp;
        int flags = cnp->cn_flags;
        struct vnode *newvp;
        struct u9fsmount *nmp;
        long len;
        u9fsfh_t fh;
        struct u9fsnode *np;
        int lockparent, wantparent, error = 0;
        struct proc *p = cnp->cn_proc;
        struct u9fsreq req, rep;
        u_short newfid;
        struct vattr attrs;

        *vpp = NULLVP;
        if ((flags & ISLASTCN) && (dvp->v_mount->mnt_flag & MNT_RDONLY) &&
            (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME))
                return (EROFS);
        if (dvp->v_type != VDIR)
                return (ENOTDIR);
        lockparent = flags & LOCKPARENT;
        wantparent = flags & (LOCKPARENT|WANTPARENT);
        nmp = VFSTOU9FS(dvp->v_mount);
        np = VTOU9FS(dvp);
#if 0
        if ((error = cache_lookup(dvp, vpp, cnp)) && error != ENOENT) {
                struct vattr vattr;
                int vpid;

                if (error = VOP_ACCESS(dvp, VEXEC, cnp->cn_cred, p)) {
                        *vpp = NULLVP;
                        return (error);
                }

                newvp = *vpp;
                vpid = newvp->v_id;
                /*
                 * See the comment starting `Step through' in ufs/ufs_lookup.c
                 * for an explanation of the locking protocol
                 */
                if (dvp == newvp) {
                        VREF(newvp);
                        error = 0;
                } else if (flags & ISDOTDOT) {
                        VOP_UNLOCK(dvp, 0, p);
                        error = vget(newvp, LK_EXCLUSIVE, p);
                        if (!error && lockparent && (flags & ISLASTCN))
                                error = vn_lock(dvp, LK_EXCLUSIVE, p);
                } else {
                        error = vget(newvp, LK_EXCLUSIVE, p);
                        if (!lockparent || error || !(flags & ISLASTCN))
                                VOP_UNLOCK(dvp, 0, p);
                }
                if (!error) {
                        if (vpid == newvp->v_id) {
                           if (!VOP_GETATTR(newvp, &vattr, cnp->cn_cred, p)
                            && vattr.va_ctime.tv_sec == VTOU9FS(newvp)->n_ctime) {
                                u9fsstats.lookupcache_hits++;
                                if (cnp->cn_nameiop != LOOKUP &&
                                    (flags & ISLASTCN))
                                        cnp->cn_flags |= SAVENAME;
                                return (0);
                           }
                           cache_purge(newvp);
                        }
                        vput(newvp);
                        if (lockparent && dvp != newvp && (flags & ISLASTCN))
                                VOP_UNLOCK(dvp, 0, p);
                }
                error = vn_lock(dvp, LK_EXCLUSIVE, p);
                *vpp = NULLVP;
                if (error)
                        return (error);
        }
#endif
        error = 0;
        newvp = NULLVP;
        len = cnp->cn_namelen;

        /* Tclwalk tag fid newfid name */
        bzero(&req, sizeof(req));
        req.r_procp = p;
        req.r_nmp = nmp;
        req.r_type = Tclwalk;
        req.r_fid = np->n_fid;
        newfid = req.r_newfid = u9fs_id_new(nmp->nm_fids);
        bcopy(cnp->cn_nameptr, req.r_name, len);
        if( (error = u9fs_request(&req, &rep, 1)) ) {
          u9fs_id_free(nmp->nm_fids, newfid);
          return error;
        }

        fh = rep.r_qid.path;
        if( fh == 0 ) {
          u9fs_id_free(nmp->nm_fids, newfid);
          error = ENOENT;
          goto lastcheck;
        }

        /*
         * Handle RENAME case...
         */
        if (cnp->cn_nameiop == RENAME && wantparent && (flags & ISLASTCN)) {
#if 0
          /* XXX: I dont understand this. rename foo foo? */
                if (U9FS_CMPFH(np, fhp, fhsize)) {
                        m_freem(mrep);
                        return (EISDIR);
                }
#endif
                error = u9fs_nget(dvp->v_mount, fh, &np, p);
                if (error)
                  goto fail;

                if ( np->n_fid )
                  u9fs_free_fid(newfid, nmp, p);
                else
                  np->n_fid = newfid;

                newvp = U9FSTOV(np);
                *vpp = newvp;
                cnp->cn_flags |= SAVENAME;
                if (!lockparent)
                        VOP_UNLOCK(dvp, 0, p);
                return (0);
        }

        if (flags & ISDOTDOT) {
                VOP_UNLOCK(dvp, 0, p);
                error = u9fs_nget(dvp->v_mount, fh, &np, p);
                if (error) {
                        vn_lock(dvp, LK_EXCLUSIVE | LK_RETRY, p);
                        goto fail;
                }
                if( np->n_fid )
                  u9fs_free_fid(newfid, nmp, p);
                else
                  np->n_fid = req.r_newfid;

                newvp = U9FSTOV(np);
                if (lockparent && (flags & ISLASTCN) &&
                    (error = vn_lock(dvp, LK_EXCLUSIVE, p))) {
                        vput(newvp);
                        return (error);
                }
        } else if (np->n_qid.path == fh) {
                u9fs_free_fid(newfid, nmp, p);
                VREF(dvp);
                newvp = dvp;
        } else {
                error = u9fs_nget(dvp->v_mount, fh, &np, p);
                if (error)
                  goto fail;

                if( np->n_fid )
                  u9fs_free_fid(newfid, nmp, p);
                else
                  np->n_fid = req.r_newfid;

                if (!lockparent || !(flags & ISLASTCN))
                        VOP_UNLOCK(dvp, 0, p);
                newvp = U9FSTOV(np);

                VOP_GETATTR(newvp, & attrs, p->p_ucred, p);
        }

        if (cnp->cn_nameiop != LOOKUP && (flags & ISLASTCN))
                cnp->cn_flags |= SAVENAME;
#if 0
        if ((cnp->cn_flags & MAKEENTRY) &&
            (cnp->cn_nameiop != DELETE || !(flags & ISLASTCN))) {
                np->n_ctime = np->n_vattr.va_ctime.tv_sec;
                cache_enter(dvp, newvp, cnp);
        }
#endif
        *vpp = newvp;
 lastcheck:
        if (error) {
                if (newvp != NULLVP) {
                        vrele(newvp);
                        *vpp = NULLVP;
                }
                if ((cnp->cn_nameiop == CREATE || cnp->cn_nameiop == RENAME) &&
                    (flags & ISLASTCN) && error == ENOENT) {
                        if (!lockparent)
                                VOP_UNLOCK(dvp, 0, p);
                        if (dvp->v_mount->mnt_flag & MNT_RDONLY)
                                error = EROFS;
                        else
                                error = EJUSTRETURN;
                }
                if (cnp->cn_nameiop != LOOKUP && (flags & ISLASTCN))
                        cnp->cn_flags |= SAVENAME;
        }
        return (error);

 fail:
        u9fs_free_fid(newfid, nmp, p);
        return (error);
}

/*
 * u9fs read call.
 * Just call u9fs_bioread() to do the work.
 */
static int
u9fs_read(ap)
        struct vop_read_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                int  a_ioflag;
                struct ucred *a_cred;
        } */ *ap;
{
  register struct vnode *vp = ap->a_vp;
  
  if (vp->v_type != VREG)
    return (EPERM);
  return (u9fs_bioread(vp, ap->a_uio, ap->a_ioflag, ap->a_cred, 0));
}

/*
 * u9fs readlink call
 */
static int
u9fs_readlink(ap)
        struct vop_readlink_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                struct ucred *a_cred;
        } */ *ap;
{
  return (EOPNOTSUPP);
}

/*
 * u9fs mknod vop
 * just call u9fs_mknodrpc() to do the work.
 */
/* ARGSUSED */
static int
u9fs_mknod(ap)
        struct vop_mknod_args /* {
                struct vnode *a_dvp;
                struct vnode **a_vpp;
                struct componentname *a_cnp;
                struct vattr *a_vap;
        } */ *ap;
{
  return (EOPNOTSUPP);
}

/*
 * u9fs file create call
 */
static int
u9fs_create(ap)
        struct vop_create_args /* {
                struct vnode *a_dvp;
                struct vnode **a_vpp;
                struct componentname *a_cnp;
                struct vattr *a_vap;
        } */ *ap;
{
        register struct vnode *dvp = ap->a_dvp;
        register struct vattr *vap = ap->a_vap;
        register struct componentname *cnp = ap->a_cnp;
        struct u9fsnode *np = (struct u9fsnode *)0;
        struct vnode *newvp = (struct vnode *)0;
        int error = 0, len;
        struct vattr vattr;
        struct u9fsreq req, rep;
        struct u9fsmount *nmp;
        u9fsfh_t fh;
        struct proc * p;
        int pfid;

#if 0
        /*
         * Oops, not for me..
         */
        if (vap->va_type == VSOCK)
                return (u9fs_mknodrpc(dvp, ap->a_vpp, cnp, vap));
#endif

        if (error = VOP_GETATTR(dvp, &vattr, cnp->cn_cred, cnp->cn_proc)) {
                VOP_ABORTOP(dvp, cnp);
                return (error);
        }

        nmp = VFSTOU9FS(dvp->v_mount);
        np = VTOU9FS(dvp);
        p = cnp->cn_proc;

        bzero(&req, sizeof(req));
        req.r_nmp = nmp;
        req.r_procp = p;

        req.r_type = Tclone;
        pfid = req.r_fid  = np->n_fid;
        req.r_newfid = u9fs_id_new(nmp->nm_fids);
        error = u9fs_request(&req, &rep, 1);
        if( error )
          return error;

        req.r_type = Tcreate;
        req.r_fid = req.r_newfid;
        len = cnp->cn_namelen;
        if( len > U9FS_NAMELEN )
          len = U9FS_NAMELEN;
        strncpy(req.r_name, cnp->cn_nameptr, len);
        req.r_name[U9FS_NAMELEN] = 0;
        req.r_perm = U9P_PERM_ALL(vap->va_mode);
        if( vap->va_type == VDIR ) {
          req.r_perm |= 0x80000000;
          req.r_mode = U9P_MODE_RD;
        } else
          req.r_mode = U9P_MODE_WR | U9P_MODE_TRUNC;
        if(vap->va_vaflags & VA_EXCLUSIVE) 
          req.r_mode = U9P_MODE_EX;

        error = u9fs_request(&req, &rep, 1);
        if( error )
          return error;

        fh = rep.r_qid.path;
        u9fs_nget(dvp->v_mount, fh, &np, p);
        newvp = U9FSTOV(np);
        if( vap->va_type == VDIR )
          np->n_rdfid = req.r_fid;
        else
          np->n_wrfid = req.r_fid;
        
        req.r_type = Tclwalk;
        req.r_fid  = pfid;
        req.r_newfid = u9fs_id_new(nmp->nm_fids);
        /* r_name is already filled */
        error = u9fs_request(&req, &rep, 1);
        if( error )
          return error;
        np->n_fid = req.r_newfid;
        VOP_GETATTR(newvp, & vattr, p->p_ucred, p);

        *ap->a_vpp = newvp;
        zfree(namei_zone, cnp->cn_pnbuf);

        return 0;
}

/*
 * u9fs file remove call
 * To try and make u9fs semantics closer to ufs semantics, a file that has
 * other processes using the vnode is renamed instead of removed and then
 * removed later on the last close.
 * - If v_usecount > 1
 *        If a rename is not already in the works
 *           call u9fs_sillyrename() to set it up
 *     else
 *        do the remove rpc
 */
static int
u9fs_remove(ap)
        struct vop_remove_args /* {
                struct vnodeop_desc *a_desc;
                struct vnode * a_dvp;
                struct vnode * a_vp;
                struct componentname * a_cnp;
        } */ *ap;
{
        register struct vnode *vp = ap->a_vp;
        register struct componentname *cnp = ap->a_cnp;
        struct u9fsnode *np;
        struct u9fsreq req, rep;
        struct u9fsmount *nmp;
        struct proc * p;
        int error;

        nmp = VFSTOU9FS(vp->v_mount);
        np = VTOU9FS(vp);
        p = cnp->cn_proc;
        bzero(&req, sizeof(req));
        req.r_nmp = nmp;
        req.r_procp = p;
        req.r_type = Tremove;
        req.r_fid = np->n_fid;
        error = u9fs_request(&req, &rep, 1);
        if( error )
          return error;
        zfree(namei_zone, cnp->cn_pnbuf);
        return 0;
}

/*
 * u9fs file rename call
 */
static int
u9fs_rename(ap)
        struct vop_rename_args  /* {
                struct vnode *a_fdvp;
                struct vnode *a_fvp;
                struct componentname *a_fcnp;
                struct vnode *a_tdvp;
                struct vnode *a_tvp;
                struct componentname *a_tcnp;
        } */ *ap;
{
        register struct vnode *fvp = ap->a_fvp;
        register struct vnode *tvp = ap->a_tvp;
        register struct vnode *fdvp = ap->a_fdvp;
        register struct vnode *tdvp = ap->a_tdvp;
        register struct componentname *tcnp = ap->a_tcnp;
        register struct componentname *fcnp = ap->a_fcnp;
        int error, len;
        struct u9fsmount * nmp;
        struct u9fsreq req, rep;
        struct u9fsdir dir;
        struct u9fsnode * np;

        /* we cant do cross-directory renaming or move to an existing file */
        if( fdvp != tdvp || tvp != 0 || fvp->v_mount->mnt_flag & MNT_RDONLY ){
          printf("rename to existing file not supported\n");
          error = EOPNOTSUPP;
          goto out;
        }

        nmp = VFSTOU9FS(fvp->v_mount);
        np = VTOU9FS(fvp);

        bcopy(&np->n_dir, &dir, sizeof(dir));
        len = tcnp->cn_namelen;
        if( len > U9FS_NAMELEN )
          len = U9FS_NAMELEN;
        strncpy(dir.dir_name, tcnp->cn_nameptr, len);
        dir.dir_name[U9FS_NAMELEN-1] = 0;
        
        /* stat fid */
        bzero(&req, sizeof(req));
        req.r_nmp = nmp;
        req.r_procp = fcnp->cn_proc;
        req.r_type = Twstat;
        req.r_fid = np->n_fid;
        u9p_d2m(&dir, req.r_stat);
        error = u9fs_request(& req, & rep, 1);

 out:
        if (tdvp == tvp)
                vrele(tdvp);
        else
                vput(tdvp);
        if (tvp)
                vput(tvp);
        vrele(fdvp);
        vrele(fvp);

        return error;
}

/*
 * u9fs hard link create call
 */
static int
u9fs_link(ap)
        struct vop_link_args /* {
                struct vnode *a_tdvp;
                struct vnode *a_vp;
                struct componentname *a_cnp;
        } */ *ap;
{
  return (EOPNOTSUPP);
}

/*
 * u9fs symbolic link create call
 */
static int
u9fs_symlink(ap)
        struct vop_symlink_args /* {
                struct vnode *a_dvp;
                struct vnode **a_vpp;
                struct componentname *a_cnp;
                struct vattr *a_vap;
                char *a_target;
        } */ *ap;
{
  return (EOPNOTSUPP);
}

/*
 * u9fs make dir call
 */
static int
u9fs_mkdir(ap)
        struct vop_mkdir_args /* {
                struct vnode *a_dvp;
                struct vnode **a_vpp;
                struct componentname *a_cnp;
                struct vattr *a_vap;
        } */ *ap;
{
  struct vop_create_args cap;

  cap.a_dvp = ap->a_dvp;
  cap.a_vpp = ap->a_vpp;
  cap.a_cnp = ap->a_cnp;
  cap.a_vap = ap->a_vap;
  return u9fs_create(&cap);
}

/*
 * u9fs remove directory call
 */
static int
u9fs_rmdir(ap)
        struct vop_rmdir_args /* {
                struct vnode *a_dvp;
                struct vnode *a_vp;
                struct componentname *a_cnp;
        } */ *ap;
{
        register struct vnode *vp = ap->a_vp;
        register struct componentname *cnp = ap->a_cnp;
        struct u9fsnode *np;
        struct u9fsreq req, rep;
        struct u9fsmount *nmp;
        struct proc * p;
        int error;

        nmp = VFSTOU9FS(vp->v_mount);
        np = VTOU9FS(vp);
        p = cnp->cn_proc;
        bzero(&req, sizeof(req));
        req.r_nmp = nmp;
        req.r_procp = p;
        req.r_type = Tremove;
        req.r_fid = np->n_fid;
        error = u9fs_request(&req, &rep, 1);
        if( error )
          return error;
        u9fs_id_free(nmp->nm_fids, np->n_fid);
        np->n_fid = 0;
        zfree(namei_zone, cnp->cn_pnbuf);
        return 0;
}

/*
 * u9fs readdir call
 */
static int
u9fs_readdir(ap)
        struct vop_readdir_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                struct ucred *a_cred;
        } */ *ap;
{
  register struct vnode *vp = ap->a_vp;
  register struct uio *uio = ap->a_uio;
  int error;
  
  if (vp->v_type != VDIR)
    return (EPERM);

  /*
   * Call u9fs_bioread() to do the real work.
   */
  error = u9fs_bioread(vp, uio, 0, ap->a_cred, 0);
  
  return (error);
}

/*
 * Kludge City..
 * - make u9fs_bmap() essentially a no-op that does no translation
 * - do u9fs_strategy() by doing I/O with u9fs_readrpc/u9fs_writerpc
 *   (Maybe I could use the process's page mapping, but I was concerned that
 *    Kernel Write might not be enabled and also figured copyout() would do
 *    a lot more work than bcopy() and also it currently happens in the
 *    context of the swapper process (2).
 */
static int
u9fs_bmap(ap)
        struct vop_bmap_args /* {
                struct vnode *a_vp;
                daddr_t  a_bn;
                struct vnode **a_vpp;
                daddr_t *a_bnp;
                int *a_runp;
                int *a_runb;
        } */ *ap;
{
  register struct vnode *vp = ap->a_vp;
  
  if (ap->a_vpp != NULL)
    *ap->a_vpp = vp;
  if (ap->a_bnp != NULL)
    *ap->a_bnp = ap->a_bn * btodb(vp->v_mount->mnt_stat.f_iosize);
  if (ap->a_runp != NULL)
    *ap->a_runp = 0;
  if (ap->a_runb != NULL)
    *ap->a_runb = 0;
  return (0);

  return 0;
}

/*
 * Strategy routine.
 * For async requests when u9fsiod(s) are running, queue the request by
 * calling u9fs_asyncio(), otherwise just all u9fs_doio() to do the
 * request.
 */
static int
u9fs_strategy(ap)
        struct vop_strategy_args *ap;
{
        register struct buf *bp = ap->a_bp;
        struct ucred *cr;
        struct proc *p;
        int error = 0;

        if (bp->b_flags & B_PHYS)
                panic("nfs physio");
        if (bp->b_flags & B_ASYNC)
                panic("u9fs async");

        p = curproc;    /* XXX */
        if (bp->b_flags & B_READ)
                cr = bp->b_rcred;
        else
                cr = bp->b_wcred;
        error = u9fs_doio(bp, cr, p);
        return (error);  
}

/*
 * Mmap a file
 *
 * NB Currently unsupported.
 */
/* ARGSUSED */
static int
u9fs_mmap(ap)
        struct vop_mmap_args /* {
                struct vnode *a_vp;
                int  a_fflags;
                struct ucred *a_cred;
                struct proc *a_p;
        } */ *ap;
{
        return (EINVAL);
}

/*
 * fsync vnode op. Just call u9fs_flush() with commit == 1.
 */
/* ARGSUSED */
static int
u9fs_fsync(ap)
        struct vop_fsync_args /* {
                struct vnodeop_desc *a_desc;
                struct vnode * a_vp;
                struct ucred * a_cred;
                int  a_waitfor;
                struct proc * a_p;
        } */ *ap;
{
  /* we have a blocking writeback cache */
  return 0;
}

/*
 * U9FS advisory byte-level locks.
 * Currently unsupported.
 */
static int
u9fs_advlock(ap)
        struct vop_advlock_args /* {
                struct vnode *a_vp;
                caddr_t  a_id;
                int  a_op;
                struct flock *a_fl;
                int  a_flags;
        } */ *ap;
{
        register struct u9fsnode *np = VTOU9FS(ap->a_vp);

        /*
         * The following kludge is to allow diskless support to work
         * until a real NFS lockd is implemented. Basically, just pretend
         * that this is a local lock.
         */
        return (lf_advlock(ap, &(np->n_lockf), np->n_size));
}

/*
 * Print out the contents of an u9fsnode.
 */
static int
u9fs_print(ap)
        struct vop_print_args /* {
                struct vnode *a_vp;
        } */ *ap;
{
  panic("u9fs_print");
  return 0;
}

/*
 * Just call u9fs_writebp() with the force argument set to 1.
 */
static int
u9fs_bwrite(ap)
        struct vop_bwrite_args /* {
                struct vnode *a_bp;
        } */ *ap;
{
  panic("u9fs_bwrite");
  return 0;
}

/*
 * Vnode op for VM getpages.
 */
static int
u9fs_getpages(ap)
        struct vop_getpages_args /* {
                struct vnode *a_vp;
                vm_page_t *a_m;
                int a_count;
                int a_reqpage;
                vm_ooffset_t a_offset;
        } */ *ap;
{
        int i, error, nextoff, size, toff, npages, count;
        struct uio uio;
        struct iovec iov;
        vm_offset_t kva;
        struct buf *bp;
        struct vnode *vp;
        struct proc *p;
        struct ucred *cred;
        struct u9fsmount *nmp;
        vm_page_t *pages;

        vp = ap->a_vp;
        p = curproc;                            /* XXX */
        cred = curproc->p_ucred;                /* XXX */
        nmp = VFSTOU9FS(vp->v_mount);
        pages = ap->a_m;
        count = ap->a_count;

        if (vp->v_object == NULL) {
                printf("u9fs_getpages: called with non-merged cache vnode??\n");
                return VM_PAGER_ERROR;
        }

        /*
         * We use only the kva address for the buffer, but this is extremely
         * convienient and fast.
         */
        bp = getpbuf();

        npages = btoc(count);
        kva = (vm_offset_t) bp->b_data;
        pmap_qenter(kva, pages, npages);

        iov.iov_base = (caddr_t) kva;
        iov.iov_len = count;
        uio.uio_iov = &iov;
        uio.uio_iovcnt = 1;
        uio.uio_offset = IDX_TO_OFF(pages[0]->pindex);
        uio.uio_resid = count;
        uio.uio_segflg = UIO_SYSSPACE;
        uio.uio_rw = UIO_READ;
        uio.uio_procp = p;

        error = u9fs_readrpc(vp, &uio, cred);
        pmap_qremove(kva, npages);

        relpbuf(bp);

        if (error && (uio.uio_resid == count))
                return VM_PAGER_ERROR;

        size = count - uio.uio_resid;

        for (i = 0, toff = 0; i < npages; i++, toff = nextoff) {
                vm_page_t m;
                nextoff = toff + PAGE_SIZE;
                m = pages[i];

                m->flags &= ~PG_ZERO;

                if (nextoff <= size) {
                        m->valid = VM_PAGE_BITS_ALL;
                        m->dirty = 0;
                } else {
                        int nvalid = ((size + DEV_BSIZE - 1) - toff) & ~(DEV_BSIZE - 1);
                        vm_page_set_validclean(m, 0, nvalid);
                }
                
                if (i != ap->a_reqpage) {
                        /*
                         * Whether or not to leave the page activated is up in
                         * the air, but we should put the page on a page queue
                         * somewhere (it already is in the object).  Result:
                         * It appears that emperical results show that
                         * deactivating pages is best.
                         */

                        /*
                         * Just in case someone was asking for this page we
                         * now tell them that it is ok to use.
                         */
                        if (!error) {
                                if (m->flags & PG_WANTED)
                                        vm_page_activate(m);
                                else
                                        vm_page_deactivate(m);
                                vm_page_wakeup(m);
                        } else {
                                vnode_pager_freepage(m);
                        }
                }
        }
        return 0;
}

/*
 * Vnode op for VM putpages.
 */
static int
u9fs_putpages(ap)
        struct vop_putpages_args /* {
                struct vnode *a_vp;
                vm_page_t *a_m;
                int a_count;
                int a_sync;
                int *a_rtvals;
                vm_ooffset_t a_offset;
        } */ *ap;
{
  panic("u9fs_putpages");
  return 0;
}

static int
u9fs_inactive(ap)
        struct vop_inactive_args /* {
                struct vnode *a_vp;
                struct proc *a_p;
        } */ *ap;
{
  VOP_UNLOCK(ap->a_vp, 0, ap->a_p);
  return 0;
}

/*
 * Reclaim an u9fsnode so that it can be used for other purposes.
 */
static int
u9fs_reclaim(ap)
        struct vop_reclaim_args /* {
                struct vnode *a_vp;
        } */ *ap;
{
        register struct vnode *vp = ap->a_vp;
        register struct u9fsnode *np = VTOU9FS(vp);
        register struct u9fsmount *nmp = VFSTOU9FS(vp->v_mount);
        struct proc * p = curproc;

        /* some vnodes do not have fids due to previous access failure */
        if( np->n_fid ) {
          /* clunk fids */
          u9fs_free_fid(np->n_fid, nmp, p);
          if( np->n_rdfid )
            u9fs_free_fid(np->n_rdfid, nmp, p);
          if( np->n_wrfid )
            u9fs_free_fid(np->n_wrfid, nmp, p);
        }

        LIST_REMOVE(np, n_hash);
        cache_purge(vp);
        zfree(u9fsnode_zone, vp->v_data);
        vp->v_data = (void *)0;

        return (0);
}

/*
 * Vnode op for write using bio
 */
static int
u9fs_write(ap)
        struct vop_write_args /* {
                struct vnode *a_vp;
                struct uio *a_uio;
                int  a_ioflag;
                struct ucred *a_cred;
        } */ *ap;
{
  if (ap->a_vp->v_type != VREG)
    return (EIO);

  return u9fs_biowrite(ap->a_vp, ap->a_uio, ap->a_ioflag, ap->a_cred);
}

/*
 * Nfs abort op, called after namei() when a CREATE/DELETE isn't actually
 * done. Currently nothing to do.
 */
/* ARGSUSED */
static int
u9fs_abortop(ap)
        struct vop_abortop_args /* {
                struct vnode *a_dvp;
                struct componentname *a_cnp;
        } */ *ap;
{
        return (0);
}

/*
 * u9fs write call
 */
int
u9fs_writerpc(vp, uiop, cred)
        register struct vnode *vp;
        register struct uio *uiop;
        struct ucred *cred;
{
  struct u9fsmount *nmp = VFSTOU9FS(vp->v_mount);
  int error = 0, len, tsiz, rlen;
  struct u9fsreq req, rep;
  struct u9fsnode * np = VTOU9FS(vp);
  struct proc * p = uiop->uio_procp;
  struct mbuf * top;

  tsiz = uiop->uio_resid;
  if (uiop->uio_offset + tsiz > nmp->nm_maxfilesize)
    return (EFBIG);
  bzero(&req, sizeof(req));
  req.r_nmp = nmp;
  req.r_procp = p;
  req.r_type = Twrite;
  req.r_fid = np->n_wrfid;
  while (tsiz > 0) {
    len = (tsiz > nmp->nm_wsize) ? nmp->nm_wsize : tsiz;
    req.r_offset = uiop->uio_offset;
    req.r_count = len;
    error = u9fs_uiotombuf(uiop, &top, len);
    if( error )
      break;
    req.r_data = (char *)top;
    error = u9fs_request(&req, &rep, 1);
    if( error )
      break;
    rlen = rep.r_count;
    if( rlen < len ) {
      error = EIO;
      break;
    }
    tsiz -= len;

    /* each write message increments version number by one.
       to avoid flushing our write cache, update the version */
    if( np->n_qid.vers )
      np->n_qid.vers++;
    else 
      np->n_qid.vers = np->n_dir.dir_qid.vers + 1;
  }
  if (error)
    uiop->uio_resid = tsiz;
  return (error);
}

/*
 * Readdir rpc call.
 * Called from below the buffer cache by u9fs_doio().
 */
int
u9fs_readdirrpc(vp, uiop, cred)
        struct vnode *vp;
        register struct uio *uiop;
        struct ucred *cred;

{
        register int len, left;
        register struct dirent *dp;
        struct u9fsmount *nmp = VFSTOU9FS(vp->v_mount);
        struct u9fsnode *np = VTOU9FS(vp);
        int error = 0, tlen, more_dirs = 1, bigenough;
        struct u9fsreq req, rep;
        int count;
        struct u9fsdir u9dir;

        bigenough = uiop->uio_resid >= sizeof(struct dirent);
        bzero(&req, sizeof(req));
        req.r_nmp = nmp;
        req.r_type = Tread;
        req.r_fid = np->n_rdfid;
        req.r_count = nmp->nm_readdirsize;
        while ( more_dirs && bigenough ) {
          req.r_offset = uiop->uio_offset;
          error = u9fs_request(&req, &rep, 0);
          if( error )
            return error;

          count = rep.r_count;
          more_dirs = (count == req.r_count);
          len = 0;
          dp = (struct dirent *)uiop->uio_iov->iov_base;
          left = uiop->uio_resid;
          while( len < count ) {
            /* XXX: too conservative, but OK */
            if( left < sizeof(*dp) ) {
              bigenough = 0;
              break;
            }
            if( u9p_m_m2d(&req.r_mrep, & u9dir) ) {
              printf("u9p_m_m2d failed!\n");
              return (EIO);
            }
            
            dp->d_fileno = u9dir.dir_qid.path;
            if( U9P_PERM_CHDIR(u9dir.dir_mode) )
              dp->d_type = DT_DIR;
            else
              dp->d_type = DT_REG;
            u9dir.dir_name[U9FS_NAMELEN-1] = 0; /* just to be sure */
            dp->d_namlen = strlen(u9dir.dir_name);
            memcpy(dp->d_name, u9dir.dir_name, dp->d_namlen+1);
            tlen = DIRHDSIZ + dp->d_namlen + 4;
            tlen = tlen - (tlen & 0x3);
            dp->d_reclen = tlen;
            dp = (struct dirent *)(((char *)dp) + tlen);
            left -= tlen;
            len += sizeof(u9dir);
          }
          tlen = uiop->uio_resid - left;
          uiop->uio_resid = left;
          uiop->uio_iov->iov_base += tlen;
          uiop->uio_iov->iov_len -= tlen;
          uiop->uio_offset += len;
          m_freem(req.r_mrep);
        }
        return 0;
}

/*
 * u9fs read rpc call
 * Ditto above
 */
int
u9fs_readrpc(vp, uiop, cred)
        register struct vnode *vp;
        struct uio *uiop;
        struct ucred *cred;
{
  struct u9fsmount *nmp;
  struct u9fsnode *np = VTOU9FS(vp);
  int error = 0, len, retlen, tsiz;
  struct u9fsreq req, rep;

  nmp = VFSTOU9FS(vp->v_mount);
  tsiz = uiop->uio_resid;
  if (uiop->uio_offset + tsiz > nmp->nm_maxfilesize)
    return (EFBIG);
  bzero(&req, sizeof(req));
  req.r_nmp = nmp;
  req.r_type = Tread;
  req.r_fid = np->n_rdfid;
  while (tsiz > 0) {
    len = (tsiz > nmp->nm_rsize) ? nmp->nm_rsize : tsiz;
    req.r_count = len;
    req.r_offset = uiop->uio_offset;
    error = u9fs_request(&req, &rep, 0);
    if( error )
      return error;
    retlen = rep.r_count;
    if( retlen && (error = u9fs_mbuftouio(req.r_mrep, uiop, retlen)) ) {
      m_freem(req.r_mrep);
      return error;
    }
    
    m_freem(req.r_mrep);
    req.r_mrep = 0;
    tsiz -= retlen;
    if (retlen < len)
      tsiz = 0;
  }
  return (0);
}

static void u9fs_free_fid(fid, nmp, p)
     u_short fid;
     struct u9fsmount * nmp;
     struct proc * p;
{
  struct u9fsreq req, rep;

  /* clunk fid */
  bzero(&req, sizeof(req));
  req.r_nmp = nmp;
  req.r_procp = p;
  req.r_type = Tclunk;
  req.r_fid = fid;
  u9fs_request(&req, &rep, 1);
  u9fs_id_free(nmp->nm_fids, fid);
}