/*
 * nasd_dir.c
 *
 * Directory operations for EDRFS.
 *
 * Author: Nat Lanza, Mathew Monroe
 */
/*
 * Copyright (c) of Carnegie Mellon University, 1999.
 *
 * Permission to reproduce, use, and prepare derivative works of
 * this software for internal use is granted provided the copyright
 * and "No Warranty" statements are included with all reproductions
 * and derivative works. This software may also be redistributed
 * without charge provided that the copyright and "No Warranty"
 * statements are included in all redistributions.
 *
 * NO WARRANTY. THIS SOFTWARE IS FURNISHED ON AN "AS IS" BASIS.
 * CARNEGIE MELLON UNIVERSITY MAKES NO WARRANTIES OF ANY KIND, EITHER
 * EXPRESSED OR IMPLIED AS TO THE MATTER INCLUDING, BUT NOT LIMITED
 * TO: WARRANTY OF FITNESS FOR PURPOSE OR MERCHANTABILITY, EXCLUSIVITY
 * OF RESULTS OR RESULTS OBTAINED FROM USE OF THIS SOFTWARE. CARNEGIE
 * MELLON UNIVERSITY DOES NOT MAKE ANY WARRANTY OF ANY KIND WITH RESPECT
 * TO FREEDOM FROM PATENT, TRADEMARK, OR COPYRIGHT INFRINGEMENT.
 */


#include <nasd/nasd_options.h>
#include <nasd/nasd_sys.h>
#include <nasd/nasd_types.h>
#include <nasd/nasd_mem.h>
#include <nasd/nasd_threadstuff.h>
#include <nasd/nasd_freelist.h>
#include <nasd/nasd_edrfs_client.h>
#include <nasd/linux/nasd_edrfs_client_linux.h>
#include <nasd/linux/nasd_edrfs_client_linux_mount.h>
#include <nasd/nasd_edrfs_dir.h>
#include <nasd/nasd_edrfs_internal.h>
#include <nasd/nasd_edrfs_types_marshall.h>

#include <linux/kernel.h>
#include <linux/mm.h>
#include <linux/string.h>
#include <linux/stat.h>
#include <linux/errno.h>
#include <linux/locks.h>
#include <linux/unistd.h>
#include <linux/pagemap.h>

#include <asm/system.h>
#include <asm/uaccess.h>

#include <linux/fs.h>
#include <linux/dcache.h>

#define DEBUG_READ_DIR_DETAIL            0
#define DEBUG_READDIR_DETAIL             0
#define DEBUG_OPEN_DIR_DETAIL            0
#define DEBUG_RELEASE_DIR_DETAIL         0
#define DEBUG_CREATE_DETAIL              0
#define DEBUG_LOOKUP_DETAIL              0
#define DEBUG_LINK_DETAIL                0
#define DEBUG_UNLINK_DETAIL              0
#define DEBUG_SYMLINK_DETAIL             0
#define DEBUG_MKDIR_DETAIL               0
#define DEBUG_RMDIR_DETAIL               0
#define DEBUG_RENAME_DETAIL              0
#define DEBUG_DENTRY_DELETE_DETAIL       0
#define DEBUG_DENTRY_RELEASE_DETAIL      0
#define DEBUG_INVALIDATE_DIRCACHE_DETAIL 0
#define DEBUG_INSTANTIATE_DETAIL         0

/* directory file operations */
static ssize_t nasd_edrfs_read_dir (struct file  *filp,
				    char         *buf,
				    size_t        count,
				    loff_t       *ppos);

static int     nasd_edrfs_readdir (struct file  *filp,
				   void         *dirent,
				   filldir_t     filldir);

static int     nasd_edrfs_open_dir (struct inode  *dir,
				    struct file   *filp);

static int     nasd_edrfs_release_dir (struct inode  *dir,
				       struct file   *filp);


static struct file_operations nasd_edrfs_dir_operations = {
  NULL,                    /* llseek             */
  nasd_edrfs_read_dir,     /* read               */
  NULL,                    /* write              */
  nasd_edrfs_readdir,      /* readdir            */
  NULL,                    /* poll               */
  nasd_edrfs_ioctl,        /* ioctl              */
  NULL,                    /* mmap               */
  nasd_edrfs_open_dir,     /* open               */
  NULL,                    /* flush              */
  nasd_edrfs_release_dir,  /* release            */
  NULL,                    /* fsync              */
  NULL,                    /* fasync             */
  NULL,                    /* check_media_change */
  NULL,                    /* revalidate         */
  NULL                     /* lock               */
};

/* utility functions */
static nasd_status_t
nasd_edrfs_fetch_dirpage(struct inode           *inode,
			 nasd_offset_t           offset,
			 nasd_edrfs_dirpage_t   *dir_page);

/* directory inode operations */
static int nasd_edrfs_create (struct inode   *dir,
			      struct dentry  *dentry,
			      int             mode);

static struct dentry *nasd_edrfs_lookup (struct inode   *dir,
					 struct dentry  *dentry);

static int nasd_edrfs_link (struct dentry  *from_dentry,
			    struct inode   *dir,
			    struct dentry  *to_dentry);

static int nasd_edrfs_unlink (struct inode   *dir,
			      struct dentry  *dentry);

static int nasd_edrfs_symlink (struct inode   *dir,
			       struct dentry  *dentry,
			       const  char    *name);

static int nasd_edrfs_mkdir (struct inode   *dir,
			     struct dentry  *dentry,
			     int             mode);

static int nasd_edrfs_rmdir (struct inode   *dir,
			     struct dentry  *dentry);

static int nasd_edrfs_rename (struct inode   *from_dir,
			      struct dentry  *from_dentry,
			      struct inode   *to_dir,
			      struct dentry  *to_dentry);

extern int nasd_edrfs_revalidate (struct dentry  *dentry);


struct inode_operations nasd_edrfs_inode_dir_operations = {
  &nasd_edrfs_dir_operations,  /* file ops    */
  nasd_edrfs_create,           /* create      */
  nasd_edrfs_lookup,           /* lookup      */
  NULL,                        /* link        */
  nasd_edrfs_unlink,           /* unlink      */
  nasd_edrfs_symlink,          /* symlink     */
  nasd_edrfs_mkdir,            /* mkdir       */
  nasd_edrfs_rmdir,            /* rmdir       */
  NULL,                        /* mknod       */
  nasd_edrfs_rename,           /* rename      */
  NULL,                        /* readlink    */
  NULL,                        /* follow_link */
  NULL,                        /* readpage    */
  NULL,                        /* writepage   */
  NULL,                        /* bmap        */
  NULL,                        /* truncate    */
  NULL,                        /* permission  */
  NULL,                        /* smap        */
  NULL,                        /* updatepage  */
  nasd_edrfs_revalidate        /* revalidate  */
};


/* dentry operations */

static void nasd_edrfs_dentry_delete(struct dentry  *dentry);

static void nasd_edrfs_dentry_release(struct dentry  *dentry);


struct dentry_operations nasd_edrfs_dentry_operations = {
  nasd_edrfs_lookup_revalidate,  /* validate */
  NULL,	                         /* hash     */
  NULL,	                         /* compare  */
  nasd_edrfs_dentry_delete,      /* delete   */
  nasd_edrfs_dentry_release,     /* release  */
  NULL                           /* iput     */
};


