/* User side for terminal driver
   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.  */


io_write_inband (struct termuser *term,
		 mach_port_t reply_port,
		 int isctty,
		 int pid, 
		 int pgrp,
		 char *data,
		 u_int datalen,
		 int offset,
		 int *cc)
{
  int amt;
  struct user_request *ur;
  
  if (!(term->openmode & FS_LOOKUP_WRITE))
    return POSIX_EBADF;
  
  if (termstate.c_lflag & TOSTOP) 
    {
      switch (ctty_check (isctty, pid, pgrp, SIGTTOU))
	{
	case STOPPED:
	case CATCH:
	  enqueue_abort (reply_port);
	  return MIG_NO_REPLY;
	case IGNBLK:
	case NOSIG:
	  break;
	case ORPHANED:
	  return POSIX_EIO;
	}
    }      
  
  /* If we can immediately satisfy the request, do so */
  if (QUEUE_SIZE - (outqp - outq) >= datalen)
    {
      bcopy (data, outqp, datalen);
      outqp += datalen;
      *cc = datalen;
      return POSIX_SUCCESS;
    }
  
  amt = QUEUE_SIZE - (outqp - outq);

  bcopy (data, outqp, amt);
  outqp += amt;
  if (termuser->openmode & FS_LOOKUP_NDELAY)
    {
      if (amt > 0)
	{
	  *cc = amt;
	  return POSIX_SUCCESS;
	}
      else
	return POSIX_EAGAIN;
    }
  else
    {
      ur = malloc (sizeof (struct user_request));
      ur->next = uwrites;
      uwrites = ur;
      ur->term = term;
      ur->reply_port = reply_port;
      ur->isctty = isctty;
      ur->pid = pid;
      ur->pgrp = pgrp;
      ur->buffer = malloc (datalen - amt);
      ur->bp = ur->buffer;
      bcopy (data + amt, ur->buffer, datalen - amt);
      ur->nbytes = datalen - amt;
      return MIG_NO_REPLY;
    }
}

error_t
io_read_inband (struct termuser *term,
		mach_port_t reply_port,
		int isctty,
		int pid,
		int pgrp,
		char *data,
		u_int *datalen,
		int offset,
		int readsize)
{
  char *q, **qpp;
  int amt;
  struct user_request *ur;
  
  if (!(term->openmode & FS_LOOKUP_READ))
    return POSIX_EBADF;
  
  switch (cttycheck (isctty, pid, pgrp, SIGTTIN))
    {
    case STOPPED:
    case CATCH:
      enqueue_abort (reply_port);
      return MIG_NO_REPLY;
      
    case IGNBLK:
    case ORPHANED:
      return POSIX_EIO;
      
    case NOSIG:
      break;
    }

  /* This currently does the Posix timers incorrectly for
     non-canonical mode! XXX */

  /* Find out which input queue to use.  */
  if (termstate.c_lflag & ICANON)
    {
      q = readq;
      qpp = &readqp;
    }
  else
    {
      q = lineq;
      qpp = &lineqp;
    }
  
  /* Find out how much we can read.  */
  amt = *qpp - q;
  if (amt > readsize)
    amt = readsize;
  
  /* Read it.  */
  if (amt > 0)
    {
      bcopy (q, data, amt);
      bcopy (*qpp, q, (*qpp - q) - amt);
      *qpp = q + (*qpp - q) - amt;
      *datalen = amt;
      return POSIX_SUCCESS;
    }

  /* Block or return EAGAIN */
  if (term->openmode & FS_LOOKUP_NDELAY)
    return POSIX_EAGAIN;
  else
    {
      ur = malloc (sizeof (struct user_request));
      ur->next = ureads;
      ureads = ur;
      ur->term = term;
      ur->reply_port = reply_port;
      ur->isctty = isctty;
      ur->pid = pid;
      ur->pgrp = pgrp;
      ur->buffer = 0;
      ur->bp = 0;
      ur->nbytes = readsize;
      return MIG_NO_REPLY;
    }
}

error_t
io_getcttyport (struct termuser *term,
		mach_port_t *port)
{
  if (!(term & (FS_LOOKUP_READ | FS_LOOKUP_WRITE)))
    return POSIX_EBADF;
  *port = cttyid;
  return POSIX_SUCCESS;
}

error_t
io_ioctl_x (struct termuser *term,
	    int isctty,
	    int pid,
	    int pgrp,
	    int code)
{
  return process_ioctl (code, 0, isctty, pid, pgrp);
}

error_t
io_ioctl_r (struct termuser *term,
	    int isctty,
	    int pid,
	    int pgrp,
	    int code,
	    char *data,
	    u_int *datalen)
{
  error_t err;
  if (*datalen < IOCPARM_LEN (code))
    return POSIX_EINVAL;
  err = process_ioctl (code, data, isctty, pid, pgrp);
  if (!err)
    *datalen = IOCPARM_LEN (code);
  return err;
}

error_t
io_ioctl_w (struct termuser *term,
	    int isctty,
	    int pid,
	    int pgrp,
	    int code,
	    char *data,
	    u_int datalen)
{
  if (datalen < IOCPARM_LEN (code))
    return POSIX_EINVAL;
  return process_ioctl (code, data, isctty, pid, pgrp);
}

error_t
io_ioctl_rw (struct termuser *term,
	     int isctty,
	     int pid,
	     int pgrp,
	     int code,
	     char *datain,
	     u_int datainlen,
	     char *dataout,
	     u_int *dataoutlen)
{
  error_t err;
  if (datainlen < IOCPARM_LEN (code) || *dataoutlen < IOCPARM_LEN (code))
    return POSIX_EINVAL;
  bcopy (datain, dataout, datainlen);
  err = process_ioctl (code, dataout, isctty, pid, pgrp);
  if (!err)
    *dataoutlen = IOCPARM_LEN (code);
  return err;
}




/* This is called to see if we need to stop the caller on a terminal
   operation.  Return STOPPED if the caller has stopped, return NOSTOP if
   the caller was signalled but won't stop, and NOSIG if the caller
   wasn't signalled.  */
enum ctty_stat
ctty_check (int isctty,
	    pid_t pid,
	    pid_t pgrp,
	    int signo)

{
  error_t err;
  int willstop;
  
  if (!isctty || pgrp == owner)
    return NOSIG;
  
  err = proc_ctty_kill (procserver, -owner, cttyid, signo, pid, &willstop);
  if (err)
    return NOSIG;
  else
    return willstop;
}
