/*
 * Super block/filesystem wide 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/locks.h>
#include <linux/userfs_fs.h>
#include <linux/userfs_fs_sb.h>
#include <linux/userfs_mount.h>
#include <linux/string.h>
#include <asm/segment.h>

#include "userfs.h"

static void userfs_free_super(struct super_block *sb)
{

  userfs_close_chan(sb);

  if (sb->s_op != &userfs_super_operations)
    kfree_s(sb->s_op, sizeof(struct super_operations));

  kfree_s(U_SB(sb).s_iops->default_file_ops, sizeof(struct file_operations));
  kfree_s(U_SB(sb).s_iops, sizeof(struct inode_operations));

  sb->s_dev = 0;

  if (U_SB(sb).s_nwproc != 0)
    {
      assert(U_SB(sb).s_wlist != NULL);
      userfs_kiss(sb, UP_WALL, UP_FINISH);
    }

  assert(U_SB(sb).s_nwproc == 0);
  assert(U_SB(sb).s_wlist == NULL);

#ifdef MODULE
  if (U_SBP(sb) != NULL) {
    kfree(U_SBP(sb));
    U_SBP(sb) = NULL;
  }
  MOD_DEC_USE_COUNT;
#endif
		
}
	
static void userfs_put_super(struct super_block *sb)
{
  up_preamble snd;
  upp_repl repl;
  int ret;

  userfs_genpkt(sb, &snd, up_umount);

  lock_super(sb);
  if ((ret = userfs_doop(sb, &snd, &repl, void, NULL, void, NULL)) != 0)
    printk("userfs_put_super: userfs_doop returns %d\n", ret);

  unlock_super(sb);
  if (repl.errno != 0)
    printk("userfs_put_super: up_umount failed with %ld\n", repl.errno);
  userfs_free_super(sb);
  printk("userfs: put_super finished\n");
}

static void userfs_free_inode(struct inode *ino)
{
  if (ino->i_count > 1)
    return;
	
  userfs_free_dirra(ino);
#ifdef MODULE
  if (U_INOP(ino) != NULL)
    kfree_s(U_INOP(ino), sizeof(struct userfs_inode_info));
  U_INOP(ino) = NULL;
#endif
  /* Make sure the inode isn't cached */
  clear_inode(ino);
}

static void userfs_put_inode(struct inode *ino)
{
  up_preamble pre;
  upp_iput_s pkt;
  upp_repl repl;
  int ret;

  userfs_genpkt(ino->i_sb, &pre, up_iput);
  pkt.handle = U_INO(ino).handle;

  lock_inode(ino);
	
  if ((ret = userfs_doop(ino->i_sb, &pre, &repl,
                         upp_iput_s, &pkt, void, NULL)) != 0)
    printk("userfs_put_inode: userfs_doop failed %d\n", ret);
  else
    {
      if (repl.errno != 0)
        printk("userfs_put_inode: operation failed with %ld\n",
               repl.errno);
    }
  unlock_inode(ino);
  userfs_free_inode(ino);
}

static void userfs_statfs_stub(struct super_block *sb, struct statfs *buf)
{
  put_fs_long(USERFS_SUPER_MAGIC, &buf->f_type);
  put_fs_long(1024, &buf->f_bsize);
  put_fs_long(0, &buf->f_blocks);
  put_fs_long(0, &buf->f_bfree);
  put_fs_long(0, &buf->f_bavail);
  put_fs_long(0, &buf->f_files);
  put_fs_long(0, &buf->f_ffree);
  put_fs_long(NAME_MAX, &buf->f_namelen);
  /* Don't know what value to put in buf->f_fsid */
}

static void userfs_statfs(struct super_block *sb, struct statfs *buf)
{
  up_preamble pre;
  upp_repl repl;
  upp_statfs_r rcv;
  int ret;
  struct statfs sfs;
	
  userfs_genpkt(sb, &pre, up_statfs);

  if ((ret = userfs_doop(sb, &pre, &repl,
                         void, NULL, upp_statfs_r, &rcv)) != 0)
    {
      printk("userfs_statfs: userfs_doop failed: %d\n", ret);
      memset(&sfs, 0, sizeof(struct statfs));
    }
  else
    {
      sfs.f_type = USERFS_SUPER_MAGIC;
      sfs.f_bsize = rcv.bsize;
      sfs.f_blocks = rcv.blocks;
      sfs.f_bfree = rcv.bfree;
      sfs.f_bavail = rcv.bavail;
      sfs.f_files = rcv.files;
      sfs.f_ffree = rcv.ffree;
      sfs.f_fsid = rcv.fsid;
      sfs.f_namelen = rcv.namelen;
    }
  memcpy_tofs(buf, &sfs, sizeof(struct statfs));
}

static inline struct file *fdtofp(int fd)
{
  struct file *file;

  if (fd>=NR_OPEN)
    return NULL;

#ifdef AFTER1_1_11
  file = current->files->fd[fd];
#else
  file = current->filp[fd];
#endif
  if (file->f_inode == NULL)
    return NULL;
	
  return file;
}

