/*
 * Deal with file operations
 *
 * Jeremy Fitzharinge <jeremy@softway.oz.au>, May 1993
 */

#include <linux/config.h>
#ifdef MODULE
#include <linux/module.h>
#include <linux/version.h>
#endif

#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/sched.h>
#include <linux/fs.h>
#include <linux/stat.h>
#include <linux/errno.h>
#include <linux/fcntl.h>
#include <linux/locks.h>
#include <linux/userfs_fs.h>
#include <linux/userfs_fs_sb.h>
#include <linux/userfs_fs_f.h>
#include <linux/userfs_mount.h>
#include <asm/segment.h>

#include "userfs.h"

static int userfs_read(struct inode *inode, struct file *fl,
                       char *buf, int size)
{
  unsigned int max_size;
  up_preamble pre;
  upp_repl repl;
  upp_read_s snd;
  upp_read_r rcv;
  int rd = 0;
  int sz;
  int ret;
		
  if (inode == NULL)
    {
      printk("userfs_read: inode is NULL\n");
      return -EINVAL;
    }

  rcv.data.nelem = 0;
  max_size = MAX_KMALLOC - sizeof_upp_read_r(&rcv);
	
  snd.file = U_INO(inode).handle;
  snd.ctok = U_FILE(fl).cred_tok;

  while(size > 0)
    {
      userfs_genpkt(inode->i_sb, &pre, up_read);

      snd.off = fl->f_pos;
      snd.size = MIN(max_size, size);
		
      if ((ret = userfs_doop(inode->i_sb, &pre, &repl,
                             upp_read_s, &snd,
                             upp_read_r, &rcv)) != 0)
        {
          printk("userfs_read: userfs_doop failed %d\n",
                 ret);
          if (rd == 0)
            rd = ret;
          break;
        }

      if (repl.errno != 0)
        {
          if (rd == 0)
            rd = -repl.errno;
          break;
        }

      if (rcv.data.nelem == 0)
        break;

      sz = MIN(size, rcv.data.nelem);

      if (rcv.data.nelem > max_size)
        {
          printk("userfs_read: more than %d bytes in data (!!!) (%ld)\n",
                 max_size, rcv.data.nelem);
          sz = MIN(sz, max_size);
        }

      memcpy_tofs(buf, rcv.data.elems, sz);
      fl->f_pos += sz;
      size -= sz;
      rd += sz;
      buf += sz;

      FREE(rcv.data.elems);
    }
  return rd;
}

static int userfs_write(struct inode *inode, struct file *fl,
                        char *buf, int size)
{
  up_preamble pre;
  upp_repl repl;
  upp_write_s snd;
  upp_write_r rcv;
  int wr = 0;
  int sz;
  int ret;
  unsigned char *mbuf = NULL;
  int bufsz = 0;
  unsigned int max_size;
	
  if (inode == NULL)
    {
      printk("userfs_write: inode is NULL\n");
      return -EINVAL;
    }

  snd.data.nelem = 0;
  max_size = MAX_KMALLOC - sizeof_upp_write_s(&snd);
	
  snd.file = U_INO(inode).handle;
  snd.ctok = U_FILE(fl).cred_tok;

  while(size > 0)
    {
      sz = MIN(max_size, size);

      mbuf = (unsigned char *)kmalloc(sz, GFP_KERNEL);
      bufsz = sz;

      memcpy_fromfs(mbuf, buf, sz);

      userfs_genpkt(inode->i_sb, &pre, up_write);

      if (fl->f_flags & O_APPEND)
        snd.off = inode->i_size;
      else
        snd.off = fl->f_pos;
      snd.data.elems = mbuf;
      snd.data.nelem = sz;
		
      if ((ret = userfs_doop(inode->i_sb, &pre, &repl,
                             upp_write_s, &snd,
                             upp_write_r, &rcv)) != 0)
        {
          printk("userfs_write: userfs_doop failed %d\n",
                 ret);
          if (wr == 0)
            wr = ret;
          break;
        }

      kfree_s(mbuf, bufsz);
      mbuf = NULL;
      bufsz = 0;

      if (repl.errno != 0)
        {
          if (wr == 0)
            wr = -repl.errno;
          break;
        }

      sz = rcv.wrote;

      if (sz == 0)
        {
          printk("userfs_write: write op returned 0\n");
          break;
        }
      fl->f_pos += sz;
      size -= sz;
      wr += sz;
      buf += sz;
    }

  if (wr >= 0)
    {
      inode->i_size = fl->f_pos;
      inode->i_dirt = 1;
    }

  if (mbuf != NULL)
    {
      kfree_s(mbuf, bufsz);
      mbuf = NULL;
      bufsz = 0;
    }
  return wr;
}

