/*
 *  This file is part of ixemul.library for the Amiga.
 *  Copyright (C) 1991, 1992  Markus M. Wild
 *  Portions Copyright (C) 1994 Rafael W. Luebbert
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Library General Public
 *  License as published by the Free Software Foundation; either
 *  version 2 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Library General Public License for more details.
 *
 *  You should have received a copy of the GNU Library General Public
 *  License along with this library; if not, write to the Free
 *  Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  $Id: pipe.c,v 1.4 1994/06/19 15:14:19 rluebbert Exp $
 *
 *  $Log: pipe.c,v $
 *  Revision 1.4  1994/06/19  15:14:19  rluebbert
 *  *** empty log message ***
 *
 *  Revision 1.2  1992/07/04  19:21:08  mwild
 *  (finally..) fix the bug which could cause pipe readers/writers to deadlock
 *
 * Revision 1.1  1992/05/14  19:55:40  mwild
 * Initial revision
 *
 */

#define _KERNEL
#include "ixemul.h"
#include "kprintf.h"

#include <sys/ioctl.h>
#include <string.h>
#include "select.h"

/* information for the temporary implementation of pipes.
   PIPE: has the big disadvantage that it blocks in the most unpleasent
   situations, and doesn't send SIGPIPE to processes that write on
   readerless pipes. Unacceptable for this library ;-)) */

#define PIPE_SIZE	5120

struct tmp_pipe {
  u_short	tp_flags;		/* see below */
  u_char	tp_buffer[PIPE_SIZE];
  u_char	*tp_reader, *tp_writer;	/* buffer pointers.
					   when tp_reader==tp_writer, no data
					   is available */
  struct Task   *tp_task;		/* task that waits for select */
};

#define TPF_NO_READER	(1<<0)
#define TPF_NO_WRITER	(1<<1)
#define TPF_LOCKED	(1<<2)
#define TPF_WANT_LOCK	(1<<3)

static int __pread(), __pwrite(), __pselect(), __pioctl(), __pclose();
static struct tmp_pipe *__pinit();

static inline void
__get_pipe (struct file *f)
{
  struct tmp_pipe *tp = f->f_tp;

retry:  
  Forbid ();
  for (;;)
    {
      if (!(tp->tp_flags & TPF_LOCKED))
        {
          tp->tp_flags &= ~TPF_WANT_LOCK;
          tp->tp_flags |= TPF_LOCKED;
          /* got it ! */
          break;
	}
      tp->tp_flags |= TPF_WANT_LOCK;
      KPRINTF_DISABLED (("__get_pipe: going to sleep\n"));
      if (ix_sleep ((caddr_t)&tp->tp_flags, "get_pipe") < 0)
        {
	  KPRINTF_DISABLED (("__get_pipe: interrupted\n"));
	  Permit ();
	  setrun (FindTask (0));
          goto retry;
        }
      KPRINTF_DISABLED (("__get_pipe: back from sleep (not interrupted)\n"));
      /* have to always recheck whether we really got the lock */
    }
  Permit ();      
}

static inline void
__release_pipe (struct file *f)
{
  struct tmp_pipe *tp = f->f_tp;

  Forbid ();
  if (tp->tp_flags & TPF_WANT_LOCK)
    ix_wakeup ((u_int)&tp->tp_flags);
    
  tp->tp_flags &= ~(TPF_WANT_LOCK|TPF_LOCKED);
  Permit ();
}


