/*
 * IO to user process
 *
 * 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/sched.h>
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/userfs_fs.h>
#include <linux/userfs_fs_sb.h>
#include <linux/locks.h>
#include <linux/malloc.h>
#include <asm/segment.h>

#include "userfs.h"

#if 0
#define DBG
#define DB(x)	x
#else
/* #define NO_ASSERT */	/* Assertions arn't really *that* expensive */
#define DB(x)	;
#endif


extern int close_fp(struct file *);

/* Close channels, make everything fail later */
void userfs_close_chan(struct super_block *sb)
{
#ifdef MODULE
  if (U_SBP(sb) == NULL)
    return;
#endif

  /* Wake up all sleepers before killing connection */
  userfs_kiss(sb, UP_WALL, UP_FINISH);

  if (U_SB(sb).s_toproc)
    {
      close_fp(U_SB(sb).s_toproc);
      U_SB(sb).s_toproc = NULL;
    }

  if (U_SB(sb).s_fromproc)
    {
      close_fp(U_SB(sb).s_fromproc);
      U_SB(sb).s_fromproc = NULL;
    }
}

/*
 * Write to a file descriptor, like the sys_write
 *
 * The data buffer passed in is in kernel space.
 * The tricky part is that f_op->write needs a buffer in
 * current's user space, so we remap "fs" to the kernel data space,
 * so that kernel space appears as user space.
 *
 * This will keep writing until everything is done, unless there's
 * EOF or an error.
 */
static int userfs_send(struct file *file, char *data, unsigned int count)
{
  int ret;
  struct inode *inode;
  int wrote = 0;
  unsigned short fs;
  unsigned long blocked;
	
  if (!file)
    return -EBADF;

  if (!(file->f_mode & 2))
    return -EBADF;
  if (!file->f_op || !file->f_op->write)
    return -EINVAL;
  if (!count)
    return 0;

  if (count == 0)
    return 0;

  inode = file->f_inode;
	
  fs = get_fs();
  set_fs(get_ds());

  /* Filesystems are supposed to be uninterruptible */
  blocked = current->blocked;
  current->blocked = (unsigned long)-1;

  while(count > 0)
    {
      assert(file != NULL);
      assert(file->f_op != NULL);
      assert(file->f_op->write != NULL);

      ret = file->f_op->write(inode,file,(char *)data,count);

      if (ret <= 0)
        goto bad;
		
      count -= ret;
      data += ret;
      wrote += ret;
    }

  ret = wrote;
 bad:
  current->blocked = blocked;

  set_fs(fs);
  if (ret == 0)
    printk("userfs_send: f_op->write wrote 0 bytes, %d total\n",
           wrote);
	
  return ret;
}

/*
 * As above, except for reading into kernel space.
 *
 * Note that this is unlike a normal read() syscall, since it will
 * keep reading until count bytes are read, unless the read returns
 * error or EOF.
 */
static int userfs_recv(struct file *file, char *data, unsigned int count)
{
  int ret, read = 0;
  struct inode * inode;
  unsigned short fs;
  unsigned long blocked;

  if (!file)
    return -EBADF;

  if (!(file->f_mode & 1))
    return -EBADF;
  if (!file->f_op || !file->f_op->read)
    return -EBADF;
  if (!count)
    return 0;

  inode = file->f_inode;
	
  fs = get_fs();
  set_fs(get_ds());

  /* Filesystems are supposed to be uninterruptible */
  blocked = current->blocked;

  while(count > 0)
    {
      assert(file != NULL);
      assert(file->f_op != NULL);
      assert(file->f_op->read != NULL);

      current->blocked = (unsigned long)-1;

      ret = file->f_op->read(inode,file,(char *)data,count);

      if (ret < 0)
        goto bad;
		
      if (ret == 0)
        break;
		
      data += ret;
      count -= ret;
      read += ret;
    }

  ret = read;
 bad:
  current->blocked = blocked;
  set_fs(fs);
  return ret;
}

/*
 * Generate a normal request packet for sending, including managing
 * seqence numbers, etc
 */
void userfs_genpkt(struct super_block *sb, up_preamble *pre, up_ops op)
{
  pre->op = op;
  pre->version = UP_VERSION;
  pre->isreq = UP_REQ;
  pre->seq = U_SB(sb).s_seq++;
  pre->size = 0;
}

/*
 * Set lock on the userfs part of super block
 */
