/*
 * nasd_readwrite.c
 *
 * IO operations for EDRFS.
 *
 * Author: Nat Lanza
 */
/*
 * 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 <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_IOBUF_TO_MEMLIST_DETAIL  0
#define DEBUG_READ_BASIC_DETAIL        0
#define DEBUG_READ_BASIC_DETAIL_EXTRA  0
#define DEBUG_READ_KIOVEC_DETAIL       0
#define DEBUG_WRITE_KIOVEC_DETAIL      0

int nasd_edrfs_iobuf_to_memlist(nasd_edrfs_kiobuf_data_t *iostuff) {
  int i, num_pages = 0, num_needed;
  unsigned long buf_remaining, buf_total = 0;
  struct kiobuf    *iobuf       = iostuff->iobuf;
  nasd_mem_list_t  *memlist     = iostuff->memlist;
  nasd_mem_list_t  *cur_memlist = NULL;

  num_pages = iobuf->nr_pages;
  buf_remaining = buf_total = iobuf->length;

  /* let's handle the easy case first. */
  if (num_pages <= NASD_EDRFS_MAX_EMBEDDED_MEMLIST) {
    
    if (iobuf->offset != 0) {
      memlist[0].addr = (void *) (iobuf->pagelist[0] + iobuf->offset);
      memlist[0].len  = PAGE_SIZE - iobuf->offset;
    } else {
      memlist[0].addr = (void *) iostuff->iobuf->pagelist[0];
      memlist[0].len  = PAGE_SIZE;
    }

    buf_remaining     -= memlist[0].len;
    memlist[0].stride  = 0;
    memlist[0].nelem   = 1;

    for (i = 1; i < num_pages; i++) {
      memlist[i].addr = (void *) iobuf->pagelist[i];
      memlist[i].stride = 0;
      memlist[i].nelem  = 1;

      /* if we're at the end, make sure we don't overextend the memlist */
      if (buf_remaining > PAGE_SIZE) {
	memlist[i].len   = PAGE_SIZE;
	memlist[i].next  = &(memlist[i+1]);
      } else {
	memlist[i].len   = buf_remaining;
	memlist[i].next  = NULL; /* null-terminate it */
      }
      
      buf_remaining   -= memlist[i].len;
    }

  } else { /* harder */

    /* how many do we need to allocate? */
    num_needed = num_pages - NASD_EDRFS_MAX_EMBEDDED_MEMLIST;
    NASD_ASSERT(num_needed > 0);

    if (iostuff->num_alloc != 0) { /* free the existing buffer */
      NASD_Free(iostuff->memlist_alloc,
		iostuff->num_alloc * sizeof(nasd_mem_list_t));
    }
    
    /* now grab the new buffer */
    NASD_Malloc(iostuff->memlist_alloc, sizeof(nasd_mem_list_t) * num_needed,
		(nasd_mem_list_t *));
    if (iostuff->memlist_alloc == NULL) {
      iostuff->num_alloc = 0;
      return NASD_NO_MEM;
    }

    iostuff->num_alloc = num_needed;

    /* thread the static entries */
    for (i = 0; i < (NASD_EDRFS_MAX_EMBEDDED_MEMLIST - 1); i++) {
      memlist[i].next = &(memlist[i + 1]);
    }

    /* tack on the allocated ones */
    memlist[NASD_EDRFS_MAX_EMBEDDED_MEMLIST - 1].next = iostuff->memlist_alloc;

    /* thread the allocated entries */
    for (i = 1; i < num_needed; i++) {
      iostuff->memlist_alloc[i - 1].next = &(iostuff->memlist_alloc[i]);
    }

    iostuff->memlist_alloc[num_needed - 1].next = NULL;

    /* now run through the iobuf and copy the page addresses we care about */
    cur_memlist   = memlist;
    buf_remaining = buf_total;

    /* handle the first page in an iobuf specially 'cause of the offset */
    if (iobuf->offset != 0) {
      cur_memlist->addr   = (void *) (iobuf->pagelist[0] + iobuf->offset);
      cur_memlist->len  = PAGE_SIZE - iobuf->offset;
    } else {
      cur_memlist->addr = (void *) iostuff->iobuf->pagelist[0];
      cur_memlist->len  = PAGE_SIZE;
    }

    buf_remaining      -= cur_memlist->len;
    cur_memlist->stride = 0;
    cur_memlist->nelem  = 1;
    cur_memlist         = cur_memlist->next;
    
    for (i = 1; i < iobuf->nr_pages; i++) {
      cur_memlist->addr   = (void *) iobuf->pagelist[i];
      cur_memlist->stride = 0;
      cur_memlist->nelem  = 1;
      
      /* if we're at the end, make sure we don't overextend the memlist */
      if (buf_remaining > PAGE_SIZE) { cur_memlist->len = PAGE_SIZE;     }
      else                           { cur_memlist->len = buf_remaining; }

      buf_remaining -= cur_memlist->len;
      cur_memlist    = cur_memlist->next;
    }

    /* the memlist had better be null-terminated. */
    NASD_ASSERT(cur_memlist == NULL);
  }

  NASD_ASSERT(buf_remaining == 0);
  return NASD_SUCCESS;
}