int
pipe (int pv[2])
{
  struct file *f1, *f2;
  struct tmp_pipe *tp;
  int res, err, omask;
  
  omask = syscall (SYS_sigsetmask, ~0);
  res = -1; 
  err = EMFILE;
  if ((tp = __pinit ()))
    {
      if (! falloc (&f1, pv))
        {
          if (! falloc (&f2, pv+1))
            {
	      f1->f_tp     = tp;
	      f1->f_stb.st_mode = 0666 | S_IFCHR;
	      f1->f_stb.st_size = PIPE_SIZE;
	      f1->f_stb.st_blksize = 512;
	      f1->f_flags  = FREAD;
	      f1->f_type   = DTYPE_PIPE;
	      f1->f_read   = __pread;
	      f1->f_write  = 0;
	      f1->f_ioctl  = __pioctl;
	      f1->f_close  = __pclose;
	      f1->f_select = __pselect;

	      f2->f_tp     = tp;
	      f2->f_stb.st_mode = 0666 | S_IFCHR;
	      f2->f_stb.st_size = PIPE_SIZE;
	      f2->f_stb.st_blksize = 512;
	      f2->f_flags  = FWRITE;
	      f2->f_type   = DTYPE_PIPE;
	      f2->f_read   = 0;
	      f2->f_write  = __pwrite;
	      f2->f_ioctl  = __pioctl;
	      f2->f_close  = __pclose;
	      f2->f_select = __pselect;

	      res = err =0;
	      goto ret;
	    }
	  f1->f_count = 0;
	}

      kfree (tp);
    }

ret:
  syscall (SYS_sigsetmask, omask);

  errno = err;
  KPRINTF_DISABLED (("&errno = %lx, errno = %ld\n", &errno, errno));
  return res;
}

static struct tmp_pipe *
__pinit (void)
{
  struct tmp_pipe *tp = (struct tmp_pipe *) kmalloc (sizeof (*tp));
  
  if (tp)
    {
      tp->tp_flags = 0;
      tp->tp_reader = tp->tp_writer = tp->tp_buffer;
      tp->tp_task = 0;
    }

  return tp;
}

static int
__pclose (struct file *f)
{
  ix_lock_base ();

  f->f_count--;

  if (f->f_count == 0)
    {
      if (f->f_read)
        f->f_tp->tp_flags |= TPF_NO_READER;
      else
	f->f_tp->tp_flags |= TPF_NO_WRITER;
	
      if ((f->f_tp->tp_flags & (TPF_NO_READER|TPF_NO_WRITER)) ==
	  (TPF_NO_READER|TPF_NO_WRITER))
	kfree (f->f_tp);
      else
	ix_wakeup ((u_int)f->f_tp);
    }

  ix_unlock_base ();

  return 0;
}

static int
__pread (struct file *f, char *buf, int len)
{
  int omask = syscall (SYS_sigsetmask, ~0);
  int err = errno;
  int really_read = 0;
  struct tmp_pipe *tp = f->f_tp;

  __get_pipe (f);

  while (len)
    {
      if (tp->tp_reader == tp->tp_writer)
	{
	  KPRINTF_DISABLED (("__pread: len == %ld, buffer full\n", len));
	  if (tp->tp_flags & TPF_NO_WRITER)
	    {
	      KPRINTF_DISABLED (("__pread: EOF\n"));
	      err = 0;
	      break;
	    }
	
	  if (f->f_flags & FNDELAY)
	    {
	      if (! really_read)
		{
		  really_read = -1;
		  err = EAGAIN;
		}
	      break;
	    }
	  else if (really_read)
	    {
	      err = 0;
	      break;
	    }
	  else
	    {
	      int sleep_rc;
	      KPRINTF_DISABLED (("__pread: going to sleep.\n"));
	      /* wait for something to be read or all readers to close */
	      Forbid ();
	      /* sigh.. Forbid() is necessary, or the other end may change
	         the pipe, and in the worst case also settle for sleep(), and
	         there it is.. deadlock.. */
	      __release_pipe (f);

	      /* make write interruptible */
	      syscall (SYS_sigsetmask, omask);
	      sleep_rc = ix_sleep ((caddr_t)tp, "pwrite");
	      Permit ();
	      if (sleep_rc < 0)
	        setrun (FindTask (0));
	      omask = syscall (SYS_sigsetmask, ~0);

	      __get_pipe (f);
	      continue;		/* retry */
	    }
	}
      else
	{
	  /* okay, there's something to read from the pipe */
	  if (tp->tp_reader > tp->tp_writer)
	    {
	      /* read till end of buffer and wrap around */
	      int avail = PIPE_SIZE - (tp->tp_reader - tp->tp_buffer);
	      int do_read = len < avail ? len : avail;

	      /* KPRINTF_DISABLED (("__pread-1: reading %ld bytes.\n", do_read)); */

	      really_read += do_read;
	      bcopy (tp->tp_reader, buf, do_read);
	      len -= do_read;
	      buf += do_read;
	      tp->tp_reader += do_read;
	      if (tp->tp_reader - tp->tp_buffer == PIPE_SIZE)
		/* wrap around */
		tp->tp_reader = tp->tp_buffer;
	    }
	  if (len && tp->tp_reader < tp->tp_writer)
	    {
	      int avail = tp->tp_writer - tp->tp_reader;
	      int do_read = len < avail ? len : avail;

	      /* KPRINTF_DISABLED (("__pread-2: reading %ld bytes.\n", do_read)); */

	      really_read += do_read;
	      bcopy (tp->tp_reader, buf, do_read);
	      tp->tp_reader += do_read;
	      len -= do_read;
	      buf += do_read;
	    }
	}

      ix_wakeup ((u_int)tp);
    }

  __release_pipe (f);
 
  syscall (SYS_sigsetmask, omask);
  errno = err;
  KPRINTF_DISABLED (("&errno = %lx, errno = %ld\n", &errno, errno));
  return really_read;
}