static void userfs_lock(struct super_block *sb)
{
  if (U_SB(sb).s_lock)
    {
      struct wait_queue wait = { current, NULL };
		
      add_wait_queue(&U_SB(sb).s_wait, &wait);
    repeat:
      current->state = TASK_UNINTERRUPTIBLE;
      if (U_SB(sb).s_lock)
        {
          schedule();
          goto repeat;
        }
      remove_wait_queue(&U_SB(sb).s_wait, &wait);
      current->state = TASK_RUNNING;
    }
  U_SB(sb).s_lock = 1;
}

/*
 * Release lock on userfs part of super block
 */
static void userfs_unlock(struct super_block *sb)
{
  U_SB(sb).s_lock = 0;
  wake_up(&U_SB(sb).s_wait);
}

/*
 * Add entry to list of waiting processes
 */
static void addwlist(struct userfs_sb_info *sbi, struct pwlist *ent)
{
  assert(ent != NULL);
  if (ent == NULL)
    return;

  ent->next = sbi->s_wlist;
  if (ent->next)
    ent->next->prev = ent;
	
  ent->prev = NULL;
  sbi->s_wlist = ent;

  sbi->s_nwproc++;

  /* Assert postconditions */
  assert(sbi->s_wlist == ent);
  assert(ent->prev == NULL);
  assert(ent->next == NULL || (ent->next->prev == ent));
  assert(sbi->s_nwproc != 0);
  assert(   (ent->next == NULL && sbi->s_nwproc == 1)
         || (ent->next != NULL && sbi->s_nwproc > 1));
}

/*
 * Remove from list of waiting processes
 */
static void remwlist(struct userfs_sb_info *sbi, struct pwlist *ent)
{
  assert(ent != NULL);
  if (ent == NULL)
    return;

  if (ent->next)
    {
      assert(ent->next != ent);
      ent->next->prev = ent->prev;
    }

  if (ent->prev)
    {
      assert(ent->prev != ent);
      ent->prev->next = ent->next;
    }
  else
    {
      assert(sbi->s_wlist == ent);
      sbi->s_wlist = ent->next;
    }

  ent->next = ent->prev = NULL;

  sbi->s_nwproc--;

  /* Assert some postconditions */
  assert(sbi->s_nwproc >= 0);
  assert(   (sbi->s_wlist == NULL && sbi->s_nwproc == 0)
         || (sbi->s_wlist != NULL && sbi->s_nwproc >= 1));
  assert(sbi->s_wlist == NULL || (sbi->s_wlist->prev == NULL));
}

#ifdef DBG
static void dumplist(struct userfs_sb_info *sbi)
{
  struct pwlist *lp;
  int i;

  for(lp = sbi->s_wlist, i = 0; lp != NULL; lp = lp->next, i++)
    printk("%d: seq %d pid=%d bed=%p why=%d more=%p repl.seq=%d\n", 
           i, lp->seq, lp->pid, lp->bed, lp->why, lp->more, lp->repl.seq);

  assert(i == sbi->s_nwproc);
}
#endif

/*
 * Find waiting entry, given sequence number
 */
static struct pwlist *findwent(struct userfs_sb_info *sbi, int seq)
{
  struct pwlist *lp;

  for(lp = sbi->s_wlist; lp != NULL; lp = lp->next)
    if (lp->seq == seq)
      return lp;

#ifdef DBG
  printk("findwent: seq=%d not found, %d things on list\n",
         seq, sbi->s_nwproc);
  dumplist(sbi);
#endif
  return NULL;
}

/*
 * Kiss one process awake.  Sets up the reason, the reply header
 * and the reply body in the list entry and wakes it up.
 */
static struct pwlist *kissone(struct super_block *sb, int seq, int why,
			      upp_repl *repl, char *more)
{
  struct pwlist *lp;
  struct userfs_sb_info *sbi = &U_SB(sb);
	
  if (seq == UP_WFIRST)
    lp = sbi->s_wlist;
  else
    lp = findwent(sbi, seq);
	
  if (lp == NULL)
    {
      DB(printk("kissone: nothing done for seq=%d\n",
                seq));
      return lp;
    }

  lp->why = why;

  remwlist(sbi, lp);

  if (repl != NULL)
    {
      lp->more = more;
      lp->repl = *repl;
    }
  else
    {
      assert(why != UP_REPL);
      lp->repl.size = 0;
      lp->repl.seq = -1;
      lp->more = NULL;
    }

  DB(printk("kissone: pid %d: kissing seq=%d, why=%d\n",
            current->pid, seq, why));

  assert(lp->bed != NULL);
  assert(lp->pid == -1);

  if (lp->bed != NULL)
    {
      lp->pid = current->pid;
      wake_up(&lp->bed);
      lp->bed = NULL;
    }

  return lp;
}