/* utility functions */

static nasd_status_t
nasd_edrfs_fetch_dirpage(struct inode           *inode,
			 nasd_offset_t           offset,
			 nasd_edrfs_dirpage_t   *dir_page) {
  unsigned long          pageaddr1, pageaddr2;
  nasd_mem_list_t        memlist[2];
  int                    out_len;
  nasd_status_t          rc = NASD_SUCCESS;
  nasd_edrfs_sb_info_t  *sb_info = NASD_EDRFS_SUPER_SBINFO(inode->i_sb);

  pageaddr1 = get_cached_page(inode, offset, 0);
  pageaddr2 = get_cached_page(inode, offset + PAGE_SIZE, 0);

  if (!pageaddr1 || !pageaddr2) {
    if (!pageaddr1) { pageaddr1 =
			get_cached_page(inode, offset, 1); }
    if (!pageaddr2) { pageaddr2 =
			get_cached_page(inode, offset + PAGE_SIZE, 1); }

    /* set up the memlist */
    memlist[0].len    = memlist[1].len    = PAGE_SIZE;
    memlist[0].stride = memlist[1].stride = 0;
    memlist[0].nelem  = memlist[1].nelem  = 1;      
    memlist[0].addr = (void *) pageaddr1;
    memlist[1].addr = (void *) pageaddr2;
    memlist[0].next = &(memlist[1]);
    memlist[1].next = NULL;
    
    /* do the actual page read */
    rc = nasd_edrfs_read_memlist(inode, memlist, NASD_EDRFS_DIRPAGE_SIZE,
				 offset, &out_len);
    if (rc) {
      nasd_edrfscli_error_string_t err_str;
      nasd_printf("EDRFS: read failed, rc=0x%lx (%s) op_status=0x%x (%s)\n",
		  rc, nasd_error_string(rc), 0,
		  nasd_edrfscli_error_string(sb_info->handles.server_handle,
					     0, err_str));
      put_cached_page(pageaddr1);
      put_cached_page(pageaddr2);
      return rc;
    }
    
    if (out_len != NASD_EDRFS_DIRPAGE_SIZE) {
      if (out_len > 0) {
	nasd_printf("EDRFS: short directory page: %d @ %lu\n",
		    (int) out_len, (unsigned long) offset);
      }
	rc = NASD_FAIL;
      goto out;
    }
  }
 
  nasd_edrfs_dir_parse_page_parts((void *) pageaddr1,
				  (void *) pageaddr2, dir_page);

 out:
  put_cached_page(pageaddr1);
  put_cached_page(pageaddr2);
  return rc;
}


/* directory file operations */
static ssize_t nasd_edrfs_read_dir(struct file  *filp,
				   char         *buf,
				   size_t        count,
				   loff_t       *ppos) {
#if DEBUG_READ_DIR_DETAIL
  nasd_printf("nasd_edrfs_read_dir(0x%lx, 0x%lx, %ld, 0x%lx)\n",
	      (unsigned long) filp, (unsigned long) buf, count,
	      (unsigned long) ppos);
#endif /* DEBUG_READ_DIR_DETAIL */

  return -EISDIR;
}


/* 
 * nasd_edrfs_readdir
 * 
 * Read directory entries from the file manager.
 * 
 */
static int nasd_edrfs_readdir(struct file  *filp,
			      void         *dirent,
			      filldir_t     filldir) {
  struct dentry             *dentry;
  struct inode              *inode;
  nasd_edrfs_inode_info_t   *inode_info;
  nasd_edrfs_sb_info_t      *sb_info;

  nasd_rpc_status_t          op_status;
  nasd_status_t              rc;
  int                        err, fillerr, i, slot, pageno;
  nasd_len_t                 out_len;
  nasd_offset_t              offset;

  NASD_ASSERT(filp    != NULL);
  NASD_ASSERT(dirent  != NULL);
  NASD_ASSERT(filldir != NULL);

  dentry = filp->f_dentry;
  inode  = dentry->d_inode;

#if DEBUG_READDIR_DETAIL
  nasd_printf("nasd_edrfs_readdir(0x%lx, 0x%lx, 0x%lx)\n",
	      (unsigned long) filp,
	      (unsigned long) dirent,
	      (unsigned long) filldir);
#endif /* DEBUG_READDIR_DETAIL */

  if (!inode || !S_ISDIR(inode->i_mode)) {
    printk("EDRFS: inode is NULL or not a directory\n");
    return -EBADF;
  }

  inode_info = NASD_EDRFS_INODE_INFO(inode);
  sb_info = NASD_EDRFS_SUPER_SBINFO(inode->i_sb);

  if (sb_info->server_parse) { /* server-side parsing */
    nasd_edrfs_readdir_data_t *data;
    nasd_edrfs_readdir_args_t *args;
    nasd_edrfs_readdir_res_t  *res;
    nasd_edrfs_dirent_t       *cur_entry;

    data = nasd_edrfs_get_readdir_data();
    if (data == NULL){ return -ENOMEM; }

    args = &data->args;
    res = &data->res;
    
    /* off by one. mumble. */
    if (filp->f_pos == 0) { filp->f_pos = 1; }
    
    args->in_count = NASD_EDRFS_READDIR_ENT_MAX;
    args->in_marker = filp->f_pos;
    args->in_markerv = 0;
    
    args->in_credential.uid = current->uid;
    args->in_credential.gid = current->gid;
    
    memcpy(&args->in_identifier, &inode_info->ident,
	   sizeof(nasd_edrfs_identifier_t));
    
    rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles,
				       &inode_info->ident,
				       &args->in_credential,
				       &args->in_cookie);
    if (rc != NASD_SUCCESS) {
      nasd_printf("EDRFS: couldn't get cookie for 0x%"
		  NASD_ID_FMT " rc=0x%lx (%s)\n",
		  &inode_info->ident.nasd_identifier, rc,
		  nasd_error_string(rc));
      nasd_edrfs_free_readdir_data(data);
      return nasd_edrfs_convert_error(rc, 0);
    }

    nasd_edrfscli_readdir(sb_info->handles.server_handle, args,
			  data->out_entries, res, &op_status);
    rc = res->nasd_status;

    if ((rc != NASD_SUCCESS) || (op_status != 0)) {
      nasd_edrfscli_error_string_t err_str;
      nasd_printf("EDRFS: readdir failed, rc=0x%lx (%s) op_status=0x%x (%s)\n",
		  rc, nasd_error_string(rc), op_status,
		  nasd_edrfscli_error_string(sb_info->handles.server_handle,
					     op_status, err_str));
      nasd_edrfs_free_readdir_data(data);
      return -EIO;
    }

    NASD_ASSERT(res->out_count <= NASD_EDRFS_READDIR_ENT_MAX);

    for (i = 0; i < res->out_count; i++) {
      cur_entry = &data->out_entries[i];
      fillerr = filldir(dirent, cur_entry->nd_name,
			cur_entry->nd_namlen, filp->f_pos,
			nasd_edrfs_nasd_ident_to_ino(cur_entry->nd_ino));
      if (fillerr < 0) { break; }
      filp->f_pos++;
    }

#if DEBUG_READDIR_DETAIL
    nasd_printf("EDRFS: readdir returning, f_pos=%d\n", filp->f_pos);
#endif /* DEBUG_READDIR_DETAIL */

    nasd_edrfs_free_readdir_data(data);
    return 0;

  } else { /* client-side parsing */
    char                  *name = NULL;
    nasd_edrfs_dirpage_t  *dir_page = NULL;
    nasd_edrfs_dirdata_t  *dd;
    
    NASD_Malloc(dir_page, sizeof(nasd_edrfs_dirpage_t),
		(nasd_edrfs_dirpage_t *));
    NASD_Malloc(name, NASD_EDRFS_MAX_NAME_LEN, (char *));
    NASD_ASSERT(dir_page);
    NASD_ASSERT(name);

    /* figure out what the offset should be. */
    slot   = filp->f_pos % NASD_EDRFS_DIRPAGE_SIZE;
    offset = filp->f_pos - slot;
    slot >>= NASD_EDRFS_DIRSLOT_SIZE_BITS;
    
    if (slot == 127) {
      slot = 0;
      offset += NASD_EDRFS_DIRPAGE_SIZE;
      filp->f_pos += NASD_EDRFS_DIRSLOT_SIZE;
    }

    err = nasd_edrfs_revalidate(dentry);
    rc = nasd_edrfs_fetch_dirpage(inode, offset, dir_page);
    if (rc != NASD_SUCCESS) { goto client_out; }

    i = slot;
    while (i < NASD_EDRFS_DIRPAGE_NUM_SLOTS) {
      if (NASD_EDRFS_DIRMAP_TEST_SLOT(dir_page->header.usemap, i)) {
	dd = &(dir_page->data_slots[i]);
	
	nasd_edrfs_dir_extract_name(dir_page, i, name);
	fillerr = filldir(dirent, name, dd->name_len, dd->nasdid,
			  nasd_edrfs_nasd_ident_to_ino(dd->nasdid));
	if (fillerr < 0) { break; }
	
	filp->f_pos += (NASD_EDRFS_DIRSLOT_SIZE * NASD_EDRFS_DIRD_EXLEN(dd));
	i += NASD_EDRFS_DIRD_EXLEN(dd);
      } else {
	filp->f_pos += NASD_EDRFS_DIRSLOT_SIZE;
	i++;
      }
    }

  client_out:
    NASD_Free(dir_page, sizeof(nasd_edrfs_dirpage_t));
    NASD_Free(name, NASD_EDRFS_MAX_NAME_LEN);
    return 0;
  }
}