static int
__pwrite (struct file *f, char *buf, int len)
{
  int  omask = syscall (SYS_sigsetmask, ~0);
  int err = errno;
  int really_written = 0;
  struct tmp_pipe *tp = f->f_tp;

  __get_pipe (f);

  while (len)
    {
      if (tp->tp_flags & TPF_NO_READER)
	{
	  KPRINTF_DISABLED (("__pwrite: SIGPIPE\n"));
	  really_written = -1;
	  err = EPIPE;
	  /* this is something no `real' Amiga pipe handler will do ;-)) */
	  _psignal (FindTask (0), SIGPIPE);
	  break;
        }
	
      /* buffer full ?? */
      if (tp->tp_reader == tp->tp_writer + 1
	  || (tp->tp_reader == tp->tp_buffer 
	      && tp->tp_writer == tp->tp_buffer + PIPE_SIZE - 1))
	{
	  KPRINTF_DISABLED (("__pwrite: buffer full, len == %ld\n", len));
	  if (f->f_flags & FNDELAY)
	    {
	      if (! really_written)
	        {
	          really_written = -1;
	          err = EAGAIN;
	        }
	      break;
	    }
	  else
	    {
	      int sleep_rc;
	      KPRINTF_DISABLED (("__pwrite: going to sleep\n"));
	      /* wait for something to be read or all readers to close */
	      Forbid ();
	      /* sigh.. Forbid() is necessary, or the other end may change
	         the pipe, and in the worst case also settle for sleep(), and
	         there it is.. deadlock.. */
	      __release_pipe (f);

	      /* make write interruptible */
	      syscall (SYS_sigsetmask, omask);
	      sleep_rc = ix_sleep ((caddr_t)tp, "pwrite");
	      Permit ();
	      if (sleep_rc < 0)
	        setrun (FindTask (0));
	      omask = syscall (SYS_sigsetmask, ~0);

	      __get_pipe (f);
	      continue;		/* retry */
	    }
	}
      else
	{
	  /* okay, there's some space left to write to the pipe */

	  if (tp->tp_writer >= tp->tp_reader)
	    {
	      /* write till end of buffer */
	      int avail = PIPE_SIZE - 1 - (tp->tp_writer - tp->tp_buffer);
	      int do_write;

	      if (tp->tp_reader > tp->tp_buffer)
	        avail++;
	      do_write = len < avail ? len : avail;

	      /* KPRINTF_DISABLED (("__pwrite-1: writing %ld bytes.\n", do_write)); */
	      really_written += do_write;
	      bcopy (buf, tp->tp_writer, do_write);
	      len -= do_write;
	      buf += do_write;
	      tp->tp_writer += do_write;
	      if (tp->tp_writer - tp->tp_buffer == PIPE_SIZE)
	        tp->tp_writer = tp->tp_buffer;
	    }

	  if (tp->tp_writer < tp->tp_reader - 1)
	    {
	      int avail = tp->tp_reader - tp->tp_writer - 1;
	      int do_write = len < avail ? len : avail;

	      /* KPRINTF_DISABLED (("__pwrite-2: writing %ld bytes.\n", do_write)); */
	      really_written += do_write;
	      bcopy (buf, tp->tp_writer, do_write);
	      tp->tp_writer += do_write;
	      len -= do_write;
	      buf += do_write;
	    }
	  Forbid();
	  if (tp->tp_task)
	    Signal(tp->tp_task, 1UL << u.u_pipe_sig);
	  Permit();
	}
	
      ix_wakeup ((u_int)tp);
    }

  __release_pipe (f);

  syscall (SYS_sigsetmask, omask);
  errno = err;
  KPRINTF_DISABLED (("&errno = %lx, errno = %ld\n", &errno, errno));
  return really_written;
}

