/*
 * nasd_file.c
 *
 * File 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 <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>

#include <linux/iobuf.h>

#define DEBUG_READ_FILE_DETAIL   0
#define DEBUG_WRITE_FILE_DETAIL  0
#define DEBUG_MMAP_DETAIL        0
#define DEBUG_CLOSE_FILE_DETAIL  0
#define DEBUG_FLUSH_FILE_DETAIL  0
#define DEBUG_FSYNC_FILE_DETAIL  0
#define DEBUG_READPAGE_DETAIL    0
#define DEBUG_WRITEPAGE_DETAIL   0
#define DEBUG_UPDATEPAGE_DETAIL  0
#define DEBUG_REVALIDATE_DETAIL  0
#define DEBUG_READLINK_DETAIL    0
#define DEBUG_FOLLOW_LINK_DETAIL 0

/* right now this is subtly broken */
#define DO_KIOVEC_READ 0

/* file operations */

static int     nasd_edrfs_mmap       (struct file            *file,
				      struct vm_area_struct  *vma);

static ssize_t nasd_edrfs_read_file  (struct file            *file,
				      char                   *buf,
				      size_t                  count,
				      loff_t                 *ppos);

static ssize_t nasd_edrfs_write_file (struct file            *file,
				      const char             *buf,
				      size_t                  count,
				      loff_t                 *ppos);

static int     nasd_edrfs_flush_file (struct file            *file);

static int     nasd_edrfs_close_file (struct inode           *inode,
				      struct file            *file);

static int     nasd_edrfs_fsync_file (struct file            *file,
				      struct dentry          *dentry);


static struct file_operations nasd_edrfs_file_operations = {
  NULL,                    /* llseek             */
  nasd_edrfs_read_file,    /* read               */
  nasd_edrfs_write_file,   /* write              */
  NULL,                    /* readdir            */
  NULL,                    /* poll               */
  nasd_edrfs_ioctl,        /* ioctl              */
  nasd_edrfs_mmap,         /* mmap               */
  NULL,                    /* open               */
  nasd_edrfs_flush_file,   /* flush              */
  nasd_edrfs_close_file,   /* release            */
  nasd_edrfs_fsync_file,   /* fsync              */
  NULL,                    /* fasync             */
  NULL,                    /* check_media_change */
  NULL,                    /* revalidate         */
  NULL                     /* lock (not yet)     */
};


/* file inode operations */

int nasd_edrfs_readpage (struct file  *file,
			 struct page  *page);

int nasd_edrfs_writepage (struct file  *file,
			  struct page  *page);

int nasd_edrfs_updatepage (struct file    *file,
			   struct page    *page,
			   unsigned long   offset,
			   unsigned int    count,
			   int             sync);

int nasd_edrfs_revalidate (struct dentry  *dentry);


struct inode_operations nasd_edrfs_inode_file_operations = {
  &nasd_edrfs_file_operations,  /* file ops    */
  NULL,                         /* create      */
  NULL,                         /* lookup      */
  NULL,                         /* link        */
  NULL,                         /* unlink      */
  NULL,                         /* symlink     */
  NULL,                         /* mkdir       */
  NULL,                         /* rmdir       */
  NULL,                         /* mknod       */
  NULL,                         /* rename      */
  NULL,                         /* readlink    */
  NULL,                         /* follow_link */
  nasd_edrfs_readpage,          /* readpage    */
  nasd_edrfs_writepage,         /* writepage   */
  NULL,                         /* bmap        */
  NULL,                         /* truncate    */
  NULL,                         /* permission  */
  NULL,                         /* smap        */
  nasd_edrfs_updatepage,        /* updatepage  */
  nasd_edrfs_revalidate         /* revalidate  */
};


/* symlink inode operations */
static int nasd_edrfs_readlink(struct dentry  *dentry,
			       char           *buffer,
			       int             buflen);

static struct dentry *nasd_edrfs_follow_link(struct dentry  *dentry,
					     struct dentry  *base,
					     unsigned int    follow);