nasd_status_t nasd_edrfs_read_basic(struct inode  *inode,
				    void          *buf,
				    int            buffer_space,
				    nasd_len_t     in_len,
				    nasd_offset_t  offset,
				    nasd_len_t    *out_len) {
  nasd_edrfs_inode_info_t *inode_info;
  nasd_edrfs_sb_info_t    *sb_info;
  
  nasd_edrfs_credential_t  in_credential;
  nasd_error_string_t      err_str;
  nasd_rpc_status_t        op_status;
  nasd_cookie_t            cookie;
  nasd_status_t            rc;
  char                    *my_buf = NULL;
  
  nasd_security_param_t    sp;
  nasd_p_smpl_op_dr_args_t read_args;
  nasd_p_fastread_dr_res_t read_res;
  
  NASD_ASSERT(inode   != NULL);
  NASD_ASSERT(buf     != NULL);
  NASD_ASSERT(out_len != NULL);
  
  inode_info = NASD_EDRFS_INODE_INFO(inode);
  sb_info = NASD_EDRFS_SUPER_SBINFO(inode->i_sb);
  
#if DEBUG_READ_BASIC_DETAIL
  nasd_printf("nasd_edrfs_read_basic asked to read %Lu bytes at offset %ld from nasdid %" NASD_ID_FMT "\n",
	      in_len, offset, inode_info->ident.nasd_identifier);
#endif /* DEBUG_READ_BASIC_DETAIL */ 
  
  if (buffer_space == NASD_EDRFS_BUFFER_USER) {
    /* buf is userspace, so get a kernel buffer to read into */
    NASD_Malloc(my_buf, in_len, (char *));
    
    if (my_buf == NULL) {
      nasd_printf("NASD-EDRFS: read_basic couldn't get memory!\n");
      rc = NASD_NO_MEM;
      goto out_nomem;
    }
  } else {
    my_buf = buf;
  }
  
  /* set up RPC */
  
  /* bail if our drive handle is bad. */
  if (!(sb_info->handles.flags & NASD_BINDING_DRIVE_VALID)) {
    rc = NASD_BAD_HANDLE;
    goto out;
  }
  
  in_credential.uid = current->uid;
  in_credential.gid = current->gid;
  
  /* get a cookie */
  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &in_credential, &cookie);
  if (rc != NASD_SUCCESS) { goto out; }
  

  read_args.in_identifier      = inode_info->ident.nasd_identifier;
  read_args.in_partnum         = inode_info->ident.partnum;
  read_args.in_offset          = offset;
  read_args.in_len             = in_len;

  SETUP_SECURITY_PARAM(sp, cookie.capability);

  nasd_cl_p_read_simple_dr(sb_info->handles.drive_handle,
			   cookie.key, &sp, &cookie.capability,
			   &read_args,
			   my_buf, &read_res, &op_status);
  rc = read_res.nasd_status;
  *out_len = read_res.out_datalen;

#if DEBUG_READ_BASIC_DETAIL
  nasd_printf("  read_simple returned, read %d bytes\n", *out_len);
#endif /* DEBUG_READ_BASIC_DETAIL */

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    nasd_printf("NASD-EDRFS: read_basic: 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));
    goto out;
  }

  /* sigh */
  if (buffer_space == NASD_EDRFS_BUFFER_USER) {
    copy_to_user(buf, my_buf, read_res.out_datalen);
  }

 out:
  if (buffer_space == NASD_EDRFS_BUFFER_USER) {
    NASD_Free(my_buf, in_len);
  }
 out_nomem:
  return rc; 
}          


