/*
 *	General inody operations for user process filesystem implmentation
 *
 *	Jeremy Fitzhardinge <jeremy@sw.oz.au>, 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 <asm/segment.h>
#include <linux/string.h>

#include "userfs.h"

void __wait_on_inode(struct inode * inode)
{
  struct wait_queue wait = { current, NULL };

  add_wait_queue(&inode->i_wait, &wait);
 repeat:
  current->state = TASK_UNINTERRUPTIBLE;
  if (inode->i_lock) {
    schedule();
    goto repeat;
  }
  remove_wait_queue(&inode->i_wait, &wait);
  current->state = TASK_RUNNING;
}

static int userfs_lookup(struct inode *dir,
                         const char *name, int len,
                         struct inode **result)
{
  int ret = -ENOENT;
  up_preamble pre;
  upp_repl repl;
  upp_lookup_s snd;
  upp_lookup_r rcv;

  *result = NULL;

  if (!dir)
    return -ENOENT;

  if (!S_ISDIR(dir->i_mode))
    {
      ret = -ENOTDIR;
      goto out;
    }
	
  userfs_genpkt(dir->i_sb, &pre, up_lookup);

  snd.name.nelem = len;
  snd.name.elems = (char *)name;
  snd.dir = U_INO(dir).handle;

  if ((ret = userfs_doop(dir->i_sb, &pre, &repl,
                         upp_lookup_s, &snd,
                         upp_lookup_r, &rcv)) != 0)
    printk("userfs_lookup: userfs_doop failed %d\n", ret);

  if (ret == 0)
    if ((ret = -repl.errno) == 0)
      {
        assert(dir->i_sb != NULL)
          *result = iget(dir->i_sb, rcv.handle.handle);
      }
 out:
  iput(dir);
  return ret;
}

void userfs_setcred(up_cred *cr)
{
  int i;
	
  cr->uid = current->uid;
  cr->suid = current->suid;
  cr->euid = current->euid;
  cr->gid = current->gid;
  cr->sgid = current->sgid;
  cr->egid = current->egid;
#ifdef AFTER1_1_11
  cr->umask = current->fs->umask;
#else
  cr->umask = current->umask;
#endif
  cr->groups.elems = (long *)current->groups;

  for(i = 0; i < NGROUPS && current->groups[i] != NOGROUP; i++)
    ;
  cr->groups.nelem = i;
}

static int do_create(struct inode *dir, const char *name, int nlen, int mode,
		     dev_t rdev, up_handle *hand)
{
  up_preamble pre;
  upp_repl repl;
  upp_create_s snd;
  upp_create_r rcv;
  int ret;
		
  if (!dir || !S_ISDIR(dir->i_mode))
    {
      iput(dir);
      return -ENOTDIR;
    }
	
  userfs_genpkt(dir->i_sb, &pre, up_create);
	
  snd.mode = mode;
  snd.dir = U_INO(dir).handle;
  userfs_setcred(&snd.cred);
  snd.name.elems = (char *)name;
  snd.name.nelem = nlen;
  snd.rdev = rdev;

  if ((ret = userfs_doop(dir->i_sb, &pre, &repl,
                         upp_create_s, &snd, upp_create_r, &rcv)) != 0)
    printk("do_create(userfs): userfs_doop failed: %d\n", ret);
  else
    {
      if (hand != NULL)
        *hand = rcv.file;

      ret = -repl.errno;
    }

  iput(dir);
  return ret;
}

static int userfs_create(struct inode *dir, const char *name, int nlen,
			 int mode, struct inode **result)
{
  struct inode *ino = NULL;
  int ret = 0;
  up_handle file;

  *result = NULL;

  dir->i_count++;
  ret = do_create(dir, name, nlen, S_IFREG | mode, 0, &file);
	
  if (ret == 0)
    {
      assert(dir->i_sb != NULL);
      ino = iget(dir->i_sb, file.handle);
      if (ino == NULL)
        {
          printk("userfs_create: iget failed for %lx\n", file.handle);
          ret = -EBADF;
        }
      else
        *result = ino;
    }
  iput(dir);
  return ret;
}

static int userfs_mknod(struct inode *dir, const char *name, int nlen,
			int mode, int rdev)
{
  return do_create(dir, name, nlen, mode, rdev, NULL);
}

static int userfs_mkdir(struct inode *dir, const char *name, int nlen,
			int mode)
{
  return do_create(dir, name, nlen, S_IFDIR | mode, 0, NULL);
}

/* Actually do the readlink - doesn't eat ino */
static int do_readlink(struct inode *ino, up_name *res)
{
  int ret;
  upp_readlink_s snd;
  upp_readlink_r rcv;
  up_preamble pre;
  upp_repl repl;
	
  rcv.name.elems = NULL;
  rcv.name.nelem = 0;

  if (!S_ISLNK(ino->i_mode))
    return -EINVAL;

  userfs_genpkt(ino->i_sb, &pre, up_readlink);

  snd.link = U_INO(ino).handle;

  if ((ret = userfs_doop(ino->i_sb, &pre, &repl,
                         upp_readlink_s, &snd,
                         upp_readlink_r, &rcv)) != 0)
    {
      printk("do_readlink(userfs): userfs_doop failed %d\n", ret);
      return ret;
    }

  if (repl.errno != 0)
    return -repl.errno;

  *res = rcv.name;
	
  return 0;
}