struct super_block *
userfs_read_super(struct super_block *sb, void *raw_data, int flag)
{
  struct file *toproc;
  struct file *fromproc;
  struct userfs_mount *mntinfo = (struct userfs_mount *)raw_data;
  up_preamble snd;
  upp_mount_r rcv;
  upp_repl repl;
  int ret;
	
#ifdef MODULE
  MOD_INC_USE_COUNT;
  U_SBP(sb) = kmalloc(sizeof(struct userfs_sb_info), GFP_KERNEL);
  memset(U_SBP(sb), 0, sizeof(U_SB(sb)));
#endif
  toproc = fromproc = NULL;

  U_SB(sb).s_toproc = toproc;
  U_SB(sb).s_fromproc = fromproc;

  if (raw_data == NULL)
    {
      printk("userfs_read_super: extra mount data missing\n");
      goto fail;
    }
	
  if (mntinfo->version != USERFS_VERSION)
    {
      printk("userfs_read_super: mount data version %d, kernel is %d\n",
             mntinfo->version, USERFS_VERSION);
      goto fail;
    }

  if (mntinfo->magic != USERFS_SUPER_MAGIC)
    {
      printk("userfs: client tried to mount with incorrect"
             " magic number in parameter block\n");
      goto fail;
    }
	
  if ((toproc = fdtofp(mntinfo->fromkern)) == NULL || !(toproc->f_mode&2))
    {
      printk("userfs_read_super: bad fromkern fd %d\n",
             mntinfo->fromkern);
      goto fail;
    }
	
  U_SB(sb).s_toproc = toproc;

  if ((fromproc = fdtofp(mntinfo->tokern)) == NULL || !(fromproc->f_mode&1))
    {
      printk("userfs_read_super: bad tokern fd %d\n",
             mntinfo->tokern);
      goto fail;
    }

  U_SB(sb).s_fromproc = fromproc;
	
  toproc->f_count++;
  fromproc->f_count++;
	
  sb->s_magic = USERFS_SUPER_MAGIC;
  sb->s_blocksize = 2048;
  sb->s_op = &userfs_super_operations;

  U_SB(sb).s_seq = mntinfo->seq;
  U_SB(sb).s_iops = &userfs_inode_operations;

  U_SB(sb).s_nwproc = 0;
  U_SB(sb).s_canread = 0;
  U_SB(sb).s_wlist = NULL;
	
  userfs_genpkt(sb, &snd, up_mount);

  ret = userfs_doop(sb, &snd, &repl, void, NULL, upp_mount_r, &rcv);

  if (ret != 0)
    {
      printk("userfs_read_super: userfs_doop failed with %d\n", ret);
      goto fail;
    }

  if (repl.errno != 0)
    {
      printk("userfs_read_super: mount failed with %ld\n",
             repl.errno);
      goto fail;
    }

  U_SB(sb).s_root = rcv.root;

  sb->s_op = userfs_probe_sops(sb);
  U_SB(sb).s_iops = userfs_probe_iops(sb);

  if (!(sb->s_mounted = iget(sb, U_SB(sb).s_root.handle)))
    {
      printk("userfs_read_super: get root inode %ld failed\n",
             U_SB(sb).s_root.handle);
      goto fail;
    }

  return sb;
	
 fail:
  printk("userfs_read_super"": failed\n");
  if (sb != NULL)
    {
      sb->s_dev = 0;
      sb->s_op = NULL;
      sb->s_magic = 0;
      userfs_close_chan(sb);
#ifdef MODULE
      if (U_SBP(sb) != NULL) {
        kfree(U_SBP(sb));
        U_SBP(sb) = NULL;
      }
#endif
    }
#ifdef MODULE
  MOD_DEC_USE_COUNT;
#endif
  return NULL;
}

extern struct inode_operations userfs_inode_operations;

void userfs_set_inode(struct inode *inode, const up_inode *rcv)
{
  inode->i_mode = rcv->mode;
  inode->i_nlink = rcv->nlink;
  inode->i_uid = rcv->uid;
  inode->i_gid = rcv->gid;
  inode->i_size = rcv->size;
  inode->i_atime = rcv->atime;
  inode->i_mtime = rcv->mtime;
  inode->i_ctime = rcv->ctime;
  inode->i_blksize = rcv->blksize;
  inode->i_blocks = rcv->blocks;
  inode->i_rdev = rcv->rdev;
}