/* Free all read-ahead dir entries */
void userfs_free_dirra(struct inode *ino)
{
  struct userfs_dir_ra *ra;
  int i;

#ifdef MODULE
  if (U_INOP(ino) == NULL)
    return;
#endif
  if (ino == NULL || U_INO(ino).dir_ra == NULL)
    return;

  ra = U_INO(ino).dir_ra;
  U_INO(ino).dir_ra = NULL;
	
  for(i = 0; i < ra->nent; i++)
    {
      if (ra->ents[i].name != NULL)
        kfree_s(ra->ents[i].name, ra->ents[i].nlen);
    }

  kfree_s(ra->ents, sizeof(*ra->ents)*ra->nent);
  kfree_s(ra, sizeof(*ra));
}

/*
 * Look up dir entry given offset for inode
 * Returns pointer to entry on sucess, NULL on failure
 */
static struct userfs_dent *lookup_ra_dent(struct inode *dir, off_t off)
{
  struct userfs_dir_ra *ra;
  int i;
	
  if (dir == NULL || U_INO(dir).dir_ra == NULL)
    return NULL;

  ra = U_INO(dir).dir_ra;

  for(i = 0; i < ra->nent; i++)
    if (ra->ents[i].roff == off)
      return &ra->ents[i];
  return NULL;
}

static int userfs_multireaddir(struct inode *dir, struct file *fl,
			       struct dirent *dirent, int count)
{
  up_preamble pre;
  upp_repl repl;
  upp_multireaddir_s snd;
  upp_multireaddir_r rcv;
  struct userfs_dent *dent;
  struct userfs_dir_ra *ra;
  long ret;
  int i;
  off_t off;
	
  if (!dir || !S_ISDIR(dir->i_mode))
    return -ENOTDIR;

  /* Try for read-ahead entries first */
  if ((dent = lookup_ra_dent(dir, fl->f_pos)) != NULL)
    {
      ret = MIN(NAME_MAX, dent->nlen);
      if (ret > 0)
        memcpy_tofs(dirent->d_name, dent->name, ret);
      put_fs_byte('\0', dirent->d_name+ret);
      put_fs_long(dent->inum, &dirent->d_ino);
      put_fs_word(ret, &dirent->d_reclen);
		
      fl->f_pos += dent->offset;
      return dent->offset;
    }

  /* Flush the read-ahead and get something anew */
  userfs_free_dirra(dir);
	
  userfs_genpkt(dir->i_sb, &pre, up_multireaddir);

  snd.off = fl->f_pos;
  snd.dir = U_INO(dir).handle;
  snd.ctok = U_FILE(fl).cred_tok;

  rcv.elems = NULL;

  lock_inode(dir);
	
  if ((ret = userfs_doop(dir->i_sb, &pre, &repl, upp_multireaddir_s,
                         &snd, upp_multireaddir_r, &rcv)) != 0)
    {
      printk("userfs_multireaddir: userfs_doop failed %ld\n", ret);
      unlock_inode(dir);
      return ret;
    }

  if (repl.errno != 0)
    {
      ret = -repl.errno;
      goto out;
    }

  ra = kmalloc(sizeof(*ra), GFP_KERNEL);
  ra->nent = rcv.nelem;
  ra->ents = kmalloc(sizeof(struct userfs_dent)*rcv.nelem, GFP_KERNEL);
	
  for(off = fl->f_pos, i = 0; i < rcv.nelem; i++)
    {
      ra->ents[i].roff = off;
      off += ra->ents[i].offset = rcv.elems[i].off;
      ra->ents[i].name = rcv.elems[i].name.elems;
      ra->ents[i].nlen = rcv.elems[i].name.nelem;
      ra->ents[i].inum = rcv.elems[i].file.handle;
    }
  U_INO(dir).dir_ra = ra;
	
  if ((dent = lookup_ra_dent(dir, fl->f_pos)) != NULL)
    {
      int len;
		
      len = MIN(NAME_MAX, dent->nlen);
      if (len > 0)
        memcpy_tofs(dirent->d_name, dent->name, len);
      put_fs_byte('\0', dirent->d_name+len);
      put_fs_long(dent->inum, &dirent->d_ino);
      put_fs_word(len, &dirent->d_reclen);
		
      fl->f_pos += dent->offset;
      ret = dent->offset;
    }
  else
    ret = 0;
	
 out:
  unlock_inode(dir);
  return ret;
}

static int userfs_readdir(struct inode *dir, struct file *fl,
			  struct dirent *dirent, int count)
{
  up_preamble pre;
  upp_repl repl;
  upp_readdir_s snd;
  upp_readdir_r rcv;
  long ret;

  if (!dir || !S_ISDIR(dir->i_mode))
    return -ENOTDIR;