struct inode_operations nasd_edrfs_inode_symlink_operations = {
  NULL,                    /* file ops    */
  NULL,                    /* create      */
  NULL,                    /* lookup      */
  NULL,                    /* link        */
  NULL,                    /* unlink      */
  NULL,                    /* symlink     */
  NULL,                    /* mkdir       */
  NULL,                    /* rmdir       */
  NULL,                    /* mknod       */
  NULL,                    /* rename      */
  nasd_edrfs_readlink,     /* readlink    */
  nasd_edrfs_follow_link,  /* follow_link */
  NULL,                    /* readpage    */
  NULL,                    /* writepage   */
  NULL,                    /* bmap        */
  NULL,                    /* truncate    */
  NULL,                    /* permission  */
  NULL,                    /* smap        */
  NULL,                    /* updatepage  */
  NULL                     /* revalidate  */
};

/* file operations */

static ssize_t nasd_edrfs_read_file(struct file  *file,
				    char         *buf,
				    size_t        count,
				    loff_t       *ppos) {
  struct dentry  *dentry;
  struct inode   *inode;
  ssize_t         result;
  size_t          page_cache = 0;
  loff_t          pos = *ppos;
  nasd_len_t      bytes_read;
  nasd_status_t   rc;

  struct page    *page;
  struct page   **hash;

 start: /* sometimes we need to start over. sigh. */

  NASD_ASSERT(file != NULL);
  NASD_ASSERT(buf  != NULL);
  NASD_ASSERT(ppos != NULL);

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

#if DEBUG_READ_FILE_DETAIL
  nasd_printf("nasd_edrfs_read_file(0x%lx, 0x%lx, %d, 0x%lx) ('%s' [ino 0x%lx] in '%s' [ino 0x%lx], %ld@%ld)\n",
	      (unsigned long) file, (unsigned long) buf, count,
	      (unsigned long) ppos,
	      dentry->d_name.name, dentry->d_inode->i_ino,
	      dentry->d_parent->d_name.name, dentry->d_parent->d_inode->i_ino,
	      (unsigned long) count, (unsigned long) *ppos);
#endif /* DEBUG_READ_FILE_DETAIL */

  /* is this file still valid? */
  result = nasd_edrfs_revalidate(dentry);
  if (result) { goto out; }

  if (count == 0) {
    result = 0;
    goto out;
  }

  if (count <= (PAGE_SIZE << 4)) {
    /* yes, it's a cheap hack. I know. but kiovec reads have a high
       overhead for small IOs, so this is probably faster. who knows? */
    rc = nasd_edrfs_read_basic(inode, (void *) buf,
			       NASD_EDRFS_BUFFER_USER,
			       (nasd_uint64) count,
			       (nasd_uint64) pos,
			       &bytes_read);
  } else {
    rc = nasd_edrfs_read_kiovec(inode, (void *) buf,
				(nasd_uint64) count,
				(nasd_uint64) pos,
				&bytes_read);
  }
  if (rc != NASD_SUCCESS) {
#if DEBUG_READ_FILE_DETAIL
    nasd_printf("EDRFS: read went bad somewhere: rc=0x%x (%s)\n",
		rc, nasd_error_string(rc));
#endif /* DEBUG_READ_FILE_DETAIL */
    result = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }

  result = (ssize_t) bytes_read;
  *ppos += result;
  
out:
#if DEBUG_READ_FILE_DETAIL
  nasd_printf("  read_file returning %d\n", result);
#endif /* DEBUG_READ_FILE_DETAIL */
  return result;
}