static int userfs_readlink(struct inode *ino, char *name, int nlen)
{
  int ret;
  up_name path;

  path.elems = NULL;
	
  ret = do_readlink(ino, &path);
	
  if (ret != 0)
    goto out;

  ret = MIN(nlen, path.nelem);

  if (ret > 0)
    memcpy_tofs(name, path.elems, ret);

 out:
  if (path.elems != NULL)
    FREE(path.elems);

  iput(ino);
  return ret;
}

static int do_followlink(struct inode *dir, struct inode *ino,
	int flag, int mode, struct inode **result, int useread)
{
  up_name path;
  int ret;
  char *fpath;
  int fplen;
	
  *result = NULL;

  if (dir == NULL)
    {
#ifdef AFTER1_1_11
      dir = current->fs->root;
#else
      dir = current->root;
#endif
      dir->i_count++;
    }

  if (!ino)
    {
      iput(dir);
      return -ENOENT;
    }

  if (!S_ISLNK(ino->i_mode))
    {
      iput(dir);
      *result = ino;
      return 0;
    }

  path.elems = NULL;
		
  if (current->link_count > 5)
    {
      ret = -ELOOP;
      goto out;
    }

  if (useread)
    {
      if ((ret = do_readlink(ino, &path)) != 0)
        goto out;
    }
  else
    {
      up_preamble pre;
      upp_repl repl;
      upp_followlink_s snd;
      upp_followlink_r rcv;

      rcv.path.nelem = 0;
	
      userfs_genpkt(dir->i_sb, &pre, up_followlink);

      snd.dir = U_INO(dir).handle;
      snd.link = U_INO(ino).handle;
      snd.flag = flag;
      snd.mode = mode;

      if ((ret = userfs_doop(dir->i_sb, &pre, &repl,
                             upp_followlink_s, &snd,
                             upp_followlink_r, &rcv)) != 0)
        {
          printk("userfs_followlink: userfs_doop failed %d\n",
                 ret);
          goto out;
        }

      if (repl.errno != 0)
        {
          ret = -repl.errno;
          goto out;
        }
      path = rcv.path;
    }
	
  fplen = path.nelem;
  fpath = (char *)kmalloc(fplen+1, GFP_KERNEL);
  memcpy(fpath, (char *)path.elems, fplen);
  fpath[fplen] = '\0';
	
  current->link_count++;
  dir->i_count++;		/* open_namei eats dir */
  ret = open_namei(fpath, flag, mode, result, dir);
  current->link_count--;

  kfree_s(fpath, fplen+1);
	
 out:
  if (path.elems != NULL)
    FREE(path.elems);
	
  iput(ino);
  iput(dir);

  return ret;
}

