 /*
 * nasd_inode.c
 *
 * EDRFS operations for the sadly misnamed VFS "inode" and "superblock".
 *
 * 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_internal.h>
#include <nasd/nasd_edrfs_types_marshall.h>

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

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

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

#define DEBUG_READ_INODE_DETAIL    0
#define DEBUG_PUT_INODE_DETAIL     0
#define DEBUG_DELETE_INODE_DETAIL  0
#define DEBUG_NOTIFY_CHANGE_DETAIL 0
#define DEBUG_READ_SUPER_DETAIL    0
#define DEBUG_PUT_SUPER_DETAIL     0
#define DEBUG_STATFS_DETAIL        0
#define DEBUG_UMOUNT_BEGIN_DETAIL  0
#define DEBUG_DO_GETATTR_DETAIL    0
#define DEBUG_REFRESH_INODE_DETAIL 0
#define DEBUG_IDENT_GET_DETAIL     0
#define DEBUG_CONVERT_ERROR_DETAIL 0

#ifndef NASD_OD_BLOCK_SIZE
#define NASD_OD_BLOCK_SIZE	   8192
#endif /* NASD_OD_BLOCK_SIZE */

extern struct inode_operations nasd_edrfs_inode_file_operations;
extern struct inode_operations nasd_edrfs_inode_dir_operations;
extern struct inode_operations nasd_edrfs_inode_symlink_operations;
extern struct dentry_operations nasd_edrfs_dentry_operations;

static inline void nasd_edrfs_print_edrfs_attr(nasd_edrfs_attributes_t *attr) {
  nasd_printf("nasd_edrfs_attributes_t {\n");
  nasd_printf("  type  = %o,\n", attr->type);
  nasd_printf("  mode  = %o,\n", attr->mode);
  nasd_printf("  nlink = %d,\n", attr->nlink);
  nasd_printf("  uid   = %d,\n", attr->uid);
  nasd_printf("  gid   = %d,\n", attr->gid);
  nasd_printf("} 0x%lx\n", (unsigned long) attr);
}

/* superblock operations */
struct super_block *nasd_edrfs_read_super(struct super_block  *sb,
					  void                *raw_data,
					  int                  silent);

static void nasd_edrfs_read_inode (struct inode  *inode);

static void nasd_edrfs_put_inode (struct inode  *inode);

static void nasd_edrfs_delete_inode (struct inode  *inode);

static int  nasd_edrfs_notify_change (struct dentry  *dentry,
				      struct iattr   *attr);

static void nasd_edrfs_put_super (struct super_block  *super);

static int  nasd_edrfs_statfs (struct super_block  *super,
			       struct statfs       *buf,
			       int                  bufsiz);

static void nasd_edrfs_umount_begin (struct super_block  *super);


static struct super_operations nasd_edrfs_super_ops = {
  nasd_edrfs_read_inode,     /* read_inode    */
  NULL,                      /* write_inode   */
  nasd_edrfs_put_inode,      /* put_inode     */
  nasd_edrfs_delete_inode,   /* delete_inode  */
  nasd_edrfs_notify_change,  /* notify_change */
  nasd_edrfs_put_super,      /* put_super     */
  NULL,                      /* write_super   */
  nasd_edrfs_statfs,         /* statfs        */
  NULL,                      /* remount_fs    */
  NULL,                      /* clear_inode   */
  nasd_edrfs_umount_begin    /* umount_begin  */
};


/* secretly, we don't really do anything here, since
   lookup does the magic for us. we just need to pretty
   things up a bit */
static void nasd_edrfs_read_inode (struct inode  *inode) {
  inode->i_op = NULL;
  inode->i_mode = 0;
  inode->i_rdev = 0;
  inode->i_blksize = inode->i_sb->s_blocksize;

#if DEBUG_READ_INODE_DETAIL
  nasd_printf("nasd_edrfs_read_inode(0x%lx) [ino=%ld]\n",
	      (unsigned long) inode, inode->i_ino);
#endif /* DEBUG_READ_INODE_DETAIL */

  /* XXX deal with attribute and caching spooge here */
}


/* clean up unused inodes */
static void nasd_edrfs_put_inode (struct inode  *inode) {

#if DEBUG_PUT_INODE_DETAIL
  nasd_printf("nasd_edrfs_put_inode(0x%lx) [ino=%ld]\n",
	      (unsigned long) inode, inode->i_ino);
#endif /* DEBUG_PUT_INODE_DETAIL */
  if (inode->i_count == 1) { inode->i_nlink = 0; }
}


static void nasd_edrfs_delete_inode (struct inode  *inode) {
  nasd_edrfs_inode_info_t *inode_info = NASD_EDRFS_INODE_INFO(inode);

#if DEBUG_DELETE_INODE_DETAIL
  nasd_printf("nasd_edrfs_delete_inode(0x%lx) ([ino=%ld], i_nlink=%d)\n",
	      (unsigned long) inode, inode->i_ino, inode->i_nlink);
#endif /* DEBUG_DELETE_INODE_DETAIL */

  if (inode->i_nlink == 0) {
    NASD_Free(inode_info, sizeof(nasd_edrfs_inode_info_t));
    clear_inode(inode);
  }
}