nasd_status_t nasd_edrfs_write_basic(struct inode  *inode,
				     void          *buf,
				     int            buffer_space,
				     nasd_len_t     in_len,
				     nasd_offset_t  offset,
				     nasd_len_t    *out_len) {
  nasd_edrfs_inode_info_t *inode_info;
  nasd_edrfs_sb_info_t    *sb_info;
  
  nasd_edrfs_credential_t  in_credential;
  nasd_error_string_t      err_str;
  nasd_rpc_status_t        op_status;
  nasd_cookie_t            cookie;
  nasd_status_t            rc;
  char                    *my_buf = NULL;

  nasd_security_param_t	sp;
  nasd_p_smpl_op_dr_args_t write_args;
  nasd_p_fastwrite_dr_res_t write_res;

  NASD_ASSERT(inode   != NULL);
  NASD_ASSERT(buf     != NULL);
  NASD_ASSERT(out_len != NULL);

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

#if DEBUG_WRITE_BASIC_DETAIL
  nasd_printf("nasd_edrfs_write(0x%lx, 0x%lx, %Lu, %Lu, 0x%lx) [ino=%" NASD_ID_FMT " %Lu@%Lu]\n",
	      (unsigned long) inode, (unsigned long) buf, in_len, offset,
	      (unsigned long) out_len,
	      inode_info->ident.nasd_identifier, in_len, offset);
#endif /* DEBUG_WRITE_BASIC_DETAIL */ 

  if (buffer_space == NASD_EDRFS_BUFFER_USER) {
    NASD_Malloc(my_buf, in_len, (char *));

    if (my_buf == NULL) {
      nasd_printf("NASD-EDRFS: read_basic couldn't get memory!\n");
      rc = NASD_NO_MEM;
      goto out_nomem;
    }

    copy_from_user(my_buf, buf, in_len);

  } else {
    my_buf = buf;
  }
     

  /* set up RPC */

  /* bail if our drive handle is bad. */
  if (!(sb_info->handles.flags & NASD_BINDING_DRIVE_VALID)) {
    rc = NASD_BAD_HANDLE;
    goto out;
  }

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

  /* get a cookie */
  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &in_credential, &cookie);
  if (rc != NASD_SUCCESS) { goto out; }

  write_args.in_identifier      = inode_info->ident.nasd_identifier;
  write_args.in_partnum         = inode_info->ident.partnum;
  write_args.in_offset          = offset;
  write_args.in_len             = in_len;

  SETUP_SECURITY_PARAM(sp, cookie.capability);

  nasd_cl_p_write_simple_dr(sb_info->handles.drive_handle,
			    cookie.key, &sp, &cookie.capability,
			    &write_args,
			    my_buf, &write_res, &op_status);
  rc = write_res.nasd_status;
  *out_len = write_res.out_datalen;

  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    nasd_printf("NASD-EDRFS: write_basic: 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));
  }
  
 out:  
  if (buffer_space == NASD_EDRFS_BUFFER_USER) {
    NASD_Free(my_buf, in_len);
  }  
 out_nomem:
  return rc;
}