static int
__pselect (struct file *f, int select_cmd, int io_mode,
	   fd_set *ignored, u_long *also_ignored)
{
  struct tmp_pipe *tp = f->f_tp;

  SetSignal(0, 1UL << u.u_pipe_sig);  /* reset signal */
  tp->tp_task = NULL;
  if (select_cmd == SELCMD_CHECK || select_cmd == SELCMD_POLL)
    {
      /* we support both, read and write checks (hey, something new ;-)) */
      if (io_mode == SELMODE_IN)
	return tp->tp_reader != tp->tp_writer;
      else if (io_mode == SELMODE_OUT)
	return !(tp->tp_reader == tp->tp_writer + 1
		 || (tp->tp_reader == tp->tp_buffer 
		     && tp->tp_writer == tp->tp_buffer + PIPE_SIZE - 1));
    }

  tp->tp_task = FindTask(0);
  return 1UL << u.u_pipe_sig;
}

static int
__pioctl (struct file *f, unsigned int cmd, unsigned int inout,
          unsigned int arglen, unsigned int arg)
{
  int omask;
  int result = 0;
  struct tmp_pipe *tp = f->f_tp;
  
  omask = syscall (SYS_sigsetmask, ~0);
  __get_pipe (f);

  switch (cmd)
    {
    case FIONREAD:
      {
	unsigned int *pt = (unsigned int *)arg;
	if (tp->tp_reader < tp->tp_writer)
	  *pt = tp->tp_writer - tp->tp_reader;
	else if (tp->tp_reader > tp->tp_writer)
	  *pt = PIPE_SIZE - (tp->tp_reader - tp->tp_writer);
	else
	  *pt = 0;
	result = 0;
        break;
      }

    case FIONBIO:
      {
	result = f->f_flags & FNDELAY ? 1 : 0;
	if (*(unsigned int *)arg)
	  f->f_flags |= FNDELAY;
	else
	  f->f_flags &= ~FNDELAY;
	/* I didn't find it documented in a manpage, but I assume, we
	 * should return the former state, not just zero.. */
	break;
      }

    case FIOASYNC:
      {
	/* DOESN'T WORK YET */

	int flags = *(unsigned long*)arg;
	result = f->f_flags & FASYNC ? 1 : 0;
	if (flags)
	  f->f_flags |= FASYNC;
	else
	  f->f_flags &= ~FASYNC;

	/* ATTENTION: have to call some function here in the future !!! */

	/* I didn't find it documented in a manpage, but I assume, we
	 * should return the former state, not just zero.. */
	break;
      }

    case FIOCLEX:
    case FIONCLEX:
    case FIOSETOWN:
    case FIOGETOWN:
      /* this is no error, but nevertheless we don't take any actions.. */      
      result = 0;
      break;
    }

  __release_pipe (f);
  syscall (SYS_sigsetmask, omask);
  return result;
}