static int  nasd_edrfs_notify_change (struct dentry  *dentry,
				      struct iattr   *attr) {
  nasd_edrfs_inode_info_t   *inode_info;
  nasd_edrfs_sb_info_t      *sb_info;
  struct inode              *inode = dentry->d_inode;
  int                        error = 0;
  nasd_error_string_t        err_str;
  nasd_rpc_status_t          op_status;
  nasd_status_t              rc;
  nasd_edrfs_notify_data_t  *data;
  nasd_edrfs_setattr_args_t *args;
  nasd_edrfs_setattr_res_t  *res;
  nasd_edrfs_attributes_t   *edrfsattr;
  nasd_attribute_t          *nasd_attr;

  NASD_ASSERT(dentry != NULL);
  NASD_ASSERT(attr != NULL);
  NASD_ASSERT(inode->u.generic_ip != NULL);

  inode_info = NASD_EDRFS_INODE_INFO(inode);
  sb_info = NASD_EDRFS_SUPER_SBINFO(dentry->d_sb);

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

  /* make sure the inode is up to date first */
  error = nasd_edrfs_revalidate(dentry);
  if (error != 0) {
    nasd_printf("EDRFS: revalidate of '%s' [ino=%d] in '%s' [ino=%d] failed, error=%d!\n",
		dentry->d_name.name, dentry->d_inode->i_ino,
		dentry->d_parent->d_name.name,
		dentry->d_parent->d_inode->i_ino, error);
    goto out;
  }

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

  data = nasd_edrfs_get_notify_data();
  if(data == NULL){
    error = -ENOMEM;
    goto out;
  }

  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: notify_change 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 */
  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;

  args->in_fieldmask = 0;

  nasd_attr = &args->in_attribute;
  memset((char *) nasd_attr, 0, sizeof(nasd_attribute_t));

  edrfsattr = &data->edrfsattr;


  if (attr->ia_valid & ATTR_MODE) {
#if DEBUG_NOTIFY_CHANGE_DETAIL
    nasd_printf("trying to set mode (0%o)\n", edrfsattr->mode);
#endif /* DEBUG_NOTIFY_CHANGE_DETAIL */
    args->in_fieldmask |= NASD_EDRFS_ATTR_MODE | NASD_ATTR_FS_SPECIFIC;
    edrfsattr->mode = attr->ia_mode;
  }

  edrfsattr->uid = inode->i_uid;
  if (attr->ia_valid & ATTR_UID) {
    args->in_fieldmask |= NASD_EDRFS_ATTR_UID | NASD_ATTR_FS_SPECIFIC;
    edrfsattr->uid = attr->ia_uid;
  }

  edrfsattr->gid = inode->i_gid;
  if (attr->ia_valid & ATTR_GID) {
    args->in_fieldmask |= NASD_EDRFS_ATTR_GID | NASD_ATTR_FS_SPECIFIC;
    edrfsattr->gid = attr->ia_gid;
  }

  nasd_attr->object_len = inode->i_size;
  if (attr->ia_valid & ATTR_SIZE) {
    args->in_fieldmask |= NASD_ATTR_OBJECT_LEN;
    nasd_attr->object_len = attr->ia_size;
  }

  nasd_attr->fs_attr_modify_time.ts_sec = inode->i_atime;
  if (attr->ia_valid & ATTR_ATIME) {
    args->in_fieldmask |= NASD_ATTR_ATTR_MODIFY_TIME;
    nasd_attr->fs_attr_modify_time.ts_sec = attr->ia_atime;
  }

  nasd_attr->fs_object_modify_time.ts_sec = inode->i_mtime;
  if (attr->ia_valid & ATTR_MTIME) {
    args->in_fieldmask |= NASD_ATTR_MODIFY_TIME;
    nasd_attr->fs_object_modify_time.ts_sec = attr->ia_mtime;
  }

  nasd_edrfs_attributes_t_marshall(edrfsattr,
				   (nasd_otw_base_t *) nasd_attr->fs_specific);

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

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

    nasd_printf("EDRFS: setattr 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));
    goto out;
  }

  /* update the cached attribute */

#if 0
  nasd_edrfs_refresh_inode(dentry->d_inode, &res->out_attribute);
#else
  inode_info->cache_info.cache_valid = 0;
  nasd_edrfs_revalidate(dentry);
#endif /* 0 */
out:
  nasd_edrfs_free_notify_data(data);
  return error;
}