nasd_status_t nasd_edrfs_read_cached(struct inode  *inode,
				     void          *buf,
				     int            buffer_space,
				     nasd_len_t     in_len,
				     nasd_offset_t  offset,
				     nasd_len_t    *out_len) {
  nasd_edrfs_inode_info_t *inode_info;
  nasd_edrfs_sb_info_t    *sb_info;
  
  nasd_offset_t            start_page, end_page, cur_page, cur_offset;
  nasd_len_t               effective_len, effective_read, left;
  int                      i, page_offset, nr;
  unsigned long            pageaddr;

  nasd_edrfs_credential_t  in_credential;
  nasd_error_string_t      err_str;
  nasd_rpc_status_t        op_status;
  nasd_cookie_t            cookie;
  nasd_status_t            rc = NASD_SUCCESS;
  
  nasd_security_param_t    sp;
  nasd_p_smpl_op_dr_args_t read_args;
  nasd_p_fastread_dr_res_t read_res;

  nasd_mem_list_t memlist[16];
  
  NASD_ASSERT(inode   != NULL);
  NASD_ASSERT(buf     != NULL);
  NASD_ASSERT(out_len != NULL);

#if DEBUG_READ_CACHED_DETAIL
  nasd_printf("nasd_edrfs_read_cached reading %Lu@%ld from nid %"
	      NASD_ID_FMT "\n", in_len, offset,
	      inode_info->ident.nasd_identifier);
#endif /* DEBUG_READ_CACHED_DETAIL */ 
  
  start_page    =  offset           & PAGE_MASK;
  end_page      = (offset + in_len) & PAGE_MASK;
  
  if ((end_page - start_page) > (PAGE_MASK*15)) { 
    end_page    = start_page + (PAGE_MASK*15);
    in_len      = (end_page + PAGE_SIZE) - offset;
  }

  effective_len = (end_page + PAGE_SIZE) - start_page;

  /* get cached pages, trimming from the front */
  while ((start_page <= end_page) &&
	 (pageaddr = get_cached_page(inode, start_page, 0))) {
    start_page    += PAGE_SIZE;
    effective_len -= PAGE_SIZE;
    put_cached_page(pageaddr);
  }
  if (effective_len == 0) { goto got_pages; } /* wow, we had it all cached. */
    
  /* now trim from the end */
  while ((end_page >= start_page) &&
	 (pageaddr = get_cached_page(inode, end_page, 0))) {
    end_page      -= PAGE_SIZE;
    effective_len -= PAGE_SIZE;
    put_cached_page(pageaddr);
  }
  if (effective_len == 0) { goto got_pages; } /* wow, we had it all cached. */
    
  /* if we're here, there's some chunk of pages in the center of our
     little region that we don't have cached. start_page should be the
     first page in it, and end_page should be the last. now we fill a
     mem_list and do our little IO. */
  
  for (cur_page = start_page, i = 0; cur_page <= end_page;
       cur_page += PAGE_SIZE, i++) {
    memlist[i].len    = PAGE_SIZE;
    memlist[i].nelem  = 1;
    memlist[i].stride = 0;

    memlist[i].addr   = (void *) get_cached_page(inode, cur_page, 1);
    if (memlist[i].addr == NULL) {
      end_page = cur_page; 
      rc = NASD_NO_MEM;
      goto out_bad;
    }
    
    if (cur_page < end_page) { memlist[i].next = &(memlist[i+1]); }
    else                     { memlist[i].next = NULL;            }
  }
  
  rc = nasd_edrfs_read_memlist(inode, memlist, effective_len,
			       start_page, &effective_read);
  if (rc != NASD_SUCCESS) { goto out_bad; }

  /* pharaoh! let my cache pages go! */
  for (cur_page = start_page, i = 0; cur_page <= end_page;
       cur_page += PAGE_SIZE, i++) {
    put_cached_page((unsigned long) memlist[i].addr);
  }

  if (effective_read < effective_len) {
    /* hmmm. we need to figure out whether we got everything the
       user process asked for */
    if ((start_page + effective_read) < (offset + in_len)) {
      /* the last byte read is within the range the user wanted. */
      in_len -= ((offset + in_len) - (start_page + effective_read));
    }
  }

 got_pages:
  /* here we're pretty sure we have everything in the cache, so
     now it's time to copy to userspace. */

  left = in_len;
  cur_offset = offset;
  while (left) {
    cur_page    = cur_offset & PAGE_CACHE_MASK;  /* current cache page    */
    page_offset = cur_offset & ~PAGE_CACHE_MASK; /* offset in the page    */
    nr          = PAGE_CACHE_SIZE - page_offset; /* bytes to read in page */
    if (nr > (inode->i_size - offset)) { nr = inode->i_size - offset; }
    if (nr > left) { nr = left; }

    pageaddr = get_cached_page(inode, cur_page, 0);
    __copy_to_user(buf, (const char *) (pageaddr + page_offset), nr);
    put_cached_page(pageaddr);

    cur_offset += nr;
    left -= nr;
  }

 out_good:
  if (in_len <= 0) { *out_len = 0; } else { *out_len = in_len; }
  return rc;

 out_bad:
  /* error recovery is hard. let's go shopping. */
  for (cur_page = start_page, i = 0; cur_page <= end_page;
       cur_page += PAGE_SIZE, i++) {
    put_cached_page((unsigned long) memlist[i].addr);
  }
  return rc;
}