/*
 * nasd_edrfs_open_dir
 *
 * We're stateless like NFS, so we don't really do the open/close
 * dance. But we still care about making sure that stuff is valid
 * when the user wants it, 'cause maybe something bad happened since
 * we created the inode on lookup, and not doing anything would
 * just be rude.
 */
static int nasd_edrfs_open_dir(struct inode  *dir,
			       struct file   *filp) {
  struct dentry *dentry;

  NASD_ASSERT(filp != NULL);
  NASD_ASSERT(dir  != NULL);

  dentry = filp->f_dentry;

#if DEBUG_OPEN_DIR_DETAIL
  nasd_printf("nasd_edrfs_open_dir(0x%lx, 0x%lx) ('%s' [ino=%ld] in '%s' [ino=%ld])\n",
	      (unsigned long) dir, (unsigned long) filp,
	      dentry->d_name.name,
	      dentry->d_inode->i_ino,
	      dentry->d_parent->d_name.name,
	      dentry->d_parent->d_inode->i_ino);
#endif /* DEBUG_OPEN_DIR_DETAIL */

  /* we use the file position to keep track of readdirs */
  filp->f_pos = 0;

  return nasd_edrfs_revalidate(dentry);
}


static int nasd_edrfs_release_dir (struct inode  *dir,
				   struct file   *filp) {
#if DEBUG_RELEASE_DIR_DETAIL
  struct dentry *dentry = filp->f_dentry;

  nasd_printf("nasd_edrfs_release_dir(0x%lx, 0x%lx) ('%s' [ino=%ld] in '%s' [ino=%ld])\n",
	      (unsigned long) dir, (unsigned long) filp,
	      dentry->d_name.name,
	      dentry->d_inode->i_ino,
	      dentry->d_parent->d_name.name,
	      dentry->d_parent->d_inode->i_ino);
#endif /* DEBUG_READ_DIR_DETAIL */

  /* XXX dircache */
  return 0;
}



/* directory inode operations */

/*
 * nasd_edrfs_create
 *
 * If this fails, we just drop the dentry instead of creating a
 * negative dentry. This is because we don't really know whether the
 * create failed on the server or if there was just some freakish
 * network problem.
 */
static int nasd_edrfs_create(struct inode   *dir,
			     struct dentry  *dentry,
			     int             mode) {
  nasd_edrfs_inode_info_t  *inode_info  = NULL;
  nasd_edrfs_sb_info_t     *sb_info     = NULL;
#if DEBUG_CREATE_DETAIL
  nasd_error_string_t       err_str;
#endif
  nasd_rpc_status_t         op_status;
  nasd_status_t             rc;
  nasd_edrfs_create_data_t  *data;
  nasd_edrfs_create_args_t  *args;
  nasd_edrfs_create_res_t   *res;
  nasd_edrfs_attributes_t   *edrfsattr;

  NASD_ASSERT(dir    != NULL);
  NASD_ASSERT(dentry != NULL)

  if (!dir || !S_ISDIR(dir->i_mode)) {
    nasd_printf("EDRFS: inode in create is NULL or not a directory!\n");
    return -ENOENT;
  }

  inode_info = NASD_EDRFS_INODE_INFO(dir);
  sb_info = NASD_EDRFS_SUPER_SBINFO(dir->i_sb);

#if DEBUG_CREATE_DETAIL
  nasd_printf("nasd_edrfs_create(0x%lx, 0x%lx, %o) ('%s' in [ino=%ld])\n",
	      (unsigned long) dir, (unsigned long) dentry, mode,
	      dentry->d_name.name, dir->i_ino);
#endif /* DEBUG_CREATE_DETAIL */

  /* check the name length. */
  if (dentry->d_name.len > NASD_EDRFS_MAX_NAME_LEN)
    return -ENAMETOOLONG;

  nasd_edrfs_invalidate_dircache(dir);
  
  if (!(sb_info->handles.flags & NASD_BINDING_SERVER_VALID))
    return -EIO;

  data = nasd_edrfs_get_create_data();
  if (data == NULL)
    return -ENOMEM;

  args = &data->args;
  res  = &data->res;

  /* get a cookie */
  args->in_credential.uid = current->uid;
  args->in_credential.gid = current->uid;

  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &args->in_credential, &args->in_cookie);
  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: couldn't get cookie, rc=0x%lx, (%s)!\n",
		rc, nasd_error_string(rc));
    nasd_edrfs_free_create_data(data);
    return nasd_edrfs_convert_error(rc, 0);
  }

  /* set up RPC args */
  args->in_fieldmask = (NASD_ATTR_FS_SPECIFIC |
			NASD_EDRFS_ATTR_MODE |
			NASD_EDRFS_ATTR_UID  |
			NASD_EDRFS_ATTR_GID);

  edrfsattr = &data->edrfsattr;
  args->in_attribute.object_len = 0;
  args->in_attribute.fs_attr_modify_time.ts_sec = (unsigned) -1;
  args->in_attribute.fs_object_modify_time.ts_sec = (unsigned) -1;

  edrfsattr->type  = NASD_EDRFS_TYPE_REG;
  edrfsattr->uid   = current->uid;
  edrfsattr->gid   = current->gid;
  edrfsattr->mode  = mode | NASD_EDRFS_S_IFREG;
  edrfsattr->nlink = 1;
  edrfsattr->clean = NASD_EDRFS_CLEAN;

  args->in_directory.nasd_identifier = inode_info->ident.nasd_identifier;
  args->in_directory.disk_identifier = inode_info->ident.disk_identifier;
  args->in_directory.partnum         = inode_info->ident.partnum;

  strcpy(args->in_dirpath, dentry->d_name.name);
  nasd_edrfs_attributes_t_marshall(edrfsattr, (nasd_otw_base_t *)
				   args->in_attribute.fs_specific);

  nasd_edrfscli_create(sb_info->handles.server_handle, args, res, &op_status);
  rc = res->nasd_status;
  
  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