/* Do a followlink, using the followlink operation */
static int userfs_followlink(struct inode *dir, struct inode *ino,
	int flag, int mode, struct inode **result)
{
  return do_followlink(dir, ino, flag, mode, result, 0);
}

/* Do a followlink, using readlink to get the info */
static int userfs_followlink2(struct inode *dir, struct inode *ino,
	int flag, int mode, struct inode **result)
{
  return do_followlink(dir, ino, flag, mode, result, 1);
}

static int userfs_symlink(struct inode *dir, const char *name, int len,
			  const char *symname)
{
  int ret;
  up_preamble pre;
  upp_repl repl;
  upp_symlink_s snd;

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

  userfs_genpkt(dir->i_sb, &pre, up_symlink);
	
  snd.dir = U_INO(dir).handle;
  snd.name.elems = (char *)name;
  snd.name.nelem = len;
  snd.symname.elems = (char *)symname;
  snd.symname.nelem = strlen(symname);
  userfs_setcred(&snd.cred);
	
  if ((ret = userfs_doop(dir->i_sb, &pre, &repl,
                         upp_symlink_s, &snd, void, NULL)) != 0)
    printk("userfs_symlink: userfs_doop failed: %d\n", ret);
  else
    ret = -repl.errno;
	
  iput(dir);
  return ret;
}

static int userfs_unlink(struct inode *dir, const char *name, int nlen)
{
  up_preamble pre;
  upp_unlink_s snd;
  upp_repl rcv;
  int ret;

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

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

  snd.dir = U_INO(dir).handle;
  snd.name.nelem = nlen;
  snd.name.elems = (char *)name;

  if ((ret = userfs_doop(dir->i_sb, &pre, &rcv,
                         upp_unlink_s, &snd,
                         void, NULL)) != 0)
    printk("userfs_unlink: userfs_doop failed %d\n", ret);
  else
    ret = -rcv.errno;

  iput(dir);
  return ret;
}

int userfs_link(struct inode * oldinode, struct inode * dir,
                const char * name, int len)
{
  int ret;
  up_preamble pre;
  upp_repl repl;
  upp_link_s snd;
	
  if (!dir || !S_ISDIR(dir->i_mode))
    return -EBADF;

  if (!oldinode)
    {
      ret = -ENOENT;
      goto out;
    }

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

  snd.ofile = U_INO(oldinode).handle;
  snd.dir = U_INO(dir).handle;
  snd.name.elems = (char *)name;
  snd.name.nelem = len;

  if ((ret = userfs_doop(dir->i_sb, &pre, &repl,
                         upp_link_s, &snd, void, NULL)) != 0)
    printk("userfs_link: userfs_doop failed: %d\n", ret);
  else
    ret = -repl.errno;

 out:
  iput(dir);

  if (oldinode)
    iput(oldinode);

  return ret;
}

static void userfs_truncate(struct inode *ino)
{
  up_preamble pre;
  upp_truncate_s snd;
  upp_repl repl;
  int ret;
		
  userfs_genpkt(ino->i_sb, &pre, up_truncate);

  snd.file = U_INO(ino).handle;
  snd.size = ino->i_size;
	
  if ((ret = userfs_doop(ino->i_sb, &pre, &repl,
                         upp_truncate_s, &snd, void, NULL)) != 0)
    printk("userfs_truncate: userfs_doop failed %d\n", ret);
  else
    ret = repl.errno;
}

static int userfs_permission(struct inode *ino, int mask)
{
  up_preamble pre;
  upp_permission_s snd;
  upp_repl repl;
  int ret;
		
  userfs_genpkt(ino->i_sb, &pre, up_permission);

  userfs_setcred(&snd.cred);
  snd.mask = mask;
  snd.file = U_INO(ino).handle;
	
  if ((ret = userfs_doop(ino->i_sb, &pre, &repl,
                         upp_permission_s, &snd, void, NULL)) != 0)
    printk("userfs_permission: userfs_doop failed %d\n", ret);
  else
    ret = repl.errno;
	
  return ret == 0;
}