/*
 * Wake up one or all sleeping processes
 * If seq == UP_WFIRST, then the first process is woken.  Typically
 *  this would be why==UP_BATON, meaning that the process thus woken
 *  should take resposibility for reading the pipe
 * If seq == UP_WALL then all processes are woken.  It is not
 *  meaningful to have why anything but UP_FINISH.
 * This shouldn't be called if a specific process needs awakening
 */
struct pwlist *userfs_kiss(struct super_block *sb, int seq, int why)
{
  struct userfs_sb_info *sbi;
  struct pwlist *lp, *nxt;
	
  sbi = &U_SB(sb);

  if (seq == UP_WFIRST)
    {
      return kissone(sb, seq, why, NULL, NULL);
    }
	
  assert(why == UP_FINISH);
  assert(seq == UP_WALL);

  for(lp = sbi->s_wlist; lp != NULL; lp = nxt)
    {
      nxt = lp->next;

      kissone(sb, UP_WFIRST, why, NULL, NULL);		
    }

  assert(sbi->s_wlist == NULL);
  assert(sbi->s_nwproc == 0);

  return NULL;
}

/*
 * Suck stuff from the process until we get what we want,
 * then hand responsibilty over.
 *
 * This will either return when this process's reply comes, or
 * on error.
 */
static int suckin(struct super_block *sb, struct pwlist *ent)
{
  upp_repl repl;
  int replsz = sizeof_upp_repl(&repl);
  char *more;
  size_t more_sz;
  struct userfs_sb_info *sbi = &U_SB(sb);
  struct file *from = sbi->s_fromproc;
  int ret;
  unsigned char *p0, *p;

  p0 = (unsigned char *)get_free_page(GFP_KERNEL);

  ret = -1;                     /* general failure assumed */
	
  assert(sbi->s_canread == 1);

  for(;;)
    {
      more = NULL;
      more_sz = 0;
		
      if ((ret = userfs_recv(from, (char *)p0, replsz)) != replsz)
        {
          printk("suckin: %d: header recv failed: replsz=%d rcv=%d\n",
                 current->pid, replsz, ret);
          userfs_close_chan(sb);
          break;
        }

      p = decode_upp_repl(&repl, p0);

      DB(printk("suckin: pid %d, seq %d got packet seq %d, sz=%d\n",
                current->pid, ent->seq, repl.seq, repl.size));

      assert(repl.size <= MAX_KMALLOC);
      assert(repl.isreq == UP_REPL);

      if (repl.errno == 0 && repl.size != 0)
        {
          more = (char *)kmalloc(repl.size, GFP_KERNEL);
          more_sz = repl.size;

          if ((ret = userfs_recv(from, more, more_sz)) != repl.size)
            {
              printk("suckin: body recv failed: %d\n",
                     ret);
              userfs_close_chan(sb);
              break;
            }
        }

      assert(   (repl.size == 0 && more == NULL)
             || (repl.size > 0 && more != NULL));

      if (repl.seq == ent->seq)
        {
          /* For us! */

          if (sbi->s_nwproc != 0)
            userfs_kiss(sb, UP_WFIRST, UP_BATON);
          else
            sbi->s_canread = 0;

          ent->more = more;
          ent->why = UP_REPL;
          ent->repl = repl;
          ret = 0;
          more = NULL;

          DB(printk("suckin: sucker found its own (seq=%d), more=%p\n",
                    ent->seq, ent->more));
          break;
        }
      else
        {
          DB(printk("suckin: %d kissing seq %d\n",
                    ent->seq, repl.seq));
          kissone(sb, repl.seq, UP_REPL, &repl, more);
        }
    }

  if (more != NULL)
    kfree_s(more, more_sz);

  free_page((unsigned long)p0);
	
  return ret;
}

/*
 * Wait for the reply to return, mostly sleeping.
 *
 * There are two cases:
 *	1. We wait on the pipe for incoming packets.
 *		If a packet arrives that's not for us, then
 *		we wake up the process that is responsible
 *		for it.
 *		If its for us, then we accept it and pass
 *		responsibility on to another process to cope
 *		with the incoming.  If there are no other
 *		waiting processes, then there should be
 *		no outstanding replies.
 *	2. We wait on the list for something to happen.
 *		There are 3 reasons we can get woken:
 *		- Our reply arrvies.
 *		- We become resposible for reading
 *		- We unmount, and the reply is dropped
 *		If we get a reply or umount then remove ourselves
 *		from the list
 * We return 0 on success, 1 if we were woken with UP_FINISHED
 */