struct super_block *nasd_edrfs_read_super(struct super_block  *sb,
					  void                *raw_data,
					  int                  silent) {
  
  nasd_edrfs_mount_data_t *mount_data = (nasd_edrfs_mount_data_t *) raw_data;
  struct inode *root_inode = NULL;

  nasd_edrfs_super_data_t *data;
  nasd_edrfs_mount_args_t *args;
  nasd_edrfs_mount_res_t *res;
  nasd_edrfs_sb_info_t *sb_info;
  char hostname[16]; /* to make nasd_bind_to_drive happy */

  nasd_status_t rc;
  nasd_rpc_status_t op_status;

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("EDRFS: read_super(0x%lx, 0x%lx (%s:%s), %d\n",
	      (unsigned long) sb, (unsigned long) raw_data,
	      mount_data->server, mount_data->partition, silent);
#endif /* DEBUG_READ_SUPER_DETAIL */

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("  initializing drive client library...\n");
#endif /* DEBUG_READ_SUPER_DETAIL */

  rc = nasd_cl_p_init();
  if (rc) {
    nasd_printf("EDRFS: read_super can't init NASD client library, rc=0x%x\n", rc);
    goto done_bad_nasd_init;
  }

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("  initializing edrfs client library...\n");
#endif /* DEBUG_READ_SUPER_DETAIL */

  rc = nasd_edrfscli_init();
  if (rc) {
    printk(KERN_ERR "EDRFS: read_super can't init NASD EDRFS client library, rc=0x%x\n", rc);
    goto done_bad_edrfs_init;
  }

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("  initializing edrfs client fs library...\n");
#endif /* DEBUG_READ_SUPER_DETAIL */

  rc = nasd_edrfs_client_fs_init();
  if (rc) {
    nasd_printf("EDRFS: read_super can't init NASD client fs library, rc=0x%x\n", rc);
    goto done_bad_edrfs_fs_init;
  }

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("  initializing edrfs freelists...\n");
#endif /* DEBUG_READ_SUPER_DETAIL */

  rc = nasd_edrfs_data_init();
  if (rc) {
    nasd_printf("EDRFS: read_super can't init NASD client freelists, rc=0x%x\n", rc);
    goto done_bad_edrfs_fs_init;
  }


  if (raw_data == NULL) {
    nasd_printf("EDRFS: no argument data for mount!\n");
    goto done_no_args;
  }

  data = nasd_edrfs_get_super_data();
  if(data == NULL){
    nasd_printf("EDRFS: no memory for super block!\n");
    goto done_sballoc_fail; 
  }
  
  NASD_Malloc(sb_info, sizeof(nasd_edrfs_sb_info_t),
	      (nasd_edrfs_sb_info_t *));
  if(sb_info == NULL){
    nasd_printf("EDRFS: no memory for super block!\n");
    goto done_sballoc_fail; 
  }

  sb_info->handles.flags = 0;

  /* these values are in jiffies. */
  sb_info->min_dir_attrcache = 100;
  sb_info->max_dir_attrcache = 3000;
  sb_info->min_file_attrcache = 100;
  sb_info->max_file_attrcache = 3000;

  /* try to initialize our stuff and mount. */

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("  initializing capability cache...\n");
#endif /* DEBUG_READ_SUPER_DETAIL */

  rc = nasd_edrfs_caps_init();
  if (rc) {
    nasd_printf("EDRFS: couldn't init capability cache! rc=0x%x (%s)\n",
	   rc, nasd_error_string(rc));
    goto done_bad_cap_init;
  }

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("  binding to file manager...\n");
#endif /* DEBUG_READ_SUPER_DETAIL */

  rc = nasd_bind_to_edrfs_server(mount_data->server_ip, NASD_EDRFS_SERVER_PORT,
				 mount_data->binding_type, NULL, 0,
				 &sb_info->handles.server_handle);
  
  if (rc) {
    nasd_printf("EDRFS: read_super couldn't bind to server '%s', rc=0x%x (%s)\n",
	   mount_data->server_ip, rc, nasd_error_string(rc));
    goto done_bad_bind;
  }

  sb_info->handles.flags |= NASD_BINDING_SERVER_VALID;

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

  strncpy(args->in_dirpath, mount_data->partition, NASD_EDRFS_MAX_NAME_LEN);
  args->in_credential.uid = current->uid;
  args->in_credential.gid = current->uid;

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("  issuing mount rpc...\n");
#endif /* DEBUG_READ_SUPER_DETAIL */

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

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    nasd_edrfscli_error_string_t edrfserr_str;
    nasd_printf("EDRFS: mount failed, rc=0x%x (%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, edrfserr_str));
    goto done_bad_mount;
  }

  /* do we have drives? */
  if (res->out_listlen == 0) {
    nasd_printf("EDRFS: mount didn't return any drives!\n");
    goto done_bad_mount;
  }

  /* XXX we only handle the first drive now. */

  sb_info->handles.partnum         = res->out_drivelist[0].partnum;
  sb_info->handles.drive_address   = res->out_drivelist[0].network_address;
  sb_info->handles.disk_identifier = res->out_drivelist[0].disk_identifier;
#ifdef __LITTLE_ENDIAN
  sprintf(hostname, "%d.%d.%d.%d",
	 (res->out_drivelist[0].network_address & 0x000000ff),
	 (res->out_drivelist[0].network_address & 0x0000ff00) >>  8,
	 (res->out_drivelist[0].network_address & 0x00ff0000) >> 16,
	 (res->out_drivelist[0].network_address & 0xff000000) >> 24);
#else /* !__LITTLE_ENDIAN */
  sprintf(hostname, "%d.%d.%d.%d",
	 (res->out_drivelist[0].network_address & 0xff000000) >> 24,
	 (res->out_drivelist[0].network_address & 0x00ff0000) >> 16,
	 (res->out_drivelist[0].network_address & 0x0000ff00) >>  8,
	 (res->out_drivelist[0].network_address & 0x000000ff));
#endif /* __LITTLE_ENDIAN */

#if DEBUG_READ_SUPER_DETAIL
  nasd_printf("  binding to drive '%s'...\n", hostname);
#endif /* DEBUG_READ_SUPER_DETAIL */

  rc = nasd_bind_to_drive(hostname,
			  NASD_PDRIVE_PORT,
			  mount_data->binding_type,
			  NULL, 0,
			  &sb_info->handles.drive_handle);
  if (rc) {
    nasd_printf("EDRFS: read_super couldn't bind to drive '%s', rc=%d\n",
	   hostname, rc);
    goto done_bad_mount;
  }

  sb_info->handles.flags |= NASD_BINDING_DRIVE_VALID;
  
  /* stash away the cookie for our root dir */
  rc = nasd_edrfs_caps_insert(&res->out_identifier,
			      &args->in_credential,
			      &res->out_cookie);

  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: couldn't insert cookie, rc=0x%x (%s)\n",
		rc, nasd_error_string(rc));
    goto done_bad;
  }

  /* now is the time on Sprockets when we set up our superblock. */
  lock_super(sb);

  sb->s_magic = NASD_EDRFS_SUPER_MAGIC;
  sb->u.generic_sbp =  sb_info;
  sb->s_op = &nasd_edrfs_super_ops;
  sb->s_blocksize = NASD_OD_BLOCK_SIZE;
  sb->s_blocksize_bits = 13;

  rc = nasd_edrfs_do_getattr(sb, &res->out_identifier, &data->attr);
  
  if (rc) {
    nasd_printf("EDRFS: couldn't get attributes for root object 0x%" NASD_ID_FMT ", rc=0x%x (%s)\n",
	   res->out_identifier.nasd_identifier, rc,
	   nasd_error_string(rc));
    unlock_super(sb);
    goto done_bad;
  }

  nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *)
				     &data->attr.fs_specific,
				     &data->edrfsattr);

  root_inode = nasd_edrfs_ident_get(sb, &res->out_identifier, &data->attr,
				    &data->edrfsattr);

  if (root_inode == NULL) {
    /* uh-oh. */
    nasd_printf("EDRFS: couldn't get a root inode!\n");
    unlock_super(sb);
    goto done_bad;
  }

  sb->s_root = d_alloc_root(root_inode, NULL);
  sb->s_root->d_op = &nasd_edrfs_dentry_operations;

  sb_info->server_parse = (mount_data->flags & NASD_EDRFS_MOUNT_SERVERPARSE);
  sb_info->readonly     = (mount_data->flags & NASD_EDRFS_MOUNT_RW);

  nasd_edrfs_free_super_data(data);

  unlock_super(sb);
  return sb;
  
  /* Uh-oh. Something got horked. */

 done_bad:
  nasd_unbind_drive(&sb_info->handles.drive_handle);
 done_bad_mount:
 done_bad_mount_args:
  nasd_unbind_edrfs_server(&sb_info->handles.server_handle);
 done_bad_bind:
  nasd_edrfs_caps_shutdown();
 done_bad_cap_init:
  nasd_edrfs_free_super_data(data);
 done_sballoc_fail:
 done_no_args:
  nasd_edrfs_client_fs_shutdown();
 done_bad_edrfs_fs_init:
  nasd_edrfscli_shutdown();
 done_bad_edrfs_init:
  nasd_cl_p_shutdown();			
 done_bad_nasd_init:
  sb->s_dev = 0;
  return NULL;
}


