/* Generic descriptor system calls
   Copyright (C) 1991 Free Software Foundation

This file is part of the GNU Hurd.

The GNU Hurd is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 1, or (at your option)
any later version.

The GNU Hurd 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with the GNU Hurd; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

/* Written by Michael I. Bushnell.  */

#include "mach.h"
#include <cthreads.h>
#include <gnu/types.h>
#include <gnu/param.h>
#include <gnu/errno.h>
#include <gnu/uio.h>
#include <gnu/ioctl.h>
#include <gnu/signal.h>
#include <hurd/hurd_types.h>

#include "io.h"

#include "alix.h"
#include "alix_io.h"
#include "alix_proc.h"
#include <machine/alix_machdep.h>

/* Missing: */


/* SYSCALL:  Read from a file descriptor.  */
int
read (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      caddr_t buf;
      int nbytes;
    }
  *args = ap;
  
  int err;
  u_int cc;
  caddr_t buf;
  
  mutex_lock (&iolock);
  if (args->fd < 0 || args->fd > dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      return EBADF;
    }

  if (args->nbytes <= INBAND_MAX_DATA)
    err = h2ae (io_read_inband (dtable[args->fd].ioserver,
				ctty_check (args->fd), pid, pgrp, 
				args->buf, &cc, -1, args->nbytes));
  else
    {
      err = h2ae (io_read_outofband (dtable[args->fd].ioserver,
				     ctty_check (args->fd), pid, pgrp, &buf,
				     &cc, -1, args->nbytes));
      if (!err)
	err = copyout (buf, args->buf, cc);
      vm_deallocate (mach_task_self (), (int)buf, cc);
    }
  mutex_unlock (&iolock);
  *ret1 = cc;
  return err;
}


/* SYSCALL: Write to a file descriptor. */
int
write (void *ap, int *ret1, int *ret2)
{
  struct 
    {
      int fd;
      caddr_t buf;
      int nbytes;
    }
  *args = ap;
  
  int err;
  int cc;

  mutex_lock (&iolock);
  if (args->fd < 0 || args->fd > dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  
  if (args->nbytes <= INBAND_MAX_DATA)
    err = h2ae (io_write_inband (dtable[args->fd].ioserver,
				 ctty_check (args->fd), pid, pgrp,
				 args->buf, args->nbytes, -1, &cc));
  else
    err = h2ae (io_write_outofband (dtable[args->fd].ioserver,
				    ctty_check (args->fd), pid, pgrp,
				    args->buf, args->nbytes, -1, &cc));
  mutex_unlock (&iolock);
  *ret1 = cc;
  return err;
}

/* Scatter I/O reading from a file descriptor. */
int
readv (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      struct iovec *iov;
      int iovcnt;
    }
  *args = ap;
  int err;
  u_int cc;
  struct iovec *iov;
  int totread, i;
  char *buf, *bp;

  if (args->iovcnt <= 0 || args->iovcnt > MAXIOV)
    return EINVAL;

  iov = alloca (sizeof (struct iovec) * args->iovcnt);
  if (err = copyin (args->iov, iov, sizeof (struct iovec) * args->iovcnt))
    return err;
  
  totread = 0;
  for (i = 0; i < args->iovcnt; i++)
    {
      if (iov[i].iov_len < 0)
	return EINVAL;

      /* Check for overflow */
      if (0x7fffffff - totread < iov[i].iov_len)
	return EINVAL;
      
      totread += iov[i].iov_len;
    }

  if (totread <= INBAND_MAX_DATA)
    buf = alloca (totread);

  mutex_lock (&iolock);
  if (args->fd < 0 || args->fd > dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      return EBADF;
    }

  if (totread <= INBAND_MAX_DATA)
    err = h2ae (io_read_inband (dtable[args->fd].ioserver,
				ctty_check (args->fd), pid, pgrp, buf, &cc,
				-1, totread));
  else
    err = h2ae (io_read_outofband (dtable[args->fd].ioserver,
				   ctty_check (args->fd), pid, pgrp, 
				   &buf, &cc, -1, totread));
  
  mutex_unlock (&iolock);
  if (!err)
    {
      bp = buf;
      *ret1 = cc;
      for (i = 0; cc, i < args->iovcnt; i++)
	{
	  if (cc < iov[i].iov_len)
	    iov[i].iov_len = cc;
	  if (err = copyout (buf, iov[i].iov_base, iov[i].iov_len))
	    break;
	  cc -= iov[i].iov_len;
	  bp += iov[i].iov_len;
	}

      if (totread > INBAND_MAX_DATA)
	vm_deallocate (mach_task_self (), (int)buf, *ret1);
    }
  return err;
}