static void
userfs_read_inode(struct inode *inode)
{
  up_preamble pre;
  upp_repl repl;
  upp_iread_s snd;
  upp_iread_r rcv;
  int ret;

  inode->i_op = NULL;
  inode->i_mode = 0;
	
  userfs_genpkt(inode->i_sb, &pre, up_iread);

  snd.handle.handle = inode->i_ino;

  if ((ret = userfs_doop(inode->i_sb, &pre, &repl,
                         upp_iread_s, &snd,
                         upp_iread_r, &rcv)) != 0)
    {
      printk("userfs_read_inode: failed to do upp_iread: %d\n", ret);
      return;
    }

  if (repl.errno != 0)
    {
      printk("userfs_read_inode: inode read failed: ino=%ld, errno = %ld\n",
             inode->i_ino, repl.errno);
      memset(&rcv, 0, sizeof(rcv));
    }
  else
    {
      assert(rcv.handle.handle == inode->i_ino);
      assert(inode->i_nlink != 0);

      if ((rcv.handle.handle == inode->i_ino) && (inode->i_nlink != 0))
        inode->i_op = U_SB(inode->i_sb).s_iops;
      else
        memset(&rcv, 0, sizeof(rcv));
    }

#ifdef MODULE
  U_INOP(inode) = kmalloc(sizeof(struct userfs_inode_info), GFP_KERNEL);
#endif
  U_INO(inode).handle = rcv.handle;
  U_INO(inode).dir_ra = NULL;
  userfs_set_inode(inode, &rcv.ino);
}

static void userfs_pack_iattr(up_iattr *target, const struct iattr *source)
{
  target->ia_valid = source->ia_valid;
  target->ia_mode  = source->ia_mode;
  target->ia_uid   = source->ia_uid;
  target->ia_gid   = source->ia_gid;
  target->ia_size  = source->ia_size;
  target->ia_atime = source->ia_atime;
  target->ia_mtime = source->ia_mtime;
  target->ia_ctime = source->ia_ctime;
  return;
}

static int userfs_notify_change(struct inode *inode, struct iattr *attr)
{
  up_preamble pre;
  upp_notify_change_s snd;
  upp_repl rcv;
  int ret;

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

  snd.handle = U_INO(inode).handle;
  userfs_pack_iattr(&snd.iattr, attr);
	
  if ((ret = userfs_doop(inode->i_sb, &pre, &rcv,
                         upp_notify_change_s, &snd,
                         void, NULL)) != 0) {
    printk("userfs_notify_change: transaction failed: %d\n", ret);
  } else {
    ret = -rcv.errno;
  }
  return ret;
}

static void userfs_pack_inode(up_inode *ino, const struct inode *inode)
{
  ino->mode = inode->i_mode;
  ino->nlink = inode->i_nlink;
  ino->uid = inode->i_uid;
  ino->gid = inode->i_gid;
  ino->size = inode->i_size;
  ino->atime = inode->i_atime;
  ino->mtime = inode->i_mtime;
  ino->ctime = inode->i_ctime;
  ino->blksize = inode->i_blksize;
  ino->blocks = inode->i_blocks;
}

static int userfs_write_change(int flags, struct inode *inode)
{
  up_preamble pre;
  upp_iwrite_s snd;
  upp_repl rcv;
  int ret;
	
  userfs_genpkt(inode->i_sb, &pre, up_iwrite);

  snd.handle = U_INO(inode).handle;
  userfs_pack_inode(&snd.ino, inode);

  if ((ret = userfs_doop(inode->i_sb, &pre, &rcv,
                         upp_iwrite_s, &snd, void, NULL)) != 0)
    {
      printk("userfs_write_change: transaction failed: %d\n", ret);
      return ret;
    }
	
  return rcv.errno;
}

static void userfs_write_inode(struct inode *inode)
{
  int ret;
	
  /*
   * We arn't allowed to fail, so if the IO below doesn't work,
   * tough.
   */
  inode->i_dirt = 0;

  if ((ret = userfs_write_change(0, inode)) != 0)
    printk("userfs_write_inode: inode write failed: %d\n", ret);
}

struct super_operations *userfs_probe_sops(struct super_block *sb)
{
  struct super_operations *sops;

  sops = (struct super_operations *)kmalloc(sizeof(*sops), GFP_KERNEL);
  *sops = userfs_super_operations;

  if (!userfs_probe(sb, up_iread))
    sops->read_inode = NULL;
  if (!userfs_probe(sb, up_iwrite))
    sops->write_inode = NULL;
  if (!userfs_probe(sb, up_iput))
    sops->put_inode = userfs_free_inode;
  if (!userfs_probe(sb, up_umount))
    sops->put_super = userfs_free_super;
  if (!userfs_probe(sb, up_statfs))
    sops->statfs = userfs_statfs_stub;
  if (!userfs_probe(sb, up_notify_change))
    sops->notify_change = NULL;

  return sops;
}

struct super_operations userfs_super_operations =
{
  userfs_read_inode,            /* read_inode */
  userfs_notify_change,         /* notify_change */
  userfs_write_inode,           /* write_inode */
  userfs_put_inode,             /* put_inode */
  userfs_put_super,             /* put_super */
  NULL,                         /* write_super */
  userfs_statfs,		/* statfs */
  NULL                          /* remount_fs */
  };