static void nasd_edrfs_put_super (struct super_block  *super) {
  nasd_edrfs_sb_info_t *sb_info = NASD_EDRFS_SUPER_SBINFO(super);
  
  super->s_dev = 0;

#if DEBUG_PUT_SUPER_DETAIL
  nasd_printf("nasd_edrfs_put_super(0x%lx)\n", (unsigned long) super);
#endif /* DEBUG_PUT_SUPER_DETAIL */

  /* XXX do cache cleanup here */

  nasd_unbind_drive(&sb_info->handles.drive_handle);
  nasd_unbind_edrfs_server(&sb_info->handles.server_handle);

  NASD_Free(sb_info, sizeof(nasd_edrfs_sb_info_t));

  nasd_edrfs_data_shutdown();
  nasd_edrfscli_shutdown();
  nasd_edrfs_caps_shutdown();
  nasd_cl_p_shutdown();

  nasd_edrfs_client_fs_shutdown();

#if NASD_MEM_COUNT_ALLOC > 0
  if (nasd_mem_allocated) {
    nasd_printf("WARNING: %" NASD_MEMALLOC_FMT
		" bytes of memory still outstanding, something leaked core.\n",
		nasd_mem_allocated);
  } else {
    nasd_printf("0 bytes of memory still outstanding.\n");
  }
#endif /* NASD_MEM_COUNT_ALLOC > 0 */
}


static int  nasd_edrfs_statfs (struct super_block  *super,
			       struct statfs       *user_stat,
			       int                  bufsiz) {  
  nasd_cookie_t cookie;
  nasd_security_param_t sp;
  nasd_status_t rc;

  nasd_edrfs_sb_info_t *sb_info = NASD_EDRFS_SUPER_SBINFO(super);
  nasd_edrfs_handles_t *nasd_handles = &sb_info->handles;

  int error = 0;

  /* we don't allocate these on the stack because of the pitiful
     8k stack limit in the kernel. it'd suck to blow the stack somewhere
     deep in RPC hell. */
  nasd_ctrl_part_info_t *out_partinfo = NULL;
  struct statfs *tmp_stat = NULL;

#if DEBUG_STATFS_DETAIL
  nasd_printf("nasd_edrfs_statfs(0x%lx, 0x%lx, %d)\n",
	      (unsigned long) super, (unsigned long) user_stat, bufsiz);
#endif /* DEBUG_STATFS_DETAIL */

  NASD_Malloc(out_partinfo, sizeof(nasd_ctrl_part_info_t),
	      (nasd_ctrl_part_info_t *));
  if (out_partinfo == NULL) {
    nasd_printf("EDRFS: couldn't allocate memory in statfs!\n");
    error = -ENOMEM;
    goto out_nopartinfo;
  }

  NASD_Malloc(tmp_stat, sizeof(struct statfs), (struct statfs *));

  if (tmp_stat == NULL) {
    nasd_printf("EDRFS: couldn't allocate memory in statfs!\n");
    error = -ENOMEM;
    goto out_nostat;
  }

  /* @@@ initialize cookie */
  memset(&cookie, 0, sizeof(cookie));
  memset(&sp, 0, sizeof(sp));

  rc = nasd_cl_p_ctrl_get_part_info(nasd_handles->drive_handle,
				    cookie.key, &sp,
				    &cookie.capability,
				    nasd_handles->partnum,
				    out_partinfo);

  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: statfs failed, rc=0x%x (%s)!\n",
		rc, nasd_error_string(rc));
    error = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }

  tmp_stat->f_type    = NASD_EDRFS_SUPER_MAGIC;
  tmp_stat->f_bsize   = out_partinfo->blocksize;
  tmp_stat->f_blocks  = out_partinfo->part_size;
  tmp_stat->f_bfree   =
    out_partinfo->part_size - out_partinfo->blocks_allocated;
  tmp_stat->f_bavail  = tmp_stat->f_bfree;
  tmp_stat->f_files   = out_partinfo->max_objs;
  tmp_stat->f_ffree   = out_partinfo->max_objs - out_partinfo->num_obj;
  tmp_stat->f_namelen = NASD_EDRFS_MAX_NAME_LEN;

  error = copy_to_user(user_stat, tmp_stat, bufsiz) ? -EFAULT : 0;

 out:
  NASD_Free(tmp_stat, sizeof(struct statfs));
 out_nostat:
  NASD_Free(out_partinfo, sizeof(nasd_ctrl_part_info_t))
 out_nopartinfo:
  return error;
}