static int userfs_rename(struct inode *odir, const char *oname, int onlen,
			 struct inode *ndir, const char *nname, int nnlen)
{
  up_preamble pre;
  upp_rename_s snd;
  upp_repl repl;
  int ret;

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

  assert(odir->i_sb == ndir->i_sb);
	
  userfs_genpkt(odir->i_sb, &pre, up_rename);

  snd.odir = U_INO(odir).handle;
  snd.oname.elems = (char *)oname;
  snd.oname.nelem = onlen;

  snd.ndir = U_INO(ndir).handle;
  snd.nname.elems = (char *)nname;
  snd.nname.nelem = nnlen;

  if ((ret = userfs_doop(odir->i_sb, &pre, &repl,
                         upp_rename_s, &snd,
                         void, NULL)) != 0)
    printk("userfs_rename: userfs_doop failed %d\n", ret);
  else
    ret = -repl.errno;

  iput(odir);
  iput(ndir);
	
  return ret;
}

/* Ask process if operation is supported */
int userfs_probe(struct super_block *sb, up_ops op)
{
  up_preamble pre;
  upp_repl repl;
  int ret;

  userfs_genpkt(sb, &pre, op);

  pre.isreq = UP_ENQ;

  if ((ret = userfs_doop(sb, &pre, &repl, void, NULL, void, NULL)) != 0)
    {
      printk("userfs_probe: userfs_doop failed: %d\n", ret);
      return 0;
    }

  return repl.errno == 0;
}

/* disable things that process won't cope with */
struct inode_operations *userfs_probe_iops(struct super_block *sb)
{
  struct inode_operations *iops;

  iops = (struct inode_operations *)kmalloc(sizeof(*iops), GFP_KERNEL);

  *iops = userfs_inode_operations;

  iops->default_file_ops = userfs_probe_fops(sb);

  if (!userfs_probe(sb, up_lookup))
    iops->lookup = NULL;
  if (!userfs_probe(sb, up_create))
    {
      iops->create = NULL;
      iops->mkdir = NULL;
      iops->mknod = NULL;
    }
  if (!userfs_probe(sb, up_link))
    iops->link = NULL;
  if (!userfs_probe(sb, up_unlink))
    {
      iops->unlink = NULL;
      iops->rmdir = NULL;
    }
  if (!userfs_probe(sb, up_rename))
    iops->rename = NULL;
  if (!userfs_probe(sb, up_readlink))
    iops->readlink = NULL;
  if (!userfs_probe(sb, up_symlink))
    iops->symlink = NULL;
	
  /*
   * If the proc supports readlink but not followlink,
   * implement followlink in terms of readlink.
   * Is this really the Right Thing?
   */
  if (!userfs_probe(sb, up_followlink))
    {
      if (iops->readlink)
        iops->follow_link = userfs_followlink2;
      else
        iops->follow_link = NULL;
    }
  if (!userfs_probe(sb, up_permission))
    iops->permission = NULL;
  if (!userfs_probe(sb, up_truncate))
    iops->truncate = NULL;
	
  return iops;
}

struct inode_operations userfs_inode_operations =
{
  &userfs_file_operations,
  userfs_create,		/* create */
  userfs_lookup,		/* lookup */
  userfs_link,                  /* link */
  userfs_unlink,		/* unlink */
  userfs_symlink,		/* symlink */
  userfs_mkdir,                 /* mkdir */
  userfs_unlink,	       	/* rmdir */
  userfs_mknod,                 /* mknod */
  userfs_rename,		/* rename */
  userfs_readlink,              /* readlink */
  userfs_followlink,            /* follow_link */
  NULL,                         /* bmap */
  userfs_truncate,              /* truncate */
  userfs_permission             /* permission */
  };