#if DEBUG_CREATE_DETAIL
    nasd_printf("EDRFS: create failed, rc=0x%x (%s), op_status=0x%x (%s)\n",
		rc, nasd_error_string(rc), op_status,
		nasd_cl_error_string(sb_info->handles.drive_handle,
				     op_status, err_str));
#endif /* DEBUG_CREATE_DETAIL */
    nasd_edrfs_free_create_data(data);
    /* we don't drop the dentry here because there might have
       been a transient network failure. */
    return nasd_edrfs_convert_error(rc, op_status);
  }

#if DEBUG_CREATE_DETAIL
  nasd_printf("  created object, diskid=%x, part=%d, id=0x%" NASD_ID_FMT "\n",
	      res->out_identifier.disk_identifier,
	      res->out_identifier.nasd_identifier,
	      res->out_identifier.partnum);
#endif /* DEBUG_CREATE_DETAIL */

  nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
				     res->out_attribute.fs_specific,
				     edrfsattr);

  /* stash the cookie and instantiate the dentry */
  rc = nasd_edrfs_instantiate(dentry,
			      &res->out_identifier,
			      &args->in_credential,
			      &res->out_cookie,
			      &res->out_attribute,
			      edrfsattr);
  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: create couldn't instantiate its dentry!\n");
    d_drop(dentry);
    nasd_edrfs_free_create_data(data);
    return -EIO;
  }

  dir->i_nlink++;

  nasd_edrfs_free_create_data(data);
  return 0;
}


static struct dentry *nasd_edrfs_lookup(struct inode   *dir,
					struct dentry  *dentry) {
  nasd_edrfs_inode_info_t   *inode_info;
  nasd_edrfs_sb_info_t      *sb_info;
  struct inode              *inode = NULL;
  int                        error = 0, slot, found, out_len;
  unsigned long              offset;

  nasd_edrfs_lookup_data_t  *lookupstuff;

  nasd_error_string_t       err_str;
  nasd_rpc_status_t         op_status;
  nasd_status_t             rc;

  NASD_ASSERT(dir    != NULL);
  NASD_ASSERT(dentry != NULL);

  inode_info = NASD_EDRFS_INODE_INFO(dir);
  sb_info    = NASD_EDRFS_SUPER_SBINFO(dir->i_sb);

#if DEBUG_LOOKUP_DETAIL
  nasd_printf("nasd_edrfs_lookup(0x%lx, 0x%lx) ('%s' in '%s')\n",
	      (unsigned long) dir, (unsigned long) dentry,
	      dentry->d_name.name, dentry->d_parent->d_name.name);
#endif /* DEBUG_LOOKUP_DETAIL */

  if (dentry->d_name.len > NASD_EDRFS_MAX_NAME_LEN){
    error = -ENAMETOOLONG;
    goto out;
  }

  if (!dir || !S_ISDIR(dir->i_mode)) {
    nasd_printf("EDRFS: lookup: hey, this isn't a directory!\n");
    error = -ENOTDIR;
    goto out;
  }

  /* do real lookup here */
  if (!(sb_info->handles.flags & NASD_BINDING_SERVER_VALID)){
    error = -EIO;
    goto out;
  }

  lookupstuff = nasd_edrfs_get_lookup_data();
  if (lookupstuff == NULL){
    error = -ENOMEM;
      goto out;
  }
  
  if (sb_info->server_parse) { /* server-side parsing */
    nasd_edrfs_lookup_args_t  *args;
    nasd_edrfs_lookup_res_t   *res;

    args = &lookupstuff->args;
    res  = &lookupstuff->res;

    args->in_credential.uid = current->uid;
    args->in_credential.gid = current->gid;

    rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				       &args->in_credential, &args->in_cookie);
    if (rc != NASD_SUCCESS){
      nasd_edrfs_free_lookup_data(lookupstuff);
      error = nasd_edrfs_convert_error(rc, 0);
      goto freeout;
    }
    
    args->in_identifier.nasd_identifier = inode_info->ident.nasd_identifier;
    args->in_identifier.disk_identifier = inode_info->ident.disk_identifier;
    args->in_identifier.partnum         = inode_info->ident.partnum;
    strcpy(args->in_dirpath, dentry->d_name.name);

    nasd_edrfscli_lookup(sb_info->handles.server_handle, args, res, &op_status);
    rc = res->nasd_status;

    if ((rc != NASD_SUCCESS) || (op_status != 0)) { /* failure */
      error = nasd_edrfs_convert_error(rc, op_status);
#if DEBUG_LOOKUP_DETAIL
      nasd_printf("EDRFS: lookup: rc=0x%x (%s), op_status=0x%x (%s)\n",
		  rc, nasd_error_string(rc), op_status,
		  nasd_cl_error_string(sb_info->handles.drive_handle,
				     op_status, err_str));
#endif /* DEBUG_LOOKUP_DETAIL */
      
      if (error == -ENOENT) {
	error = 0;
      }

      /* this should create a negative dentry */
      dentry->d_op = &nasd_edrfs_dentry_operations;
      dentry->d_time = jiffies;
      d_add(dentry, NULL);
      goto freeout;
    }
 
    /* stash our cookie */
    rc = nasd_edrfs_caps_insert(&res->out_identifier,
				&args->in_credential,
				&res->out_cookie);
    if (rc != NASD_SUCCESS) {
      /* uh oh. this is bad. but we can continue; it's just like
	 the cookie got ejected from the cache, so next time it's
	 needed we'll ask for it again. */
      nasd_printf("EDRFS: lookup: caps insert failed, rc=0x%x (%s)\n",
		  rc, nasd_error_string(rc));
    }
    
    /* now set up the inode with attributes and ident and such */

    /* is the attribute we got back valid? */
    if (res->out_attribute.valid == NASD_EDRFS_ATTRIBUTE_INVALID) {
      rc = nasd_edrfs_do_getattr(dentry->d_sb, &res->out_identifier,
				 &res->out_attribute.attribute);
    }

    nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
				       res->out_attribute.attribute.fs_specific,
				       &lookupstuff->edrfsattr);

    inode = nasd_edrfs_ident_get(dentry->d_sb, &res->out_identifier,
				 &res->out_attribute.attribute,
				 &lookupstuff->edrfsattr);

  } else { /* client-side parse */
    nasd_edrfs_dirpage_t      *dir_page = NULL;

    NASD_Malloc(dir_page, sizeof(nasd_edrfs_dirpage_t),
		(nasd_edrfs_dirpage_t *));
    NASD_ASSERT(dir_page);
    
    offset = found = 0;
    while (!found) {      
      rc = nasd_edrfs_fetch_dirpage(dir, offset, dir_page);

      if (rc != NASD_SUCCESS) {
	/* we've run out of directory pages. it's not here.
	   create a negative dentry and bail. */
	dentry->d_op = &nasd_edrfs_dentry_operations;
	dentry->d_time = jiffies;
	d_add(dentry, NULL);
	NASD_Free(dir_page, sizeof(nasd_edrfs_dirpage_t));
	goto freeout;
      }
      
      slot = nasd_edrfs_dir_lookup_entry(dir_page,
					 (char *) dentry->d_name.name);
      if (slot != -1) { found = 1; }

      offset += NASD_EDRFS_DIRPAGE_SIZE;
    }
    
    if (found == 0) {
      nasd_printf("this shouldn't happen (%s:%d).\n", __FILE__, __LINE__);
    }

#if DEBUG_LOOKUP_DETAIL
    nasd_printf("found it in slot %d: nid 0x%" NASD_ID_FMT "\n",
		slot, dir_page->data_slots[slot].nasdid);
#endif /* DEBUG_LOOKUP_DETAIL */
    
    /* now set up the inode with attributes and ident and such */
    
    /* fill out the edrfsid */
    lookupstuff->edrfsid.nasd_identifier = dir_page->data_slots[slot].nasdid;
    lookupstuff->edrfsid.disk_identifier = sb_info->handles.disk_identifier;
    lookupstuff->edrfsid.partnum         = sb_info->handles.partnum;
        
    rc = nasd_edrfs_do_getattr(dentry->d_sb, &lookupstuff->edrfsid,
			       &lookupstuff->nasdattr);
  
    nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
				       lookupstuff->nasdattr.fs_specific,
				       &lookupstuff->edrfsattr);
    
    inode = nasd_edrfs_ident_get(dentry->d_sb,
				 &lookupstuff->edrfsid,
				 &lookupstuff->nasdattr,
				 &lookupstuff->edrfsattr);

    NASD_Free(dir_page, sizeof(nasd_edrfs_dirpage_t));
  }
  
  /* this is how NFS handles an iget() failure. I can only
     assume it's right. after all, it's not like there's any
     documentation to tell me otherwise. */
  if (inode == NULL) { error = -EACCES; }
  
  dentry->d_op = &nasd_edrfs_dentry_operations;
  dentry->d_time = jiffies;
  d_add(dentry, inode);
  