void nasd_edrfs_free_dentries(struct inode *inode) {
  struct list_head *tmp = NULL, *head = &inode->i_dentry;
  struct dentry *dentry = NULL;

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

 restart:
  tmp = head;

  while ((tmp = tmp->next) != head) {
    dentry = list_entry(tmp, struct dentry, d_alias);
    
#if DEBUG_FREE_DENTRIES_DETAIL
    nasd_printf("EDRFS: free_dentries: found %s/%s, d_count=%d, hashed=%d\n",
		dentry->d_parent->d_name.name, dentry->d_name.name,
		dentry->d_count, !list_empty(&dentry->d_hash));
#endif /* DEBUG_FREE_DENTRIES_DETAIL */

    if (!dentry->d_count) {
      dget(dentry);
      d_drop(dentry);
      dput(dentry);
      goto restart;
    }
  }
}

static void nasd_edrfs_umount_begin (struct super_block  *super) {
  /* we may eventually have to play this game, but not yet.
     we're in Synchronous Land for now.
  */

#if DEBUG_UMOUNT_BEGIN_DETAIL
  nasd_printf("nasd_edrfs_umount_begin(0x%lx)\n", (unsigned long) super);
#endif /* DEBUG_UMOUNT_BEGIN_DETAIL */
}


/* initialization foo */

struct file_system_type nasd_edrfs_fs_type = {
  "edrfs",             /* filesystem name     */
  0,                    /* flags               */
  nasd_edrfs_read_super,  /* read_super function */
  NULL                  /* VFS internal        */
};


int init_nasd_edrfs_fs(void) {
  return register_filesystem(&nasd_edrfs_fs_type);
}

int shutdown_nasd_edrfs_fs(void) {
  return unregister_filesystem(&nasd_edrfs_fs_type);
}

/* other useful functions */

/*
 * nasd_edrfs_do_getattr
 *
 * Get the attributes for a file. Don't update the inode, since
 * other functions care about whether that happens, and they can
 * damn well do it themselves.
 */
nasd_status_t nasd_edrfs_do_getattr(struct super_block     *sb,
				    nasd_edrfs_identifier_t  *ident,
				    nasd_attribute_t       *attr) {
  nasd_edrfs_sb_info_t *sb_info = NASD_EDRFS_SUPER_SBINFO(sb);
  nasd_edrfs_credential_t cred;
  nasd_error_string_t err_str;
  nasd_rpc_status_t op_status;
  nasd_status_t rc;
  nasd_cookie_t cookie;

  nasd_security_param_t sp;
  nasd_p_getattr_dr_args_t getattr_args;
  nasd_p_getattr_dr_res_t  getattr_res;
  
#if DEBUG_DO_GETATTR_DETAIL
  nasd_printf("nasd_edrfs_do_getattr(0x%lx, 0x%lx, 0x%lx) [ino=%ld]\n",
	      (unsigned long) sb, (unsigned long) ident,
	      (unsigned long) attr, ident->nasd_identifier);
#endif /* DEBUG_DO_GETATTR_DETAIL */

  if (!(sb_info->handles.flags & NASD_BINDING_DRIVE_VALID)) {
    nasd_printf("uh-oh. flags are 0x%x, which means our handle is bad!\n",
		sb_info->handles.flags);
    return NASD_BAD_HANDLE;
  }
  
  cred.uid = current->uid;
  cred.gid = current->gid;

  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, ident,
				   &cred, &cookie);
  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: couldn't find or get a cookie, rc=0x%x (%s)\n",
		rc, nasd_error_string(rc));
    return rc;
  }

  getattr_args.in_identifier      = ident->nasd_identifier;
  getattr_args.in_partnum         = ident->partnum;

  SETUP_SECURITY_PARAM(sp, cookie.capability);

  nasd_cl_p_getattr_dr(sb_info->handles.drive_handle, cookie.key,
		       &sp, &cookie.capability, &getattr_args,
		       &getattr_res, &op_status);
  rc = getattr_res.nasd_status;

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
#if DEBUG_DO_GETATTR_DETAIL
    nasd_printf("EDRFS: do_getattr: 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_DO_GETATTR_DETAIL */
    return rc;
  }

  memcpy(attr, &getattr_res.out_attribute, sizeof(nasd_attribute_t));

  return NASD_SUCCESS;
}