static int userfs_sleep(struct super_block *sb, int seq, upp_repl *repl,
			 char **data)
{
  struct pwlist ent;
  struct userfs_sb_info *sbi;
  int ret = 0;

  sbi = &U_SB(sb);

  ent.seq = seq;
  ent.more = NULL;
  ent.why = 0;
  ent.bed = NULL;
  ent.pid = -1;
  ent.repl.size = 0;
  ent.repl.seq = -1;

  if (sbi->s_canread == 0)
    {
      DB(printk("userfs_sleep: reading, pid=%d seq=%d\n",
                current->pid, seq));

      sbi->s_canread = 1;

      ret = suckin(sb, &ent);
    }
  else
    {
      addwlist(sbi, &ent);

      DB(printk("userfs_sleep: sleeping on list, pid=%d seq=%d\n",
                current->pid, ent.seq));

      ent.repl.seq = -1;

      sleep_on(&ent.bed);

      DB(printk("userfs_sleep: pid %d, seq %d woken by pid %d\n",
                current->pid, ent.seq, ent.pid));

      assert(ent.pid != -1);
      assert(ent.pid != current->pid);

      switch(ent.why)
        {
        case UP_BATON:          /* We were woken to be reader */
          assert(sbi->s_canread == 1);
          DB(printk("userfs_sleep: pid %d, seq %d taking on reader job\n",
                    current->pid, ent.seq));

          ret = suckin(sb, &ent);
          break;
			
        case UP_REPL:           /* Our prince arrived */
          DB(printk("userfs_sleep: pid %d woken with %d bytes in %p\n",
                    current->pid, ent.repl.size, ent.more));
          break;

        case UP_FINISH:       /* We were sleeping and the connection failed */
          printk("userfs_sleep: got UP_FINISH from %d\n",
                 ent.pid);
          ret = -EPROTO;
          break;

        default:                /* Bad reason for waking */
          printk("userfs_sleep: woke with bad why=%d from pid %d\n",
                 ent.why, ent.pid);
          ret = -EPROTO;
          break;
        }
    }

  if (ret != 0)
    printk("userfs_sleep: failed with %d\n", ret);
  else
    {
      *data = ent.more;
      *repl = ent.repl;
    }

  return ret;
}

/*
 * Deal with a normal send/reply transaction.
 *
 * This writes out the request in two parts.  This locks the super
 * block to prevent other operations from going out at the same
 * time.  After that we sleep until the reply comes in.
 * Decode it and return.
 */
int __userfs_doop(struct super_block *sb,
		  up_preamble *pre, upp_repl *repl,
		  encode_func se, void *send,
		  decode_func rd, void *recv,
		  sizeof_func sso, sizeof_func rso)
{
  int ret;
  unsigned char *p, *p0;
  char *more = NULL;
  struct file *to = U_SB(sb).s_toproc;

  p = p0 = (unsigned char *)get_free_page(GFP_KERNEL);

  assert(pre->version == UP_VERSION);
  assert(pre->size <= MAX_KMALLOC);
  assert(pre->isreq == UP_REQ || pre->isreq == UP_ENQ);
	
  pre->size = (*sso)(send);
  assert(pre->size <= MAX_KMALLOC);

  /* Atomically send the packet */
  userfs_lock(sb);

  p = encode_up_preamble(pre, p0);
  if ((ret = userfs_send(to, (char *)p0, p-p0)) <= 0)
    {
      printk("userfs_doop: header send failed: %d\n", ret);
      userfs_close_chan(sb);
      free_page((long)p0);
      userfs_unlock(sb);
      return ret;
    }

  if (pre->size > 0)
    {
      p = (*se)(send, p0);
      if ((ret = userfs_send(to, (char *)p0, p-p0)) <= 0)
        {
          printk("userfs_doop: body send failed: %d\n", ret);
          userfs_close_chan(sb);
          free_page((long)p0);
          userfs_unlock(sb);
          return ret;
        }
      assert(p - p0 == pre->size);
    }

  userfs_unlock(sb);

  /* Sleep until a prince comes */
  if ((ret = userfs_sleep(sb, pre->seq, repl, &more)) == 0)
    {
      if (more != NULL)
        {
          (*rd)(recv, (unsigned char *)more);
          assert(repl->size == (*rso)(recv));
          DB(printk("userfs_doop: more=%p\n", more));
          kfree_s(more, repl->size);
        }
    }
  else
    printk("userfs_doop: userfs_sleep failed with %d\n", ret);
	
  free_page((long)p0);

  return ret;
}