freeout:
  nasd_edrfs_free_lookup_data(lookupstuff);
out:

#if DEBUG_LOOKUP_DETAIL
  nasd_printf("nasd_edrfs_lookup returning %d (0x%x), 0x%x == 0x%x, 0x%x\n",
	      error, (unsigned long) dentry,
	      (unsigned long) dentry->d_parent->d_inode, (unsigned long) dir,
	      (unsigned long) dentry->d_sb);
#endif /* DEBUG_LOOKUP_DETAIL */  
  return ERR_PTR(error);
}


static int nasd_edrfs_link(struct dentry  *from_dentry,
			   struct inode   *dir,
			   struct dentry  *to_dentry) {  
#if DEBUG_LINK_DETAIL
  nasd_printf("nasd_edrfs_link(0x%lx, 0x%lx, 0x%lx)\n",
	      (unsigned long) from_dentry,
	      (unsigned long) dir,
	      (unsigned long) to_dentry);
#endif /* DEBUG_LINK_DETAIL */

  return -EOPNOTSUPP;
}


static int nasd_edrfs_unlink(struct inode   *dir,
			     struct dentry  *dentry) {
  nasd_edrfs_inode_info_t  *inode_info;
  nasd_edrfs_sb_info_t     *sb_info;
  int                       error = 0;
#if DEBUG_UNLINK_DETAIL
  nasd_error_string_t       err_str;
#endif
  nasd_rpc_status_t         op_status;
  nasd_status_t             rc;
  nasd_edrfs_remove_args_t  remove_args;
  nasd_edrfs_remove_res_t   remove_res;

  NASD_ASSERT(dir    != NULL);
  NASD_ASSERT(dentry != NULL);

  if (!dir || !S_ISDIR(dir->i_mode)) {
    nasd_printf("EDRFS: inode in unlink is NULL or not a directory!\n");
    error = -ENOENT;
    goto out;
  }

  inode_info = NASD_EDRFS_INODE_INFO(dir);
  sb_info = NASD_EDRFS_SUPER_SBINFO(dir->i_sb);

#if DEBUG_UNLINK_DETAIL
  nasd_printf("nasd_edrfs_unlink(0x%lx, 0x%lx) ('%s' [ino=%ld] in [ino=%ld])\n",
	      (unsigned long) dir, (unsigned long) dentry,
	      dentry->d_name.name, dentry->d_inode->i_ino,
	      dir->i_ino);
#endif /* DEBUG_UNLINK_DETAIL */

  /* check the name length. */
  if (dentry->d_name.len > NASD_EDRFS_MAX_NAME_LEN) {
    error = -ENAMETOOLONG;
    goto out;
  }

  nasd_edrfs_invalidate_dircache(dir);

  if (!(sb_info->handles.flags & NASD_BINDING_SERVER_VALID)) {
    error = -EIO;
    goto out;
  }

  /* get a cookie */
  remove_args.in_credential.uid = current->uid;
  remove_args.in_credential.gid = current->uid;

  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &remove_args.in_credential,
				     &remove_args.in_cookie);
  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: unlink couldn't get a cookie, rc=0x%x (%s)!\n",
		rc, nasd_error_string(rc));
    error = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }

  /* set up RPC args */
  remove_args.in_directory.nasd_identifier = inode_info->ident.nasd_identifier;
  remove_args.in_directory.disk_identifier = inode_info->ident.disk_identifier;
  remove_args.in_directory.partnum         = inode_info->ident.partnum;
  strcpy(remove_args.in_dirpath, dentry->d_name.name);

  nasd_edrfscli_remove(sb_info->handles.server_handle,
		       &remove_args, &remove_res, &op_status);
  rc = remove_res.nasd_status;

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    error =  nasd_edrfs_convert_error(rc, op_status);

#if DEBUG_UNLINK_DETAIL
    nasd_printf("EDRFS: remove failed, rc=0x%x (%s), op_status=0x%x (%s)\n",
		rc, nasd_error_string(rc), op_status,
		nasd_cl_error_string(sb_info->handles.drive_handle,
				     op_status, err_str));