/* Gather I/O writing to a file descriptor.  */
int
writev (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      struct iovec *iov;
      int iovcnt;
    }
  *args = ap;
  void *buf, *bp;
  struct iovec *iov;
  int totwrite, i;
  int cc;
  int err;

  if (args->iovcnt <= 0 || args->iovcnt > MAXIOV)
    return EINVAL;
  
  iov = alloca (sizeof (struct iovec) * args->iovcnt);
  if (err = copyin (args->iov, iov, sizeof (struct iovec) * args->iovcnt))
    return err;
  
  totwrite = 0;
  for (i = 0; i < args->iovcnt; i++)
    {
      if (iov[i].iov_len < 0)
	return EINVAL;
      if (0x7fffffff - totwrite < iov[i].iov_len)
	return EINVAL;
      totwrite += iov[i].iov_len;
    }

  if (totwrite <= INBAND_MAX_DATA)
    buf = alloca (totwrite);
  else
    (void) vm_allocate (mach_task_self (), (u_int *)&buf, totwrite, 1);

  bp = buf;
  for (i = 0; i < args->iovcnt; i++)
    {
      if (err = copyin (iov[i].iov_base, bp, iov[i].iov_len))
	break;
      bp += iov[i].iov_len;
    }
  if (err)
    {
      if (totwrite > INBAND_MAX_DATA)
	vm_deallocate (mach_task_self (), (int)buf, totwrite);
      return err;
    }
  
  mutex_lock (&iolock);
  if (args->fd < 0 || args->fd > dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      if (totwrite > INBAND_MAX_DATA)
	vm_deallocate (mach_task_self (), (int)buf, totwrite);
      return EBADF;
    }
  
  if (totwrite <= INBAND_MAX_DATA)
    err = h2ae (io_write_inband (dtable[args->fd].ioserver,
				 ctty_check (args->fd), pid, pgrp, buf,
				 totwrite, -1, &cc));
  else
    {
      err = h2ae (io_write_outofband (dtable[args->fd].ioserver,
				      ctty_check (args->fd), pid, pgrp, buf,
				      totwrite, -1, &cc));
      vm_deallocate (mach_task_self (), (int)buf, totwrite);
    }
  
  mutex_unlock (&iolock);
  return err;
}


int
ioctl (void *ap, int *ret1, int *ret2)
{
  struct
    {
      int fd;
      int request;
      void *buffer;
    }
  *args = ap;
  int err = 0;
  void *buf;
  int dir = args->request & IOC_DIRMASK;
  u_int len = IOCPARM_LEN (args->request);
  io_t server;
  int isctty;
  
  mutex_lock (&iolock);
  if (args->fd < 0 || args->fd > dtablesize || !dtable[args->fd].ioserver)
    {
      mutex_unlock (&iolock);
      return EBADF;
    }
  server = dtable[args->fd].ioserver;

  if ((dir != IOC_VOID && !(dir & IOC_INOUT))
      || ((dir & IOC_VOID) && (dir & IOC_INOUT)))
    {
      mutex_unlock (&iolock);
      return ENOTTY;
    }
  
  buf = alloca (len);
  
  if (dir & IOC_IN)
    {
      err = copyin (args->buffer, buf, len);
      if (err)
	{
	  mutex_unlock (&iolock);
	  return err;
	}
    }

  isctty = ctty_check (args->fd);
  switch (dir)
    {
    case IOC_VOID:
      err = h2ae (io_ioctl_x (server, isctty, pid, pgrp, args->request));
      break;
      
    case IOC_IN:
      err = h2ae (io_ioctl_w (server, isctty, pid, pgrp, args->request,
			      buf, len));
      break;
      
    case IOC_OUT:
      err = h2ae (io_ioctl_r (server, isctty, pid, pgrp, args->request,
			      buf, &len));
      break;

    case IOC_INOUT:
      err = h2ae (io_ioctl_rw (server, isctty, pid, pgrp, args->request,
			       buf, len, buf, &len));
      break;

    default:
      err = ENOTTY;
      break;
    }
  
  mutex_unlock (&iolock);

  if (!err && (dir & IOC_OUT))
    err = copyout (buf, args->buffer, len);
      
  return err;
}