/*
 * nasd_edrfs_refresh_inode
 * 
 * Update the attributes for an inode. Also mangle the attribute
 * cache timeout stuff.
 */
int nasd_edrfs_refresh_inode(struct inode            *inode,
			     nasd_attribute_t        *nasd_attr,
			     nasd_edrfs_attributes_t *edrfsattr) {
  nasd_edrfs_inode_info_t *inode_info;
  nasd_edrfs_inode_attr_cache_info_t *cache_info;
  int invalid = 0, error = -EIO;
  nasd_uint32 saved_mode;

  NASD_ASSERT(inode     != NULL);
  NASD_ASSERT(nasd_attr != NULL);

  inode_info = NASD_EDRFS_INODE_INFO(inode);
  cache_info = &inode_info->cache_info;

#if DEBUG_REFRESH_INODE_DETAIL
  nasd_printf("nasd_edrfs_refresh_inode(0x%lx, 0x%lx, 0x%lx) [ino=%ld]\n",
	      (unsigned long) inode,
	      (unsigned long) nasd_attr,
	      (unsigned long) edrfsattr,
	      inode->i_ino);
#endif /* DEBUG_REFRESH_INODE_DETAIL */

  /* has the type changed? */
  if ((S_ISDIR(inode->i_mode) && (edrfsattr->type != NASD_EDRFS_TYPE_DIR)) ||
      (S_ISREG(inode->i_mode) && (edrfsattr->type != NASD_EDRFS_TYPE_REG)) ||
      (S_ISLNK(inode->i_mode) && (edrfsattr->type != NASD_EDRFS_TYPE_LNK))) {
    nasd_printf("NASD_EDRFS: refresh_inode: [ino=%ld] changed type!\n",
		inode->i_ino);
    goto out_changed;
  }

  if ((inode->i_mode & S_IFMT) != (edrfsattr->mode & S_IFMT)){
    nasd_printf("NASD_EDRFS: refresh_inode: [ino=%ld] changed type!\n",
		inode->i_ino);
    goto out_changed;
  }
  inode->i_mode = edrfsattr->mode;

  /* do the types match? */
  if ((S_ISDIR(inode->i_mode) && (edrfsattr->type != NASD_EDRFS_TYPE_DIR)) ||
      (S_ISREG(inode->i_mode) && (edrfsattr->type != NASD_EDRFS_TYPE_REG)) ||
      (S_ISLNK(inode->i_mode) && (edrfsattr->type != NASD_EDRFS_TYPE_LNK))) {
    nasd_printf("NASD_EDRFS: refresh_inode: [ino=%ld] type %d and mod %o don't match!\n",
		edrfsattr->type, inode->i_mode, inode->i_ino);
    goto out_changed;
  }

  inode->i_nlink  = edrfsattr->nlink;
  inode->i_uid    = edrfsattr->uid;
  inode->i_gid    = edrfsattr->gid;

  inode->i_blocks = nasd_attr->blocks_used;
  inode->i_atime  = nasd_attr->fs_attr_modify_time.ts_sec;
  inode->i_ctime  = nasd_attr->object_create_time.ts_sec;

  /* update the attr cache */
  cache_info->cached_since = jiffies;
  error = 0;

  if (inode->i_size != nasd_attr->object_len) {
#if DEBUG_REFRESH_INODE_DETAIL
    nasd_printf("  size change on [ino=%ld]\n", inode->i_ino);
#endif /* DEBUG_REFRESH_INODE_DETAIL */
    inode->i_size = nasd_attr->object_len;
    invalid = 1;
  }

  if (inode->i_mtime != nasd_attr->fs_object_modify_time.ts_sec) {
#if DEBUG_REFRESH_INODE_DETAIL
    nasd_printf("  mtime change on [ino=%ld]\n", inode->i_ino);
#endif /* DEBUG_REFRESH_INODE_DETAIL */
    invalid = 1;
  }

  inode->i_mtime = nasd_attr->fs_object_modify_time.ts_sec;

  if (invalid) {
#if DEBUG_REFRESH_INODE_DETAIL
    nasd_printf("  cached attributes are invalid\n");
#endif /* DEBUG_REFRESH_INODE_DETAIL */
    goto out_invalid;
  }

  /* update the timeout info */
  if (nasd_attr->fs_object_modify_time.ts_sec ==
      cache_info->cached_mtime) {
    /* make sure we don't go over the max timeout */

#if DEBUG_REFRESH_INODE_DETAIL
    nasd_printf("  cached attributes still valid, updating timeout from %d to %d\n",
		cache_info->cache_timeout,
		cache_info->cache_timeout << 1);
#endif /* DEBUG_REFRESH_INODE_DETAIL */

    if ((cache_info->cache_timeout <<= 1) >
	NASD_EDRFS_MAX_ATTR_TIMEOUT(inode)) {
      cache_info->cache_timeout = NASD_EDRFS_MAX_ATTR_TIMEOUT(inode);
    }
  }
  cache_info->cached_mtime = nasd_attr->fs_object_modify_time.ts_sec;

  cache_info->cache_valid = 1;

  return error;

out_changed:
  /* we're sad now, 'cause the inode is a different object */
  nasd_printf("NASD_EDRFS: refresh_inode: [ino=%ld] changed mode, %07o to %07o\n",
	      inode->i_ino, inode->i_mode, edrfsattr->mode);
  saved_mode = inode->i_mode;
  make_bad_inode(inode);
  inode->i_mode = saved_mode;

out_invalid:
  /* cached data for this file isn't valid anymore. how unfortunate. */

#if DEBUG_REFRESH_INODE_DETAIL
  nasd_printf("  invalidating %ld pages for [ino=%ld]\n",
	      inode->i_nrpages, inode->i_ino);
#endif /* DEBUG_REFRESH_INODE_DETAIL */

  invalidate_inode_pages(inode);

  /* the cached data was bad, so we don't trust this file anymore. */
  /* jiffies wrap around. that could make me very, very sad. sigh. */
  cache_info->cached_since  = 0;
  cache_info->cached_mtime  = 0;
  cache_info->cache_timeout = NASD_EDRFS_MIN_ATTR_TIMEOUT(inode);

out_noargs:
  return error;
}