#endif /* DEBUG_UNLINK_DETAIL */
    goto out;
  }
  
  dentry->d_inode->i_nlink--;
  d_delete(dentry);

 out:
  return error;
}


static int nasd_edrfs_symlink(struct inode   *dir,
			      struct dentry  *dentry,
			      const  char    *symname) {
  nasd_edrfs_sb_info_t      *sb_info;
  nasd_edrfs_inode_info_t   *inode_info;
#if DEBUG_SYMLINK_DETAIL
  nasd_error_string_t        err_str;
#endif
  nasd_rpc_status_t          op_status;
  nasd_status_t              rc;
  int                        error = 0;
  nasd_edrfs_symlink_args_t  symlink_args;
  nasd_edrfs_symlink_res_t   symlink_res;

  NASD_ASSERT(dir     != NULL);
  NASD_ASSERT(dentry  != NULL);
  NASD_ASSERT(symname != NULL);

  sb_info    = NASD_EDRFS_SUPER_SBINFO(dir->i_sb);
  inode_info = NASD_EDRFS_INODE_INFO(dir);

#if DEBUG_SYMLINK_DETAIL
  nasd_printf("nasd_edrfs_symlink(0x%lx, 0x%lx, '%s') (%s/%s, %s)\n",
	      (unsigned long) dir, (unsigned long) dentry, symname,
	      dentry->d_parent->d_name.name,
	      dentry->d_name.name, symname);
#endif /* DEBUG_SYMLINK_DETAIL */

  if ((dentry->d_name.len > NASD_EDRFS_MAX_NAME_LEN) ||
      (strlen(symname) > NASD_EDRFS_MAX_NAME_LEN)) {
    error = -ENAMETOOLONG;
    goto out;
  }

  /* we need to force a new lookup, so we drop the dentry. */
  d_drop(dentry);
  nasd_edrfs_invalidate_dircache(dir);

  /* do real symlink RPC here */
  if (!(sb_info->handles.flags & NASD_BINDING_SERVER_VALID)) {
    error = -EIO;
    goto out;
  }

  /* get a cookie */
  symlink_args.in_credential.uid = current->uid;
  symlink_args.in_credential.gid = current->uid;

  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &symlink_args.in_credential,
				     &symlink_args.in_cookie);
  if (rc != NASD_SUCCESS) {
    error = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }

  strcpy(symlink_args.in_dirpath, dentry->d_name.name);
  strcpy(symlink_args.in_symlink, symname);
  symlink_args.in_directory.nasd_identifier =
    inode_info->ident.nasd_identifier;
  symlink_args.in_directory.disk_identifier =
    inode_info->ident.disk_identifier;
  symlink_args.in_directory.partnum         =
    inode_info->ident.partnum;

  /* right now the server really doesn't care about the
     attribute we pass it. this would be bad if we still cared about
     being like EDRFS. */
  memset(&symlink_args.in_attribute, 0, sizeof(nasd_attribute_t));

  nasd_edrfscli_symlink(sb_info->handles.server_handle,
			&symlink_args, &symlink_res, &op_status);
  rc = symlink_res.nasd_status;

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
#if DEBUG_SYMLINK_DETAIL
    nasd_printf("EDRFS: symlink: nasd_status=0x%x (%s), op_status=0x%x (%s)\n",
		rc, nasd_error_string(rc), op_status,
		nasd_cl_error_string(sb_info->handles.drive_handle,
				     op_status, err_str));
#endif /* DEBUG_SYMLINK_DETAIL */

    error =  nasd_edrfs_convert_error(rc, op_status);
    goto out;
  }
  
  dentry->d_parent->d_time = jiffies;

out:
  return error;
}


static int nasd_edrfs_mkdir(struct inode   *dir,
			    struct dentry  *dentry,
			    int             mode) {
  nasd_edrfs_inode_info_t  *inode_info;
  nasd_edrfs_sb_info_t     *sb_info;
  nasd_error_string_t       err_str;
  nasd_rpc_status_t         op_status;
  nasd_status_t             rc;
  nasd_edrfs_mkdir_data_t  *data;
  nasd_edrfs_mkdir_args_t  *args;
  nasd_edrfs_mkdir_res_t   *res;
  nasd_edrfs_attributes_t   *edrfsattr;

  NASD_ASSERT(dir    != NULL);
  NASD_ASSERT(dentry != NULL);

  inode_info = NASD_EDRFS_INODE_INFO(dir);
  sb_info = NASD_EDRFS_SUPER_SBINFO(dir->i_sb);

#if DEBUG_MKDIR_DETAIL
  nasd_printf("nasd_edrfs_mkdir(0x%lx, 0x%lx, %o) ('%s' in [ino=%ld])\n",
	      (unsigned long) dir, (unsigned long) dentry, mode,
	      dentry->d_name.name, dir->i_ino);
#endif /* DEBUG_MKDIR_DETAIL */

  if (!S_ISDIR(dir->i_mode)){
    nasd_printf("EDRFS: inode in mkdir isn't a directory!\n");
    return -ENOENT;
  }

  /* check the name length. */
  if (dentry->d_name.len > NASD_EDRFS_MAX_NAME_LEN)
    return -ENAMETOOLONG;

  nasd_edrfs_invalidate_dircache(dir);

  if (!(sb_info->handles.flags & NASD_BINDING_SERVER_VALID))
    return -EIO;

  data = nasd_edrfs_get_mkdir_data();
  if (data == NULL)
    return -ENOMEM;

  args = &data->args;
  res  = &data->res;

  /* get a cookie */
  args->in_credential.uid = current->uid;
  args->in_credential.gid = current->gid;

  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &args->in_credential,
				     &args->in_cookie);
  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: mkdir couldn't get a cookie, rc=0x%x (%s)!\n",
		rc, nasd_error_string(rc));
    nasd_edrfs_free_mkdir_data(data);
    return nasd_edrfs_convert_error(rc, 0);
  };

  /* set up RPC args */
  args->in_fieldmask = (NASD_ATTR_FS_SPECIFIC |
			NASD_EDRFS_ATTR_MODE |
			NASD_EDRFS_ATTR_UID  |
			NASD_EDRFS_ATTR_GID);

  edrfsattr = &data->edrfsattr;
  args->in_attribute.object_len = 0;
  args->in_attribute.fs_attr_modify_time.ts_sec = (unsigned) -1;
  args->in_attribute.fs_object_modify_time.ts_sec = (unsigned) -1;

  edrfsattr->type  = NASD_EDRFS_TYPE_DIR;
  edrfsattr->uid   = current->uid;
  edrfsattr->gid   = current->gid;
  edrfsattr->mode  = mode | NASD_EDRFS_S_IFDIR;
  edrfsattr->nlink = 1;
  edrfsattr->clean = NASD_EDRFS_CLEAN;

  args->in_directory.nasd_identifier = inode_info->ident.nasd_identifier;
  args->in_directory.disk_identifier = inode_info->ident.disk_identifier;
  args->in_directory.partnum         = inode_info->ident.partnum;
  strcpy(args->in_dirpath, dentry->d_name.name);
  nasd_edrfs_attributes_t_marshall(edrfsattr, (nasd_otw_base_t *)
				   args->in_attribute.fs_specific);

  nasd_edrfscli_mkdir(sb_info->handles.server_handle, args, res, &op_status);
  rc = res->nasd_status;

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
#if DEBUG_MKDIR_DETAIL    
    nasd_printf("EDRFS: mkdir failed, rc=0x%x (%s), op_status=0x%x (%s)\n",
		rc, nasd_error_string(rc), op_status,
		nasd_cl_error_string(sb_info->handles.drive_handle,
				     op_status, err_str));