  userfs_genpkt(dir->i_sb, &pre, up_readdir);

  snd.off = fl->f_pos;
  snd.dir = U_INO(dir).handle;
  snd.ctok = U_FILE(fl).cred_tok;

  rcv.name.nelem = 0;

  lock_inode(dir);
	
  if ((ret = userfs_doop(dir->i_sb, &pre, &repl,
                         upp_readdir_s, &snd,
                         upp_readdir_r, &rcv)) != 0)
    {
      printk("userfs_readdir: userfs_doop failed %ld\n", ret);
      unlock_inode(dir);
      return ret;
    }

  unlock_inode(dir);
	
  if (repl.errno != 0)
    {
      ret = -repl.errno;
      goto out;
    }

  ret = MIN(NAME_MAX, rcv.name.nelem);

  if (ret > 0)
    memcpy_tofs(dirent->d_name, rcv.name.elems, ret);

  put_fs_byte('\0', dirent->d_name+ret);
  put_fs_long(rcv.file.handle, &dirent->d_ino);
  put_fs_word(ret, &dirent->d_reclen);
#if 0
  /* No other FS's seem to set this */
  put_fs_long(fl->f_pos, &dirent->d_off);
#endif
	
  fl->f_pos += rcv.off;

  ret = rcv.off;

 out:
  if (rcv.name.nelem != 0)
    FREE(rcv.name.elems);

  return ret;
}

static int userfs_open(struct inode *ino, struct file *file)
{
  upp_open_s snd;
  upp_open_r rcv;
  up_preamble pre;
  upp_repl repl;
  int ret = 0;
	
  userfs_genpkt(ino->i_sb, &pre, up_open);

  userfs_setcred(&snd.cred);
  snd.file = U_INO(ino).handle;

  if ((ret = userfs_doop(ino->i_sb, &pre, &repl,
                         upp_open_s, &snd, upp_open_r, &rcv)) != 0)
    {
      printk("userfs_open: userfs_doop failed %d\n", ret);
      goto out;
    }

  if ((ret = -repl.errno) == 0)
    {
#ifdef MODULE
      U_FILEP(file) = kmalloc(sizeof(struct userfs_file_info), GFP_KERNEL);
#endif
      U_FILE(file).cred_tok = rcv.ctok;
    }
	
 out:
  return ret;
}


#ifdef MODULE
static const struct userfs_file_info file_info = { 0 };

static int userfs_stub_open(struct inode *ino, struct file *file)
{
  U_FILEP(file) = &file_info;
  return 0;
}
#else
static int userfs_stub_open(struct inode *ino, struct file *file)
{
  return 0;
}
#endif

static void free_file(struct inode *ino, struct file *file)
{
#ifdef MODULE
  if (U_FILEP(file) != NULL && U_FILEP(file) != &file_info)
    kfree_s(U_FILEP(file), sizeof(*U_FILEP(file)));
#endif
}

static void userfs_close(struct inode *ino, struct file *file)
{
  upp_close_s snd;
  up_preamble pre;
  upp_repl repl;
  int ret = 0;
	
  userfs_genpkt(ino->i_sb, &pre, up_close);

  snd.file = U_INO(ino).handle;
  snd.ctok = U_FILE(file).cred_tok;

  if ((ret = userfs_doop(ino->i_sb, &pre, &repl,
                         upp_close_s, &snd, void, NULL)) != 0)
    printk("userfs_close: userfs_doop failed %d\n", ret);

  free_file(ino, file);
}

/* disable things that process won't cope with */
struct file_operations *userfs_probe_fops(struct super_block *sb)
{
  struct file_operations *fops;

  fops = (struct file_operations *)kmalloc(sizeof(*fops), GFP_KERNEL);

  *fops = userfs_file_operations;

  if (!userfs_probe(sb, up_read))
    fops->read = NULL;
  if (!userfs_probe(sb, up_write))
    fops->write = NULL;
  if (!userfs_probe(sb, up_multireaddir))
    {
      if (userfs_probe(sb, up_readdir))
        fops->readdir = userfs_readdir;
      else
        fops->readdir = NULL;
    }
  if (!userfs_probe(sb, up_open))
    fops->open = userfs_stub_open;
  if (!userfs_probe(sb, up_close))
    fops->release = free_file;
	
  return fops;
}

struct file_operations userfs_file_operations =
{
  NULL,                         /* userfs_lseek */
  userfs_read,
  userfs_write,
  userfs_multireaddir,
  NULL,                         /* userfs_select */
  NULL,                         /* userfs_ioctl */
  NULL,                         /* mmap */
  userfs_open,                  /* open */
  userfs_close,                 /* release */
  NULL                          /* can't fsync */
  };