struct inode *nasd_edrfs_ident_get(struct super_block      *sb,
				   nasd_edrfs_identifier_t *ident,
				   nasd_attribute_t        *nasd_attr,
				   nasd_edrfs_attributes_t *edrfsattr) {
  struct inode *inode = NULL;
  nasd_edrfs_inode_info_t *inode_info = NULL;
  int max_count;

  unsigned long ino;

  NASD_ASSERT(sb        != NULL);
  NASD_ASSERT(ident     != NULL);
  NASD_ASSERT(nasd_attr != NULL);

  ino = nasd_edrfs_ident_to_ino(ident);

#if DEBUG_IDENT_GET_DETAIL
  nasd_printf("nasd_edrfs_ident_get(0x%lx, 0x%lx, 0x%lx, 0x%lx) [0x%" NASD_ID_FMT "]\n",
	      (unsigned long) sb, (unsigned long) ident,
	      (unsigned long) nasd_attr, (unsigned long) edrfsattr,
	      ident->nasd_identifier);
#endif /* DEBUG_IDENT_GET_DETAIL */

 retry:
  inode = iget(sb, ino);

  /* did we get an inode? */
  if (!inode) {
    nasd_printf("EDRFS: ident_get: iget failed!\n");
    return NULL;
  }

  /* did something go horribly wrong? */
  if (inode->i_ino != ino) {
    nasd_printf("EDRFS: ident_get: expected ino %ld, got %ld. how odd.\n",
		ino, inode->i_ino);
    return inode;
  }

#if DEBUG_IDENT_GET_DETAIL
  nasd_printf("  got inode 0x%lx, ino=0x%lx, mode=%o, attrmode=%o type=%d\n",
	      inode, inode->i_ino, inode->i_mode,
	      edrfsattr->mode, edrfsattr->type);
#endif /* DEBUG_IDENT_GET_DETAIL */

  /* is this a busy inode? */
  max_count = (edrfsattr->mode == NASD_EDRFS_TYPE_DIR) ? 1 : edrfsattr->nlink;
  if (inode->i_count > max_count) {
    nasd_printf("EDRFS: busy inode %ld, i_count=%d, i_nlink=%d\n",
		inode->i_ino, inode->i_count, inode->i_nlink);
    nasd_edrfs_free_dentries(inode);

    /* still busy? */
    if (inode->i_count > max_count) {
      nasd_printf("EDRFS: inode %ld still busy, i_count=%d\n",
		  inode->i_ino, inode->i_count);

      make_bad_inode(inode);
      remove_inode_hash(inode);
    }

    /* try again. */
    iput(inode);
    goto retry;
  }

  /* now fill the inode */

  /* we only set the mode once. things aren't allowed to change types. */
  if (inode->i_mode == 0) {
#if DEBUG_IDENT_GET_DETAIL
    nasd_printf("  setting mode and attributes\n");
#endif /* DEBUG_IDENT_GET_DETAIL */

    inode->i_mode = edrfsattr->mode;
    
    switch (edrfsattr->type) {
    case NASD_EDRFS_TYPE_REG:
      /* regular file */
      if (!S_ISREG(inode->i_mode)) {
	nasd_printf("  encountered mode %o with type 0x%x, expected REG (0x%x)!\n",
		    inode->i_mode, edrfsattr->type, NASD_EDRFS_TYPE_REG);
	inode->i_mode |= S_IFREG; /* grr */
      }
      inode->i_op = &nasd_edrfs_inode_file_operations;
      break;
    case NASD_EDRFS_TYPE_DIR:
      /* directory */
      if (!S_ISDIR(inode->i_mode)) {
	nasd_printf("  encountered mode %o with type 0x%x, expected DIR (0x%x)!\n",
		    inode->i_mode, edrfsattr->type, NASD_EDRFS_TYPE_DIR);
	inode->i_mode |= S_IFDIR; /* grr */
      }
      inode->i_op = &nasd_edrfs_inode_dir_operations;
      break;
    case NASD_EDRFS_TYPE_LNK:
      /* symlink */
      if (!S_ISLNK(inode->i_mode)) {
	nasd_printf("  encountered mode %o with type 0x%x, expected LINK (0x%x)!\n",
		    inode->i_mode, edrfsattr->type, NASD_EDRFS_TYPE_LNK);
	inode->i_mode |= S_IFLNK; /* grr */
      }
      inode->i_op = &nasd_edrfs_inode_symlink_operations;
      break;
    default:
#if DEBUG_IDENT_GET_DETAIL
      nasd_printf("  encountered freakish type 0x%x!\n", edrfsattr->type);
#endif /* DEBUG_IDENT_GET_DETAIL */
      inode->i_mode |= S_IFREG;
      inode->i_op = &nasd_edrfs_inode_file_operations;
    }

    inode->i_size  = nasd_attr->object_len;
    inode->i_mtime = nasd_attr->fs_object_modify_time.ts_sec;
    

#if DEBUG_IDENT_GET_DETAIL
    nasd_printf("  set inode 0x%lx, ino=0x%lx, mode=%o\n",
		inode, inode->i_ino, inode->i_mode);
#endif /* DEBUG_IDENT_GET_DETAIL */

    inode->u.generic_ip = NULL;
    NASD_Malloc(inode->u.generic_ip, sizeof(nasd_edrfs_inode_info_t), (void *));
    if (inode->u.generic_ip == NULL) {
      nasd_printf("EDRFS: ident_get couldn't allocate memory!\n");
      iput(inode);
      return NULL;
    }

    inode_info = NASD_EDRFS_INODE_INFO(inode);

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

    inode_info->cache_info.cached_mtime  =
      nasd_attr->fs_object_modify_time.ts_sec;
    inode_info->cache_info.cache_timeout = NASD_EDRFS_MIN_ATTR_TIMEOUT(inode);

    nasd_edrfs_refresh_inode(inode, nasd_attr, edrfsattr);

    inode_info->cache_info.cache_valid = 1;

#if DEBUG_IDENT_GET_DETAIL
    nasd_printf("  refresh inode 0x%lx, ino=0x%lx, mode=%o\n",
		inode, inode->i_ino, inode->i_mode);
#endif /* DEBUG_IDENT_GET_DETAIL */
  }

  return inode;
}