#endif /* DEBUG_MKDIR_DETAIL */

    /* we don't drop the dentry here because there might have
       been a transient network failure. */
    nasd_edrfs_free_mkdir_data(data);
    return nasd_edrfs_convert_error(rc, op_status);
  }

#if DEBUG_MKDIR_DETAIL
  nasd_printf("  made directory, diskid=%x, part=%d, id=0x%" NASD_ID_FMT "\n",
	      res->out_identifier.disk_identifier,
	      res->out_identifier.nasd_identifier,
	      res->out_identifier.partnum);
#endif /* DEBUG_MKDIR_DETAIL */

  nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
				     res->out_attribute.fs_specific,
				     edrfsattr);

  /* stash the cookie and instantiate the dentry */
  rc = nasd_edrfs_instantiate(dentry,
			      &res->out_identifier,
			      &args->in_credential,
			      &res->out_cookie,
			      &res->out_attribute,
			      edrfsattr);
  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: mkdir couldn't instantiate its dentry!\n");
    d_drop(dentry);
    nasd_edrfs_free_mkdir_data(data);
    return -EIO;
  }

  dir->i_nlink++;

  nasd_edrfs_free_mkdir_data(data);
  return 0;
}


static int nasd_edrfs_rmdir(struct inode   *dir,
			    struct dentry  *dentry) {
  nasd_edrfs_inode_info_t  *inode_info;
  nasd_edrfs_sb_info_t     *sb_info;
  int                       error = 0;
  nasd_error_string_t       err_str;
  nasd_rpc_status_t         op_status;
  nasd_status_t             rc;
  nasd_edrfs_rmdir_args_t   rmdir_args;
  nasd_edrfs_rmdir_res_t    rmdir_res;

  NASD_ASSERT(dir    != NULL);
  NASD_ASSERT(dentry != NULL);

  inode_info = NASD_EDRFS_INODE_INFO(dir);
  sb_info = NASD_EDRFS_SUPER_SBINFO(dir->i_sb);


#if DEBUG_RMDIR_DETAIL
  nasd_printf("nasd_edrfs_rmdir(0x%lx, 0x%lx) ('%s' [ino=%ld] in [ino=%ld])\n",
	      (unsigned long) dir, (unsigned long) dentry,
	      dentry->d_name.name, dentry->d_inode->i_ino,
	      dir->i_ino);
#endif /* DEBUG_RMDIR_DETAIL */

  if (!S_ISDIR(dir->i_mode)) {
    nasd_printf("EDRFS: inode in rmdir isn't a directory!\n");
    error = -ENOENT;
    goto out;
  }

  /* check the name length. */
  if (dentry->d_name.len > NASD_EDRFS_MAX_NAME_LEN) {
    error = -ENAMETOOLONG;
    goto out;
  }

  nasd_edrfs_invalidate_dircache(dir);

  if (!(sb_info->handles.flags & NASD_BINDING_SERVER_VALID)) {
    error = -EIO;
    goto out;
  }

  /* get a cookie */
  rmdir_args.in_credential.uid = current->uid;
  rmdir_args.in_credential.gid = current->gid;

  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &rmdir_args.in_credential,
				     &rmdir_args.in_cookie);
  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: rmdir couldn't get a cookie, rc=0x%x (%s)!\n",
		rc, nasd_error_string(rc));
    error = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }

  /* set up RPC args */
  rmdir_args.in_directory.nasd_identifier = inode_info->ident.nasd_identifier;
  rmdir_args.in_directory.disk_identifier = inode_info->ident.disk_identifier;
  rmdir_args.in_directory.partnum         = inode_info->ident.partnum;
  strcpy(rmdir_args.in_dirpath, dentry->d_name.name);

  nasd_edrfscli_rmdir(sb_info->handles.server_handle,
		      &rmdir_args, &rmdir_res, &op_status);
  rc = rmdir_res.nasd_status;

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    error = nasd_edrfs_convert_error(rc, op_status);
#if DEBUG_RMDIR_DETAIL
    nasd_printf("EDRFS: rmdir failed, rc=0x%d (%s), op_status=0x%x (%s)\n",
		rc, nasd_error_string(rc), op_status,
		nasd_cl_error_string(sb_info->handles.drive_handle,
				     op_status, err_str));
#endif /* DEBUG_RMDIR_DETAIL */
    goto out;
  }

  d_drop(dentry);

  if (dentry->d_inode->i_nlink != 0) {
    dentry->d_inode->i_nlink--;
  }

 out:
  return error;
}