/* note: assumes that the addresses given in the memlist are in kernelspace. */
nasd_status_t nasd_edrfs_read_memlist(struct inode     *inode,
				      nasd_mem_list_t  *memlist,
				      nasd_len_t        in_len,
				      nasd_offset_t     offset,
				      nasd_len_t       *out_len) {
  nasd_edrfs_inode_info_t *inode_info;
  nasd_edrfs_sb_info_t    *sb_info;
  
  nasd_edrfs_credential_t  in_credential;
  nasd_error_string_t      err_str;
  nasd_rpc_status_t        op_status;
  nasd_cookie_t            cookie;
  nasd_status_t            rc = NASD_SUCCESS;
  
  nasd_security_param_t    sp;
  nasd_p_smpl_op_dr_args_t read_args;
  nasd_p_fastread_dr_res_t read_res;
  
  NASD_ASSERT(inode   != NULL);
  NASD_ASSERT(memlist != NULL);
  NASD_ASSERT(out_len != NULL);
  
  inode_info = NASD_EDRFS_INODE_INFO(inode);
  sb_info = NASD_EDRFS_SUPER_SBINFO(inode->i_sb);
  
#if DEBUG_READ_MEMLIST_DETAIL
  nasd_printf("nasd_edrfs_read_memlist asked to read %Lu bytes at offset %ld from nasdid %" NASD_ID_FMT "\n",
	      in_len, offset, inode_info->ident.nasd_identifier);
#endif /* DEBUG_READ_MEMLIST_DETAIL */ 
    
  /* bail if our drive handle is bad. */
  if (!(sb_info->handles.flags & NASD_BINDING_DRIVE_VALID)) {
    rc = NASD_BAD_HANDLE;
    return rc;
  }
  
  in_credential.uid = current->uid;
  in_credential.gid = current->gid;
  
  /* get a cookie */
  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &in_credential, &cookie);
  if (rc != NASD_SUCCESS) { return rc; }
  
  read_args.in_identifier      = inode_info->ident.nasd_identifier;
  read_args.in_partnum         = inode_info->ident.partnum;
  read_args.in_offset          = offset;
  read_args.in_len             = in_len;

  SETUP_SECURITY_PARAM(sp, cookie.capability);
  nasd_cl_p_range_read_dr(sb_info->handles.drive_handle,
			  cookie.key, &sp, &cookie.capability,
			  &read_args, memlist, &read_res, &op_status);
  rc = read_res.nasd_status;
  *out_len = read_res.out_datalen;
    
  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    nasd_printf("NASD-EDRFS: read_memlist: 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));
  }

  return rc; 
}


nasd_status_t nasd_edrfs_read_kiovec(struct inode   *inode,
				     void           *buf,
				     nasd_len_t      in_len,
				     nasd_offset_t   offset,
				     nasd_len_t     *out_len) {
  nasd_edrfs_inode_info_t  *inode_info;
  nasd_edrfs_sb_info_t     *sb_info;
  
  nasd_edrfs_credential_t   in_credential;
  nasd_error_string_t       err_str;
  nasd_rpc_status_t         op_status;
  nasd_cookie_t             cookie;
  nasd_status_t             rc = NASD_SUCCESS;

  nasd_edrfs_kiobuf_data_t *iostuff = NULL;
  int                       error;

  nasd_security_param_t     sp;
  nasd_p_smpl_op_dr_args_t  read_args;
  nasd_p_fastread_dr_res_t  read_res;

  NASD_ASSERT(inode   != NULL);
  NASD_ASSERT(buf     != NULL);
  NASD_ASSERT(out_len != NULL);

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



#if DEBUG_READ_KIOVEC_DETAIL
  nasd_printf("kiovec read of object 0x%" NASD_ID_FMT ", %ld bytes at offset %ld\n",
	      inode_info->ident.nasd_identifier, in_len, offset);
#endif /* DEBUG_READ_KIOVEC_DETAIL */

  /* bail if our drive handle is bad. */
  if (!(sb_info->handles.flags & NASD_BINDING_DRIVE_VALID)) {
    rc = NASD_BAD_HANDLE;
    goto out;
  }

  in_credential.uid = current->uid;
  in_credential.gid = current->gid;
  
  /* get a cookie */
  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &in_credential, &cookie);
  if (rc != NASD_SUCCESS) { goto out; }

  read_args.in_identifier = inode_info->ident.nasd_identifier;
  read_args.in_partnum    = inode_info->ident.partnum;
  read_args.in_offset     = offset;
  read_args.in_len        = in_len;

  /* set up the memlist */

  iostuff = nasd_edrfs_get_kiobuf_data();
  if (iostuff == NULL) { rc = NASD_NO_MEM; goto out; }

  error = map_user_kiobuf(READ, iostuff->iobuf, (unsigned long) buf, in_len);
  if (error) { rc = NASD_FAIL; goto out_cleanup; }

  rc = nasd_edrfs_iobuf_to_memlist(iostuff);
  if (rc != NASD_SUCCESS) { goto out_cleanup_iobuf; }

  /* do a range read since we have a memlist */
  SETUP_SECURITY_PARAM(sp, cookie.capability);
  nasd_cl_p_range_read_dr(sb_info->handles.drive_handle,
			  cookie.key, &sp, &cookie.capability,
			  &read_args, iostuff->memlist, &read_res, &op_status);
  rc = read_res.nasd_status;
  *out_len = read_res.out_datalen;
  
  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    nasd_printf("NASD-EDRFS: read_kiovec: 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));
  }
  
 out_cleanup_iobuf:
  unmap_kiobuf(iostuff->iobuf);
 out_cleanup:
  nasd_edrfs_free_kiobuf_data(iostuff);
 out:
  return rc;
}