static ssize_t nasd_edrfs_write_file(struct file  *file,
				     const  char  *buf,
				     size_t        count,
				     loff_t       *ppos) {
  struct dentry  *dentry;
  ssize_t         result;
  unsigned long   limit = current->rlim[RLIMIT_FSIZE].rlim_cur;
  int             sync;
  loff_t          pos = *ppos;
  nasd_len_t      bytes_written;
  nasd_status_t   rc;

  NASD_ASSERT(file != NULL);
  NASD_ASSERT(buf  != NULL);
  NASD_ASSERT(ppos != NULL);

  dentry = file->f_dentry;
  
#if DEBUG_WRITE_FILE_DETAIL
  nasd_printf("nasd_edrfs_write_file(0x%lx, 0x%lx, %d, 0x%lx) ('%s' [ino 0x%lx] in '%s' [ino 0x%lx], %ld@%ld)\n",
	      (unsigned long) file, (unsigned long) buf, count,
	      (unsigned long) ppos,
	      dentry->d_name.name, dentry->d_inode->i_ino,
	      dentry->d_parent->d_name.name, dentry->d_parent->d_inode->i_ino,
	      (unsigned long) count, (unsigned long) *ppos);
#endif /* DEBUG_WRITE_FILE_DETAIL */

  if (file->f_error) {
    result = file->f_error;
    file->f_error = 0;
    goto out;
  }

  if (file->f_flags & O_APPEND) {
    pos = dentry->d_inode->i_size;
#if DEBUG_WRITE_FILE_DETAIL
    nasd_printf("file wants us to append, setting pos to %d\n", pos);
#endif /* DEBUG_WRITE_FILE_DETAIL */
  }

  sync = file->f_flags & O_SYNC;

  if (pos >= limit) {
    /* I can't believe the VFS doesn't check for this at
       some higher level. */
#if DEBUG_WRITE_FILE_DETAIL
    nasd_printf("can't write that far into a file!\n");
#endif /* DEBUG_WRITE_FILE_DETAIL */
    result = -EFBIG;
    send_sig(SIGXFSZ, current, 0);
    goto out;
  }

  if (count > (limit - pos)) {
#if DEBUG_WRITE_FILE_DETAIL
    nasd_printf("write would make file too large, truncating!\n");
#endif /* DEBUG_WRITE_FILE_DETAIL */    
    send_sig(SIGXFSZ, current, 0);
    count = limit - pos;
  }

  /* make sure we have a good dentry to work with */
  result = nasd_edrfs_revalidate(dentry);
  if (result) {
#if DEBUG_WRITE_FILE_DETAIL
    nasd_printf("revalidate returned non-zero: %d\n", result);
#endif /* DEBUG_WRITE_FILE_DETAIL */
    goto out;
  }

  if (count <= (PAGE_SIZE << 4)) {
    /* yes, it's a cheap hack. I know. but kiovec writes have a high
       overhead for small IOs, so this is probably faster. who knows? */
    rc = nasd_edrfs_write_basic(dentry->d_inode, (void *) buf,
				NASD_EDRFS_BUFFER_USER,
				(nasd_uint64) count,
				(nasd_uint64) pos,
				&bytes_written);
  } else {
    rc = nasd_edrfs_write_kiovec(dentry->d_inode, (void *) buf,
				 (nasd_uint64) count,
				 (nasd_uint64) pos,
				 &bytes_written);
  }

  if (rc != NASD_SUCCESS) {
    nasd_printf("EDRFS: write_file went bad: 0x%lx (%s)\n",
		rc, nasd_error_string(rc));
    result = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }
  
  result = (ssize_t) bytes_written;
  *ppos += result;

  /* have we extended the file? */
  if (*ppos > dentry->d_inode->i_size) { dentry->d_inode->i_size = *ppos; }

out:
  return result;
}


static int nasd_edrfs_mmap(struct file            *file,
			   struct vm_area_struct  *vma) {
  struct dentry *dentry = file->f_dentry;
  int result;

#if DEBUG_MMAP_DETAIL
  nasd_printf("EDRFS: mmap(%s/%s)\n",
	      dentry->d_parent->d_name.name, dentry->d_name.name);
#endif /* DEBUG_MMAP_DETAIL */

  result = nasd_edrfs_revalidate(dentry);
  if (!result) {
    result = generic_file_mmap(file, vma);
  }
  
  return result;
}


static int nasd_edrfs_close_file(struct inode  *inode,
				 struct file   *file) {

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

#if DEBUG_CLOSE_FILE_DETAIL

  nasd_printf("nasd_edrfs_close_file(0x%lx, 0x%lx) (%x)\n",
	      (unsigned long) inode, (unsigned long) file,
	      inode->i_dev);
#endif /* DEBUG_CLOSE_FILE_DETAIL */

  /* we don't do any asynch writes, so this is trivial. */

  return 0;
}


static int nasd_edrfs_flush_file(struct file  *file) {

#if DEBUG_FLUSH_FILE_DETAIL
  nasd_printf("nasd_edrfs_flush_file(0x%lx) (%x)\n",
	      (unsigned long) file,
	      file->f_dentry->d_inode->i_dev);
#endif /* DEBUG_FLUSH_FILE_DETAIL */

  /* we don't do any asynch writes, so this is trivial. */

  return 0;
}