int nasd_edrfs_convert_error(nasd_status_t       rc,
			     nasd_rpc_status_t   op_status) {
int error;

#if DEBUG_CONVERT_ERROR_DETAIL
  nasd_printf("nasd_edrfs_convert_error(0x%x, %d)\n", rc, op_status);
#endif /* DEBUG_CONVERT_ERROR_DETAIL */

  if (op_status != 0) {
    return -EREMOTEIO;
  }

  if (rc != NASD_SUCCESS) {
    switch (rc) {
    case NASD_BAD_DIGEST_REPLY:
    case NASD_BAD_DIGEST_REQ:
    case NASD_BAD_IDENTIFIER:
    case NASD_BAD_KEYTYPE:
    case NASD_BAD_NONCE:
    case NASD_BAD_NONCE_REPLY:
    case NASD_CAP_PROTECTION:
    case NASD_DRIVE_PROTECTION:
    case NASD_EXPIRED_CAPABILITY:
    case NASD_IMMUTABLE_KEY:
    case NASD_INVALID_STREAMID:
    case NASD_KEYINIT_FAILED:
    case NASD_PART_PROTECTION:
    case NASD_REQUEST_OUTSIDE_RANGE:
    case NASD_SECURITY_MISMATCH:
    case NASD_TIME_TOO_LATE:
    case NASD_UNSUPPORTED_PROTECTION:
    case NASD_WEAK_COOKIE:
    case NASD_WRONG_DRIVE:
    case NASD_WRONG_NI:
    case NASD_WRONG_PARTITION:        return -EACCES;

    case NASD_EDRFS_ALREADY_EXISTS:   return -EEXIST;

    case NASD_BAD_ATTR_SET:
    case NASD_BAD_FIELDMASK:
    case NASD_BAD_IO_LIST:
    case NASD_BAD_IO_LIST_LEN:
    case NASD_BAD_LEN:
    case NASD_BAD_OFFSET:
    case NASD_BAD_PARTITION:
    case NASD_BAD_TIMESPEC:
    case NASD_GUARD_CHECK_FAILED:     return -EINVAL;

    case NASD_FAIL:
    case NASD_OFFLINE:
    case NASD_SCHEDULE_FULL:
    case NASD_TRY_LOCK_BAD_IMPL:      return -EIO;

    case NASD_EDRFS_IS_DIR:           return -EISDIR;
    case NASD_NO_MORE_OBJECTS:        return -ENFILE;
    case NASD_EDRFS_BAD_NAME:         return -ENOENT;
    case NASD_NO_MEM:                 return -ENOMEM;

    case NASD_NO_FREE_IDS:
    case NASD_NO_SPACE:               return -ENOSPC;

    case NASD_OP_NOT_SUPPORTED:       return -ENOSYS;
    case NASD_EDRFS_NOT_DIR:          return -ENOTDIR;
    case NASD_EDRFS_DIR_NOT_EMPTY:    return -ENOTEMPTY;

    case NASD_AUTHCHECK_FAILED:
    case NASD_DRIVE_INITIALIZED:
    case NASD_DRIVE_UNINITIALIZED:
    case NASD_INSUFFICIENT_PERM:
    case NASD_NOT_ON_CONTROL:         return -EPERM;

    case NASD_BAD_TRANSFER_STATE:
    case NASD_PIPE_CB_FAIL:
    case NASD_REMOTE_TRAP:
    case NASD_RPC_FAILURE:
    case NASD_RPC_TRAP:               return -EREMOTEIO;

    case NASD_TIMEOUT:                return -ETIMEDOUT;

    default:
      nasd_printf("EDRFS: convert_error got unknown error 0x%x. alert the coast guard.\n", rc);
      return -EIO;
    }
  }

  /* surprisingly, nothing was wrong. */
  return 0;
}