nasd_status_t nasd_edrfs_write_kiovec(struct inode   *inode,
				      void           *buf,
				      nasd_len_t      in_len,
				      nasd_offset_t   offset,
				      nasd_len_t     *out_len) {
  nasd_edrfs_inode_info_t  *inode_info;
  nasd_edrfs_sb_info_t     *sb_info;
  
  nasd_edrfs_credential_t   in_credential;
  nasd_error_string_t       err_str;
  nasd_rpc_status_t         op_status;
  nasd_cookie_t             cookie;
  nasd_status_t             rc = NASD_SUCCESS;

  nasd_mem_list_t          *memlist_tmp;
  nasd_edrfs_kiobuf_data_t *iostuff = NULL;
  int                       error;

  nasd_security_param_t     sp;
  nasd_p_smpl_op_dr_args_t  write_args;
  nasd_p_fastwrite_dr_res_t write_res;

  NASD_ASSERT(inode   != NULL);
  NASD_ASSERT(buf   != NULL);
  NASD_ASSERT(out_len != NULL);

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

#if DEBUG_WRITE_KIOVEC_DETAIL
  nasd_printf("kiovec write of object 0x%" NASD_ID_FMT ", %ld bytes at offset %ld\n",
	      inode_info->ident.nasd_identifier, in_len, offset);
#endif /* DEBUG_WRITE_KIOVEC_DETAIL */

  /* there's no point in continuing if our drive handle is bad. */
  if (!(sb_info->handles.flags & NASD_BINDING_DRIVE_VALID)) {
    rc = NASD_BAD_HANDLE;
    goto out;
  }

  in_credential.uid = current->uid;
  in_credential.gid = current->gid;
  
  /* get a cookie */
  rc = nasd_edrfs_find_or_get_cookie(&sb_info->handles, &inode_info->ident,
				     &in_credential, &cookie);
  if (rc != NASD_SUCCESS) { goto out; }

  write_args.in_identifier      = inode_info->ident.nasd_identifier;
  write_args.in_partnum         = inode_info->ident.partnum;
  write_args.in_offset          = offset;
  write_args.in_len             = in_len;

  /* set up the memlist */
  
  iostuff = nasd_edrfs_get_kiobuf_data();
  if (iostuff == NULL) { rc = NASD_NO_MEM; goto out; }

  error = map_user_kiobuf(WRITE, iostuff->iobuf, (unsigned long) buf, in_len);
  if (error) { rc = NASD_FAIL; goto out_cleanup; }

  rc = nasd_edrfs_iobuf_to_memlist(iostuff);
  if (rc != NASD_SUCCESS) { goto out_cleanup_iobuf; }

  /* do a range write since we have a memlist */
  SETUP_SECURITY_PARAM(sp, cookie.capability);

#if DEBUG_WRITE_KIOVEC_DETAIL
  memlist_tmp = iostuff->memlist;

  while (memlist_tmp) {
    nasd_printf("memlist element: addr=0x%x, len=%d, stride=%d, nelem=%d\n",
		memlist_tmp->addr, memlist_tmp->len, memlist_tmp->stride,
		memlist_tmp->nelem);
    memlist_tmp = memlist_tmp->next;
  }

  nasd_printf("write_kiovec: going remote\n");
#endif /* DEBUG_WRITE_KIOVEC_DETAIL */

  nasd_cl_p_range_write_dr(sb_info->handles.drive_handle,
			   cookie.key, &sp, &cookie.capability,
			   &write_args, iostuff->memlist,
			   &write_res, &op_status);

  rc = write_res.nasd_status;
  *out_len = write_res.out_datalen;
  
  if ((rc != NASD_SUCCESS) || (op_status != 0)) {
    nasd_printf("NASD-EDRFS: write_kiovec: 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));
  }
  
 out_cleanup_iobuf:
  unmap_kiobuf(iostuff->iobuf);
 out_cleanup:
  nasd_edrfs_free_kiobuf_data(iostuff);
 out:
  return rc;
}