static int nasd_edrfs_fsync_file(struct file    *file,
				 struct dentry  *dentry) {

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

#if DEBUG_FSYNC_FILE_DETAIL
  nasd_printf("nasd_edrfs_fsync_file(0x%lx, 0x%lx) (%s/%s)\n",
	      (unsigned long) file, (unsigned long) dentry,
	      file->f_dentry->d_parent->d_name.name,
	      file->f_dentry->d_name.name);
#endif /* DEBUG_FSYNC_FILE_DETAIL */

  /* we don't do any asynch writes, so this is trivial. */

  return 0;
}


/* file inode operations */

int nasd_edrfs_readpage(struct file  *file,
			struct page  *page) {
  struct inode           *inode;

  char                   *buffer = (char *) page_address(page);
  unsigned long           offset = page->offset;
  nasd_len_t              read;
  nasd_status_t           rc;
  int                     error = 0;

  NASD_ASSERT(file != NULL);
  NASD_ASSERT(page != NULL);

  inode = file->f_dentry->d_inode;

#if DEBUG_READPAGE_DETAIL
  nasd_printf("nasd_edrfs_readpage(0x%lx, 0x%lx) (%p %ld)\n",
	      (unsigned long) file, (unsigned long) page,
	      page, page->offset);
#endif /* DEBUG_READPAGE_DETAIL */

  if (test_bit(PG_uptodate, &page->flags)) {
    nasd_printf("hey! this page is up to date!\n");
  }

  clear_bit(PG_error, &page->flags);
  
#if DEBUG_READPAGE_DETAIL
  nasd_printf("about to read %lu bytes at %lu\n",
	      (unsigned long) PAGE_SIZE, (unsigned long) offset);
#endif /* DEBUG_READPAGE_DETAIL */

  rc = nasd_edrfs_read_basic(inode, buffer, NASD_EDRFS_BUFFER_KERNEL,
			     PAGE_SIZE, offset, &read);
  
#if DEBUG_READPAGE_DETAIL
  nasd_printf("got %lu bytes\n", (unsigned long) read);
#endif /* DEBUG_READPAGE_DETAIL */

  if (rc != NASD_SUCCESS) {
    error = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }

  set_bit(PG_uptodate, &page->flags);
  
out:
  clear_bit(PG_locked, &page->flags);
  wake_up(&page->wait);
  return error;
}


int nasd_edrfs_writepage(struct file  *file,
			 struct page  *page) {
  struct inode           *inode;

  unsigned long           page_addr, offset;
  nasd_len_t              written, remaining;
  nasd_status_t           rc;
  int                     error = 0;

  NASD_ASSERT(file != NULL);
  NASD_ASSERT(page != NULL);

  inode = file->f_dentry->d_inode;

#if DEBUG_WRITEPAGE_DETAIL
  nasd_printf("nasd_edrfs_writepage(0x%lx, 0x%lx) (%p %ld)\n",
	      (unsigned long) file, (unsigned long) page,
	      page, page->offset);
#endif /* DEBUG_WRITEPAGE_DETAIL */

  remaining = PAGE_SIZE;
  page_addr = page_address(page);

  do {
    offset = page->offset + (PAGE_SIZE - remaining);
    
    /* XXX replace with cached write when it works */
    rc = nasd_edrfs_write_basic(inode, (void *) (page_addr + offset),
			      NASD_EDRFS_BUFFER_KERNEL,
			      remaining, offset, &written);

    if (rc != NASD_SUCCESS) { /* page isn't valid after an IO error */
      clear_bit(PG_uptodate, &page->flags);
      error = nasd_edrfs_convert_error(rc, 0);
      goto out;
    }

    remaining -= written;

    /* have we extended the file? */
    if (offset > inode->i_size) { inode->i_size = offset; }

  } while (remaining > 0);

out:
  return error;
}