static int nasd_edrfs_rename(struct inode   *from_dir,
			     struct dentry  *from_dentry,
			     struct inode   *to_dir,
			     struct dentry  *to_dentry) {
  nasd_edrfs_inode_info_t   *from_dir_inode_info, *to_dir_inode_info;
  nasd_edrfs_sb_info_t      *sb_info;
  nasd_error_string_t        err_str;
  nasd_rpc_status_t          op_status;
  nasd_status_t              rc;
  int                        error = 0;

  nasd_edrfs_rename_data_t  *data;
  nasd_edrfs_rename_args_t  *args;
  nasd_edrfs_rename_res_t   *res;

  NASD_ASSERT(from_dir    != NULL);
  NASD_ASSERT(from_dentry != NULL);
  NASD_ASSERT(to_dir      != NULL);
  NASD_ASSERT(to_dentry   != NULL);

#if DEBUG_RENAME_DETAIL
  nasd_printf("nasd_edrfs_rename(0x%lx, 0x%lx, 0x%lx, 0x%lx)\n",
	      from_dir, from_dentry, to_dir, to_dentry);
#endif  /* DEBUG_RENAME_DETAIL */

#if DEBUG_RENAME_DETAIL
  nasd_printf("  from '%s' in [ino=%ld] to '%s' in [ino=%ld]\n",
	      from_dentry->d_name.name, from_dir->i_ino,
	      to_dentry->d_name.name, to_dir->i_ino);
#endif /* DEBUG_RENAME_DETAIL */

  if ((from_dentry->d_name.len > NASD_EDRFS_MAX_NAME_LEN) ||
      (to_dentry->d_name.len > NASD_EDRFS_MAX_NAME_LEN)) {
    error = -ENAMETOOLONG;
    goto out_nomem;
  }

  /* is the target busy? */
  if ((to_dentry->d_inode != NULL) && S_ISDIR(to_dentry->d_inode->i_mode)) {
    if (to_dentry->d_count > 1) {
      shrink_dcache_parent(to_dentry);
    }

    if (to_dentry->d_count > 1) {
      /* still busy? */
      error = -EBUSY;
      goto out_nomem;
    }
  }

  if (from_dentry->d_inode == to_dentry->d_inode) {
    error = 0;
    goto out_nomem;
  }

  if (to_dir->i_sb != from_dir->i_sb) {
    error = -EXDEV;
    goto out_nomem;
  }

  if (to_dir == from_dir) {
    /* rename in a single directory. simple. */
#if DEBUG_RENAME_DETAIL
    nasd_printf("  rename is within a single directory\n");
#endif /* DEBUG_RENAME_DETAIL */
    goto do_rename;
  }

  /* handle cross-directory love and joy */
  if (from_dentry->d_count > 1) {
#if DEBUG_RENAME_DETAIL
    nasd_printf("  from_dentry->d_count = %d\n", from_dentry->d_count);
#endif /* DEBUG_RENAME_DETAIL */
    shrink_dcache_parent(from_dentry);
  }

  if (from_dentry->d_count > 1) {
    error = -EBUSY;
    goto out_nomem;
  }


 do_rename:
  /* now do the actual rename */
  data = nasd_edrfs_get_rename_data();
  if (data == NULL) {
    nasd_printf("EDRFS: rename couldn't get memory!\n");
    error = -ENOMEM;
    goto out_nomem;
  }

  from_dir_inode_info = NASD_EDRFS_INODE_INFO(from_dir);
  to_dir_inode_info   = NASD_EDRFS_INODE_INFO(to_dir);
  sb_info             = NASD_EDRFS_SUPER_SBINFO(from_dir->i_sb);

  args = &data->args;
  res  = &data->res;

  args->in_credential.uid = current->uid;
  args->in_credential.gid = current->gid;
  
  memcpy(&args->in_from_directory, &from_dir_inode_info->ident,
	 sizeof(nasd_edrfs_identifier_t));
  memcpy(&args->in_to_directory, &to_dir_inode_info->ident,
	 sizeof(nasd_edrfs_identifier_t));

  strcpy(args->in_from_path, from_dentry->d_name.name);
  strcpy(args->in_to_path, to_dentry->d_name.name);

  /* XXX is this really the right cookie to get? */
  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles,
				     &to_dir_inode_info->ident,
				     &args->in_credential,
				     &args->in_cookie);
  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: couldn't get cookie for 0x%" NASD_ID_FMT ", rc=0x%lx (%s)\n",
		&to_dir_inode_info->ident.nasd_identifier,
		rc, nasd_error_string(rc));
    error = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }

#if DEBUG_RENAME_DETAIL
  nasd_printf("  rename going remote\n");
#endif /* DEBUG_RENAME_DETAIL */

  nasd_edrfscli_rename(sb_info->handles.server_handle, args, res, &op_status);

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    error = nasd_edrfs_convert_error(rc, op_status);
#if DEBUG_RENAME_DETAIL
    nasd_printf("  rename failed, rc=0x%x (%s), op_status=0x%x (%s)\n",
		rc, nasd_error_string(rc), op_status,
		nasd_cl_error_string(sb_info->handles.drive_handle,
				     op_status, err_str));
#endif /* DEBUG_RENAME_DETAIL */
    goto out;
  }

#if DEBUG_RENAME_DETAIL
  nasd_printf("  rename back from remote\n");
#endif /* DEBUG_RENAME_DETAIL */

  nasd_edrfs_invalidate_dircache(to_dir);
  nasd_edrfs_invalidate_dircache(from_dir);

#if DEBUG_RENAME_DETAIL
  nasd_printf("  rename doing d_move\n");
#endif /* DEBUG_RENAME_DETAIL */
  d_move(from_dentry, to_dentry);

 out:
  nasd_edrfs_free_rename_data(data);
 out_nomem:
  return error;
}


/* dentry functions */

static void nasd_edrfs_dentry_delete(struct dentry  *dentry) {

  NASD_ASSERT(dentry != NULL);

#if DEBUG_DENTRY_DELETE_DETAIL
  nasd_printf("nasd_edrfs_dentry_delete(0x%lx) ('%s' in '%s', d_count=%d)\n",
	      (unsigned long) dentry,
	      dentry->d_name.name,
	      dentry->d_parent->d_name.name,
	      dentry->d_count);
#endif /* DEBUG_DENTRY_DELETE_DETAIL */
}

static void nasd_edrfs_dentry_release(struct dentry  *dentry) {

  NASD_ASSERT(dentry != NULL);

#if DEBUG_DENTRY_RELEASE_DETAIL
  nasd_printf("nasd_edrfs_dentry_release(0x%lx) ('%s' in '%s', d_count=%d)\n",
	      (unsigned long) dentry,
	      dentry->d_name.name,
	      dentry->d_parent->d_name.name,
	      dentry->d_count);
#endif /* DEBUG_DENTRY_RELEASE_DETAIL */
}


/* other functions */

void nasd_edrfs_invalidate_dircache(struct inode *inode) {

  NASD_ASSERT(inode != NULL);

#if DEBUG_INVALIDATE_DIRCACHE_DETAIL
  nasd_printf("nasd_edrfs_invalidate_dircache(0x%lx)\n",
	      (unsigned long) inode);
#endif /* DEBUG_INVALIDATE_DIRCACHE_DETAIL */

  invalidate_inode_pages(inode);
}


nasd_status_t nasd_edrfs_instantiate(struct dentry           *dentry,
				     nasd_edrfs_identifier_t *identifier,
				     nasd_edrfs_credential_t *cred,
				     nasd_cookie_t           *cookie,
				     nasd_attribute_t        *nasd_attr,
				     nasd_edrfs_attributes_t *edrfsattr) {
  struct inode   *inode;
  nasd_status_t   rc;

  NASD_ASSERT(dentry     != NULL);
  NASD_ASSERT(identifier != NULL);
  NASD_ASSERT(cred       != NULL);
  NASD_ASSERT(cookie     != NULL);
  NASD_ASSERT(nasd_attr  != NULL);
  NASD_ASSERT(edrfsattr  != NULL);
  
  rc = nasd_edrfs_caps_insert(identifier, cred, cookie);
  if (rc != NASD_SUCCESS) {
    /* bad but not a critical failure; we can always refetch the
       cookie later */
    nasd_printf("EDRFS: caps insert failed for id %" NASD_ID_FMT ", rc=0x%x (%s)\n",
		identifier->nasd_identifier,
		rc, nasd_error_string(rc));
  }

#if DEBUG_INSTANTIATE_DETAIL
  nasd_printf("nasd_edrfs_instantiate(0x%lx, 0x%lx, 0x%lx, 0x%lx)\n",
	      (unsigned long) dentry, (unsigned long) identifier,
	      (unsigned long) cookie, (unsigned long) nasd_attr);
#endif /* DEBUG_INSTANTIATE_DETAIL */

  inode = nasd_edrfs_ident_get(dentry->d_sb, identifier, nasd_attr, edrfsattr);

  if (inode) {
    d_instantiate(dentry, inode);
    dentry->d_time = jiffies;
  } else {
    rc = NASD_FAIL;
  }

  return rc;
}