/* update and possibly write a cached page */
int nasd_edrfs_updatepage(struct file    *file,
			  struct page    *page,
			  unsigned long   offset,
			  unsigned int    count,
			  int             sync) {

  NASD_ASSERT(file != NULL);
  NASD_ASSERT(page != NULL);

#if DEBUG_UPDATEPAGE_DETAIL
  nasd_printf("nasd_edrfs_updatepage(0x%lx, 0x%lx, %lu, %u, %d)\n",
	      (unsigned long) file, (unsigned long) page,
	      offset, count, sync);
#endif /* DEBUG_UPDATEPAGE_DETAIL */

  /* XXX - we should probably do something more intelligent here */
#if 0
  return nasd_edrfs_writepage(file, page);
#endif
  return -EPERM;
}

/* time to refresh cached attributes, maybe. */
int nasd_edrfs_lookup_revalidate(struct dentry  *dentry, int flags) {
  struct inode *inode;
  nasd_edrfs_inode_info_t *inode_info;
  nasd_attribute_t attr;
  nasd_edrfs_attributes_t edrfsattr;
  nasd_status_t rc;
  int error = 0;

  NASD_ASSERT(dentry != NULL);
  inode = dentry->d_inode;

  if (inode == NULL){ goto out; }

  inode_info = NASD_EDRFS_INODE_INFO(inode);

#if DEBUG_REVALIDATE_DETAIL
  nasd_printf("nasd_edrfs_lookup_revalidate(0x%lx) ('%s' in '%s', ino=%ld)\n",
	      (unsigned long) dentry,
	      dentry->d_name.name, dentry->d_parent->d_name.name,
	      inode->i_ino);
#endif /* DEBUG_REVALIDATE_DETAIL */

  /* we assume that attributes are valid for a while, just like EDRFS. */
  if (inode_info->cache_info.cache_valid &&
      ((jiffies - inode_info->cache_info.cached_since) <
       inode_info->cache_info.cache_timeout)) {
#if DEBUG_REVALIDATE_DETAIL
    nasd_printf("  cached since %ld, now is %ld, timeout is %ld, assuming cached info is still valid\n",
		inode_info->cache_info.cached_since, jiffies,
		inode_info->cache_info.cache_timeout);
#endif /* !DEBUG_REVALIDATE_DETAIL */
    goto out;
  }
#if DEBUG_REVALIDATE_DETAIL
  else {
    nasd_printf("  cached since %ld, now is %ld, timeout is %ld, cached data has expired, revalidating\n",
		inode_info->cache_info.cached_since, jiffies,
		inode_info->cache_info.cache_timeout);
    inode_info->cache_info.cache_valid = 0;
  }
#endif /* !DEBUG_REVALIDATE_DETAIL */

  rc = nasd_edrfs_do_getattr(dentry->d_sb, &inode_info->ident, &attr);
  if (rc) {
    error = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }
  
  nasd_edrfs_attributes_t_unmarshall((nasd_otw_base_t *) attr.fs_specific,
				     &edrfsattr);

  error = nasd_edrfs_refresh_inode(inode, &attr, &edrfsattr);

out:
  return error;
}

/* work around the new and painful "revalidate is different for lookups"
   fuckage in recent 2.2.x kernels. grn. */
int nasd_edrfs_revalidate(struct dentry *dentry) {
  return nasd_edrfs_lookup_revalidate(dentry, 0);
}

/* symlink operations */
static int nasd_edrfs_readlink(struct dentry  *dentry,
			       char           *buffer,
			       int             buflen) {
  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                     *newbuf = NULL;
  int                       error = 0;

  nasd_security_param_t	    sp;
  nasd_p_smpl_op_dr_args_t  read_args;
  nasd_p_fastread_dr_res_t  read_res;

  NASD_ASSERT(dentry != NULL);
  NASD_ASSERT(buffer != NULL);
  NASD_ASSERT(dentry->d_inode != NULL);

  inode_info = NASD_EDRFS_INODE_INFO(dentry->d_inode);
  sb_info = NASD_EDRFS_SUPER_SBINFO(dentry->d_sb);
  
#if DEBUG_READLINK_DETAIL
  nasd_printf("nasd_edrfs_readlink(0x%lx, 0x%lx, %d) (%s/%s)\n",
	      (unsigned long) dentry, (unsigned long) buffer, buflen,
	      dentry->d_parent->d_name.name, dentry->d_name.name);
#endif /* DEBUG_READLINK_DETAIL */

  if (buflen > NASD_EDRFS_MAX_PATHLEN) { buflen = NASD_EDRFS_MAX_PATHLEN; }

  /* bail if our drive handle is bad. */
  if (!(sb_info->handles.flags & NASD_BINDING_DRIVE_VALID)) {
    error = -EIO;
    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) {
    nasd_printf("EDRFS: couldn't get a cookie for readlink!\n");
    error = nasd_edrfs_convert_error(rc, 0);
    goto out;
  }

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

  NASD_Malloc(newbuf, buflen, (char *));
  if (newbuf == NULL) {
    nasd_printf("EDRFS: couldn't get memory in readlink!\n");
    error = -ENOMEM;
    goto out;
  }

  SETUP_SECURITY_PARAM(sp, cookie.capability);
  
  nasd_cl_p_read_simple_dr(sb_info->handles.drive_handle,
			   cookie.key, &sp, &cookie.capability,
			   &read_args,
			   (void *) newbuf,
			   &read_res, &op_status);
  rc = read_res.nasd_status;
  
  if ((rc == NASD_SUCCESS) && (op_status == 0)) {
    copy_to_user(buffer, newbuf, read_res.out_datalen);
    put_user('\0', buffer + read_res.out_datalen);
    error = read_res.out_datalen;
  } else {
    buffer[0] = '\0';
    nasd_printf("EDRFS: readlink: nasd_status=0x%x (%s), op_status=0x%x (%s)\n",
		read_res.nasd_status, nasd_error_string(read_res.nasd_status),
		op_status, nasd_cl_error_string(sb_info->handles.drive_handle,
						op_status, err_str));
    
    error = nasd_edrfs_convert_error(rc, op_status);
  }
  
  NASD_Free(newbuf, buflen);
  
 out:
  return error;
}
			     

static struct dentry *nasd_edrfs_follow_link(struct dentry  *dentry,
					     struct dentry  *base,
					     unsigned int    follow) {
  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                     *path = NULL;
  int                       error = 0;

  nasd_security_param_t	    sp;
  nasd_p_smpl_op_dr_args_t  read_args;
  nasd_p_fastread_dr_res_t  read_res;
  
  NASD_ASSERT(dentry != NULL);
  NASD_ASSERT(dentry->d_inode != NULL);
  NASD_ASSERT(base   != NULL);

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

#if DEBUG_FOLLOW_LINK_DETAIL
  nasd_printf("nasd_edrfs_followlink(0x%lx, 0x%lx, %d) (%s/%s)\n",
	      (unsigned long) dentry, (unsigned long) base, follow,
	      dentry->d_parent->d_name.name, dentry->d_name.name);
#endif /* DEBUG_FOLLOW_LINK_DETAIL */

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

  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) {
    error = nasd_edrfs_convert_error(rc, 0);
    goto out_nomem;
  }

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

  NASD_Malloc(path, NASD_EDRFS_MAX_PATHLEN, (char *));
  if (path == NULL) {
    error = -ENOMEM;
    goto out_nomem;
  }
  
  SETUP_SECURITY_PARAM(sp, cookie.capability);
  
  nasd_cl_p_read_simple_dr(sb_info->handles.drive_handle,
			   cookie.key, &sp, &cookie.capability,
			   &read_args,
			   (void *) path,
			   &read_res, &op_status);
  rc = read_res.nasd_status;

  if ((rc == NASD_SUCCESS) && (op_status == 0)) {
    path[read_res.out_datalen] = '\0';
    base = lookup_dentry(path, base, follow);
    error = 0;
    goto out;
  } else {
    dput(base);
    
    nasd_printf("EDRFS: readlink: nasd_status=0x%x (%s), op_status=0x%x (%s)\n",
		read_res.nasd_status, nasd_error_string(read_res.nasd_status),
		op_status, nasd_cl_error_string(sb_info->handles.drive_handle,
						op_status, err_str));
    
    error = nasd_edrfs_convert_error(rc, op_status);
    goto out;
  }
  
out:
  NASD_Free(path, NASD_EDRFS_MAX_PATHLEN);
out_nomem:
  if (error != 0) { return ERR_PTR(error); }
  else            { return base; }
}
