/* fhandler_serial.cc


This file is part of Cygwin.

This software is a copyrighted work licensed under the terms of the
Cygwin license.  Please consult the file "CYGWIN_LICENSE" for
details. */

#include "winsup.h"
#include <unistd.h>
#include <sys/param.h>
#include "cygerrno.h"
#include "security.h"
#include "path.h"
#include "fhandler.h"
#include "sigproc.h"
#include "pinfo.h"
#include <asm/socket.h>
#include <devioctl.h>
#include <ntddser.h>
#include "cygwait.h"

/**********************************************************************/
/* fhandler_serial */

fhandler_serial::fhandler_serial ()
  : fhandler_base (), vmin_ (0), vtime_ (0), pgrp_ (myself->pgid)
{
  need_fork_fixup (true);
}

void
fhandler_serial::raw_read (void *ptr, size_t& ulen)
{
  OVERLAPPED ov = { 0 };
  DWORD io_err;
  COMSTAT st;
  DWORD bytes_to_read, read_bytes;
  ssize_t tot = 0;

  if (ulen > SSIZE_MAX)
    ulen = SSIZE_MAX;
  if (ulen == 0)
    return;

  /* If MIN > 0 in blocking mode, we have to read at least VMIN chars,
     otherwise we're in polling mode and there's no minimum chars. */
  ssize_t minchars = (!is_nonblocking () && vmin_) ? MIN (vmin_, ulen) : 0;

  debug_printf ("ulen %ld, vmin_ %u, vtime_ %u", ulen, vmin_, vtime_);

  ov.hEvent = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL);

  do
    {
      /* First check if chars are already in the inbound queue. */
      if (!ClearCommError (get_handle (), &io_err, &st))
	goto err;
      /* FIXME: In case of I/O error, do we really want to bail out or is it
		better just trying to pull through? */
      if (io_err)
	{
	  termios_printf ("error detected %x", io_err);
	  SetLastError (ERROR_IO_DEVICE);
	  goto err;
	}
      /* ReadFile only handles up to DWORD bytes. */
      bytes_to_read = MIN (ulen, UINT32_MAX);
      if (is_nonblocking ())
	{
	  /* In O_NONBLOCK mode we just care for the number of chars already
	     in the inbound queue. */
	  if (!st.cbInQue)
	    break;
	  bytes_to_read = MIN (st.cbInQue, bytes_to_read);
	}
      else
	{
	  /* If the number of chars in the inbound queue is sufficent
	     (minchars defines the minimum), set bytes_to_read accordingly
	     and don't wait. */
	  if (st.cbInQue && (ssize_t) st.cbInQue >= minchars)
	    bytes_to_read = MIN (st.cbInQue, bytes_to_read);
	}

      ResetEvent (ov.hEvent);
      if (!ReadFile (get_handle (), ptr, bytes_to_read, &read_bytes, &ov))
	{
	  if (GetLastError () != ERROR_IO_PENDING)
	    goto err;
	  if (is_nonblocking ())
	    {
	      CancelIo (get_handle ());
	      if (!GetOverlappedResult (get_handle (), &ov, &read_bytes,
					TRUE))
		goto err;
	    }
	  else
	    {
	      switch (cygwait (ov.hEvent))
		{
		default: /* Handle an error case from cygwait basically like
			    a cancel condition and see if we got "something" */
		  CancelIo (get_handle ());
		  fallthrough;
		case WAIT_OBJECT_0:
		  if (!GetOverlappedResult (get_handle (), &ov, &read_bytes,
					    TRUE))
		    goto err;
		  debug_printf ("got %u bytes from ReadFile", read_bytes);
		  break;
		case WAIT_SIGNALED:
		  CancelIo (get_handle ());
		  if (!GetOverlappedResult (get_handle (), &ov, &read_bytes,
					    TRUE))
		    goto err;
		  /* Only if no bytes read, return with EINTR. */
		  if (!tot && !read_bytes)
		    {
		      tot = -1;
		      set_sig_errno (EINTR);
		      debug_printf ("signal received, set EINTR");
		    }
		  else
		    debug_printf ("signal received but ignored");
		  goto out;
		case WAIT_CANCELED:
		  CancelIo (get_handle ());
		  GetOverlappedResult (get_handle (), &ov, &read_bytes, TRUE);
		  debug_printf ("thread canceled");
		  pthread::static_cancel_self ();
		  /*NOTREACHED*/
		}
	    }
	}
      tot += read_bytes;
      ptr = (void *) ((caddr_t) ptr + read_bytes);
      ulen -= read_bytes;
      minchars -= read_bytes;
      debug_printf ("vtime_ %u, vmin_ %u, read_bytes %u, tot %D",
		    vtime_, vmin_, read_bytes, tot);
      continue;

    err:
      debug_printf ("err %E");
      if (GetLastError () != ERROR_OPERATION_ABORTED)
	{
	  if (tot == 0)
	    {
	      tot = -1;
	      __seterrno ();
	    }
	  break;
	}
    }
  /* ALL of these are required to loop:

     Still room in user space buffer
     AND still a minchars requirement (implies blocking mode)
     AND vtime_ is not set. */
  while (ulen > 0 && minchars > 0 && vtime_ == 0);

out:
  CloseHandle (ov.hEvent);
  ulen = (size_t) tot;
  if (is_nonblocking () && ulen == 0)
    {
      ulen = (size_t) -1;
      set_errno (EAGAIN);
    }
}

/* Cover function to WriteFile to provide Posix interface and semantics
   (as much as possible).  */
ssize_t
fhandler_serial::raw_write (const void *ptr, size_t len)
{
  DWORD bytes_written;
  OVERLAPPED write_status;

  memset (&write_status, 0, sizeof (write_status));
  write_status.hEvent = CreateEvent (&sec_none_nih, TRUE, FALSE, NULL);
  ProtectHandle (write_status.hEvent);

  for (;;)
    {
      if (WriteFile (get_handle (), ptr, len, &bytes_written, &write_status))
	break;

      switch (GetLastError ())
	{
	case ERROR_OPERATION_ABORTED:
	  DWORD ev;
	  if (!ClearCommError (get_handle (), &ev, NULL))
	    goto err;
	  if (ev)
	    termios_printf ("error detected %x", ev);
	  continue;
	case ERROR_IO_PENDING:
	  break;
	default:
	  goto err;
	}

      if (!is_nonblocking ())
	{
	  switch (cygwait (write_status.hEvent))
	    {
	    case WAIT_OBJECT_0:
	      break;
	    case WAIT_SIGNALED:
	      PurgeComm (get_handle (), PURGE_TXABORT);
	      set_sig_errno (EINTR);
	      ForceCloseHandle (write_status.hEvent);
	      return -1;
	    case WAIT_CANCELED:
	      PurgeComm (get_handle (), PURGE_TXABORT);
	      pthread::static_cancel_self ();
	      /*NOTREACHED*/
	    default:
	      goto err;
	    }
	}
      if (!GetOverlappedResult (get_handle (), &write_status, &bytes_written, TRUE))
	goto err;

      break;
    }

  ForceCloseHandle (write_status.hEvent);

  return bytes_written;

err:
  __seterrno ();
  ForceCloseHandle (write_status.hEvent);
  return -1;
}

int
fhandler_serial::init (HANDLE f, DWORD flags, mode_t bin)
{
  return open (flags, bin & (O_BINARY | O_TEXT));
}

int
fhandler_serial::open (int flags, mode_t mode)
{
  int res;
  COMMTIMEOUTS to;

  syscall_printf ("fhandler_serial::open (%s, %y, 0%o)",
		  get_name (), flags, mode);

  if (!fhandler_base::open (flags, mode))
    return 0;

  res = 1;

  SetCommMask (get_handle (), EV_RXCHAR);

  memset (&to, 0, sizeof (to));
  SetCommTimeouts (get_handle (), &to);

  /* Reset serial port to known state of 9600-8-1-no flow control
     on open for better behavior under Win 95.

     FIXME:  This should only be done when explicitly opening the com
     port.  It should not be reset if an fd is inherited.
     Using __progname in this way, to determine how far along in the
     initialization we are, is really a terrible kludge and should
     be fixed ASAP.
  */
  if (reset_com && __progname)
    {
      DCB state;
      GetCommState (get_handle (), &state);
      syscall_printf ("setting initial state on %s (reset_com %d)",
		      get_name (), reset_com);
      state.BaudRate = CBR_9600;
      state.ByteSize = 8;
      state.StopBits = ONESTOPBIT;
      state.Parity = NOPARITY; /* FIXME: correct default? */
      state.fBinary = TRUE; /* binary xfer */
      state.EofChar = 0; /* no end-of-data in binary mode */
      state.fNull = FALSE; /* don't discard nulls in binary mode */
      state.fParity = FALSE; /* ignore parity errors */
      state.fErrorChar = FALSE;
      state.fTXContinueOnXoff = TRUE; /* separate TX and RX flow control */
      state.fOutX = FALSE; /* disable transmission flow control */
      state.fInX = FALSE; /* disable reception flow control */
      state.XonChar = 0x11;
      state.XoffChar = 0x13;
      state.fOutxDsrFlow = FALSE; /* disable DSR flow control */
      state.fRtsControl = RTS_CONTROL_ENABLE; /* ignore lead control except
						  DTR */
      state.fOutxCtsFlow = FALSE; /* disable output flow control */
      state.fDtrControl = DTR_CONTROL_ENABLE; /* assert DTR */
      state.fDsrSensitivity = FALSE; /* don't assert DSR */
      state.fAbortOnError = TRUE;
      if (!SetCommState (get_handle (), &state))
	system_printf ("couldn't set initial state for %s, %E", get_name ());
    }

  SetCommMask (get_handle (), EV_RXCHAR);
  set_open_status ();
  syscall_printf ("%p = fhandler_serial::open (%s, %y, 0%o)",
		  res, get_name (), flags, mode);
  return res;
}

/* tcsendbreak: POSIX 7.2.2.1 */
/* Break for 250-500 milliseconds if duration == 0 */
/* Otherwise, units for duration are undefined */
int
fhandler_serial::tcsendbreak (int duration)
{
  unsigned int sleeptime = 300000;

  if (duration > 0)
    sleeptime *= duration;

  if (SetCommBreak (get_handle ()) == 0)
    return -1;

  /* FIXME: need to send zero bits during duration */
  usleep (sleeptime);

  if (ClearCommBreak (get_handle ()) == 0)
    return -1;

  syscall_printf ("0 = fhandler_serial:tcsendbreak (%d)", duration);

  return 0;
}

/* tcdrain: POSIX 7.2.2.1 */
int
fhandler_serial::tcdrain ()
{
  if (FlushFileBuffers (get_handle ()) == 0)
    return -1;

  return 0;
}

/* tcflow: POSIX 7.2.2.1 */
int
fhandler_serial::tcflow (int action)
{
  DWORD win32action = 0;
  DCB dcb;
  char xchar;

  termios_printf ("action %d", action);

  switch (action)
    {
    case TCOOFF:
      win32action = SETXOFF;
      break;
    case TCOON:
      win32action = SETXON;
      break;
    case TCION:
    case TCIOFF:
      if (GetCommState (get_handle (), &dcb) == 0)
	return -1;
      if (action == TCION)
	xchar = (dcb.XonChar ? dcb.XonChar : 0x11);
      else
	xchar = (dcb.XoffChar ? dcb.XoffChar : 0x13);
      if (TransmitCommChar (get_handle (), xchar) == 0)
	return -1;
      return 0;
      break;
    default:
      return -1;
      break;
    }

  if (EscapeCommFunction (get_handle (), win32action) == 0)
    return -1;

  return 0;
}


/* switch_modem_lines: set or clear RTS and/or DTR */
int
fhandler_serial::switch_modem_lines (int set, int clr)
{
  if ((set & (TIOCM_RTS | TIOCM_DTR)) || (clr & (TIOCM_RTS | TIOCM_DTR)))
    {
      DCB dcb;

      memset(&dcb,0,sizeof(dcb));
      dcb.DCBlength = sizeof(dcb);

      if (!GetCommState(get_handle(), &dcb))
        {
          __seterrno ();
          return -1;
        }

      if (set & TIOCM_RTS)
        dcb.fRtsControl = RTS_CONTROL_ENABLE;
      else
      if (clr & TIOCM_RTS)
        dcb.fRtsControl = RTS_CONTROL_DISABLE;

      if (set & TIOCM_DTR)
        dcb.fDtrControl = DTR_CONTROL_ENABLE;
      else
      if (clr & TIOCM_DTR)
        dcb.fDtrControl = DTR_CONTROL_DISABLE;

      if (!SetCommState(get_handle(), &dcb))
        {
          __seterrno ();
          return -1;
        }
    }

  return 0;
}

/* ioctl: */
int
fhandler_serial::ioctl (unsigned int cmd, void *buf)
{
  int res = 0;

# define ibuf ((int) (intptr_t) buf)
# define ipbuf (*(int *) buf)

  DWORD ev;
  COMSTAT st;
  if (!ClearCommError (get_handle (), &ev, &st))
    {
      __seterrno ();
      res = -1;
    }
  else
    switch (cmd)
      {
      case TCFLSH:
	res = tcflush (ibuf);
	break;
      case TIOCMGET:
	DWORD modem_lines;
	if (!GetCommModemStatus (get_handle (), &modem_lines))
	  {
	    __seterrno ();
	    res = -1;
	  }
	else
	  {
	    ipbuf = 0;
	    if (modem_lines & MS_CTS_ON)
	      ipbuf |= TIOCM_CTS;
	    if (modem_lines & MS_DSR_ON)
	      ipbuf |= TIOCM_DSR;
	    if (modem_lines & MS_RING_ON)
	      ipbuf |= TIOCM_RI;
	    if (modem_lines & MS_RLSD_ON)
	      ipbuf |= TIOCM_CD;

	    DWORD cb;
	    DWORD mcr;
	    if (!DeviceIoControl (get_handle (), IOCTL_SERIAL_GET_DTRRTS,
				  NULL, 0, &mcr, 4, &cb, 0) || cb != 4)
	      ipbuf |= rts | dtr;
	    else
	      {
		if (mcr & 2)
		  ipbuf |= TIOCM_RTS;
		if (mcr & 1)
		  ipbuf |= TIOCM_DTR;
	      }
	  }
	break;
      case TIOCMSET:
	if (switch_modem_lines (ipbuf, ~ipbuf))
	  res = -1;
	break;
      case TIOCMBIS:
	if (switch_modem_lines (ipbuf, 0))
	  res = -1;
	break;
      case TIOCMBIC:
	if (switch_modem_lines (0, ipbuf))
	  res = -1;
	break;
      case TIOCCBRK:
	if (ClearCommBreak (get_handle ()) == 0)
	  {
	    __seterrno ();
	    res = -1;
	  }
	break;
      case TIOCSBRK:
	if (SetCommBreak (get_handle ()) == 0)
	  {
	    __seterrno ();
	    res = -1;
	  }
	break;
     case TIOCINQ:
       ipbuf = st.cbInQue;
       break;
     case TIOCGWINSZ:
       ((struct winsize *) buf)->ws_row = 0;
       ((struct winsize *) buf)->ws_col = 0;
       break;
     case FIONREAD:
       set_errno (ENOTSUP);
       res = -1;
       break;
     default:
       res = fhandler_base::ioctl (cmd, buf);
       break;
     }

  termios_printf ("%d = ioctl(%x, %p)", res, cmd, buf);
# undef ibuf
# undef ipbuf
  return res;
}

/* tcflush: POSIX 7.2.2.1 */
int
fhandler_serial::tcflush (int queue)
{
  DWORD flags;

  switch (queue)
    {
    case TCOFLUSH:
      flags = PURGE_TXABORT | PURGE_TXCLEAR;
      break;
    case TCIFLUSH:
      flags = PURGE_RXABORT | PURGE_RXCLEAR;
      break;
    case TCIOFLUSH:
      flags = PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR;
      break;
    default:
      termios_printf ("Invalid tcflush queue %d", queue);
      set_errno (EINVAL);
      return -1;
    }

  if (!PurgeComm (get_handle (), flags))
    {
      __seterrno ();
      return -1;
    }

  return 0;
}

/* tcsetattr: POSIX 7.2.1.1 */
int
fhandler_serial::tcsetattr (int action, const struct termios *t)
{
  /* Possible actions:
    TCSANOW:   immediately change attributes.
    TCSADRAIN: flush output, then change attributes.
    TCSAFLUSH: flush output and discard input, then change attributes.
  */

  bool dropDTR = false;
  COMMTIMEOUTS to;
  DCB ostate, state;
  int tmpDtr, tmpRts, res;
  res = tmpDtr = tmpRts = 0;

  termios_printf ("action %d", action);
  if ((action == TCSADRAIN) || (action == TCSAFLUSH))
    {
      FlushFileBuffers (get_handle ());
      termios_printf ("flushed file buffers");
    }
  if (action == TCSAFLUSH)
    PurgeComm (get_handle (), (PURGE_RXABORT | PURGE_RXCLEAR));

  /* get default/last comm state */
  if (!GetCommState (get_handle (), &ostate))
    return -1;

  state = ostate;

  /* -------------- Set baud rate ------------------ */
  /* FIXME: WIN32 also has 14400, 56000, 128000, and 256000.
     Unix also has 230400. */

  switch (t->c_ospeed)
    {
    case B0:
      /* Drop DTR - but leave DCB-resident bitrate as-is since
	 0 is an invalid bitrate in Win32 */
      dropDTR = true;
      break;
    case B110:
      state.BaudRate = CBR_110;
      break;
    case B300:
      state.BaudRate = CBR_300;
      break;
    case B600:
      state.BaudRate = CBR_600;
      break;
    case B1200:
      state.BaudRate = CBR_1200;
      break;
    case B2400:
      state.BaudRate = CBR_2400;
      break;
    case B4800:
      state.BaudRate = CBR_4800;
      break;
    case B9600:
      state.BaudRate = CBR_9600;
      break;
    case B19200:
      state.BaudRate = CBR_19200;
      break;
    case B38400:
      state.BaudRate = CBR_38400;
      break;
    case B57600:
      state.BaudRate = CBR_57600;
      break;
    case B115200:
      state.BaudRate = CBR_115200;
      break;
    case B128000:
      state.BaudRate = CBR_128000;
      break;
    case B230400:
      state.BaudRate = 230400 /* CBR_230400 - not defined */;
      break;
    case B256000:
      state.BaudRate = CBR_256000;
      break;
    case B460800:
      state.BaudRate = 460800 /* CBR_460800 - not defined */;
      break;
    case B500000:
      state.BaudRate = 500000 /* CBR_500000 - not defined */;
      break;
    case B576000:
      state.BaudRate = 576000 /* CBR_576000 - not defined */;
      break;
    case B921600:
      state.BaudRate = 921600 /* CBR_921600 - not defined */;
      break;
    case B1000000:
      state.BaudRate = 1000000 /* CBR_1000000 - not defined */;
      break;
    case B1152000:
      state.BaudRate = 1152000 /* CBR_1152000 - not defined */;
      break;
    case B1500000:
      state.BaudRate = 1500000 /* CBR_1500000 - not defined */;
      break;
    case B2000000:
      state.BaudRate = 2000000 /* CBR_2000000 - not defined */;
      break;
    case B2500000:
      state.BaudRate = 2500000 /* CBR_2500000 - not defined */;
      break;
    case B3000000:
      state.BaudRate = 3000000 /* CBR_3000000 - not defined */;
      break;
    default:
      /* Unsupported baud rate! */
      termios_printf ("Invalid t->c_ospeed %u", t->c_ospeed);
      set_errno (EINVAL);
      return -1;
    }

  /* -------------- Set byte size ------------------ */

  switch (t->c_cflag & CSIZE)
    {
    case CS5:
      state.ByteSize = 5;
      break;
    case CS6:
      state.ByteSize = 6;
      break;
    case CS7:
      state.ByteSize = 7;
      break;
    case CS8:
      state.ByteSize = 8;
      break;
    default:
      /* Unsupported byte size! */
      termios_printf ("Invalid t->c_cflag byte size %u",
		      t->c_cflag & CSIZE);
      set_errno (EINVAL);
      return -1;
    }

  /* -------------- Set stop bits ------------------ */

  if (t->c_cflag & CSTOPB)
    state.StopBits = TWOSTOPBITS;
  else
    state.StopBits = ONESTOPBIT;

  /* -------------- Set parity ------------------ */

  if (t->c_cflag & PARENB)
    {
      if(t->c_cflag & CMSPAR)
        state.Parity = (t->c_cflag & PARODD) ? MARKPARITY : SPACEPARITY;
      else
        state.Parity = (t->c_cflag & PARODD) ? ODDPARITY : EVENPARITY;
    }
  else
    state.Parity = NOPARITY;

  state.fBinary = TRUE;     /* Binary transfer */
  state.EofChar = 0;	    /* No end-of-data in binary mode */
  state.fNull = FALSE;      /* Don't discard nulls in binary mode */

  /* -------------- Parity errors ------------------ */
  /* fParity combines the function of INPCK and NOT IGNPAR */

  if ((t->c_iflag & INPCK) && !(t->c_iflag & IGNPAR))
    state.fParity = TRUE;   /* detect parity errors */
  else
    state.fParity = FALSE;  /* ignore parity errors */

  /* Only present in Win32, Unix has no equivalent */
  state.fErrorChar = FALSE;
  state.ErrorChar = 0;

  /* -------------- Set software flow control ------------------ */
  /* Set fTXContinueOnXoff to FALSE.  This prevents the triggering of a
     premature XON when the remote device interprets a received character
     as XON (same as IXANY on the remote side).  Otherwise, a TRUE
     value separates the TX and RX functions. */

  state.fTXContinueOnXoff = TRUE;     /* separate TX and RX flow control */

  /* Transmission flow control */
  if (t->c_iflag & IXON)
    state.fOutX = TRUE;   /* enable */
  else
    state.fOutX = FALSE;  /* disable */

  /* Reception flow control */
  if (t->c_iflag & IXOFF)
    state.fInX = TRUE;    /* enable */
  else
    state.fInX = FALSE;   /* disable */

  /* XoffLim and XonLim are left at default values */

  state.XonChar = (t->c_cc[VSTART] ? t->c_cc[VSTART] : 0x11);
  state.XoffChar = (t->c_cc[VSTOP] ? t->c_cc[VSTOP] : 0x13);

  /* -------------- Set hardware flow control ------------------ */

  /* Disable DSR flow control */
  state.fOutxDsrFlow = FALSE;

  /* Some old flavors of Unix automatically enabled hardware flow
     control when software flow control was not enabled.  Since newer
     Unices tend to require explicit setting of hardware flow-control,
     this is what we do. */

  /* RTS/CTS flow control */
  if (t->c_cflag & CRTSCTS)
    {							/* enable */
      state.fOutxCtsFlow = TRUE;
      state.fRtsControl = RTS_CONTROL_HANDSHAKE;
    }
  else
    {							/* disable */
      state.fRtsControl = RTS_CONTROL_ENABLE;
      state.fOutxCtsFlow = FALSE;
      tmpRts = TIOCM_RTS;
    }

  if (t->c_cflag & CRTSXOFF)
    state.fRtsControl = RTS_CONTROL_HANDSHAKE;

  /* -------------- DTR ------------------ */
  /* Assert DTR on device open */

  state.fDtrControl = DTR_CONTROL_ENABLE;

  /* -------------- DSR ------------------ */
  /* Assert DSR at the device? */

  if (t->c_cflag & CLOCAL)
    state.fDsrSensitivity = FALSE;  /* no */
  else
    state.fDsrSensitivity = TRUE;   /* yes */

  /* -------------- Error handling ------------------ */
  /* Since read/write operations terminate upon error, we
     will use ClearCommError() to resume. */

  state.fAbortOnError = TRUE;

  if ((memcmp (&ostate, &state, sizeof (state)) != 0)
      && !SetCommState (get_handle (), &state))
    {
      /* SetCommState() failed, usually due to invalid DCB param.
	 Keep track of this so we can set errno to EINVAL later
	 and return failure */
      termios_printf ("SetCommState() failed, %E");
      __seterrno ();
      res = -1;
    }

  rbinary ((t->c_iflag & IGNCR) ? false : true);
  wbinary ((t->c_oflag & ONLCR) ? false : true);

  if (dropDTR)
    {
      EscapeCommFunction (get_handle (), CLRDTR);
      tmpDtr = 0;
    }
  else
    {
      /* FIXME: Sometimes when CLRDTR is set, setting
      state.fDtrControl = DTR_CONTROL_ENABLE will fail.  This
      is a problem since a program might want to change some
      parameters while DTR is still down. */

      EscapeCommFunction (get_handle (), SETDTR);
      tmpDtr = TIOCM_DTR;
    }

  rts = tmpRts;
  dtr = tmpDtr;

  /* The following documentation on was taken from "Linux Serial Programming
  HOWTO".  It explains how MIN (t->c_cc[VMIN] || vmin_) and TIME
  (t->c_cc[VTIME] || vtime_) is to be used.

  In non-canonical input processing mode, input is not assembled into
  lines and input processing (erase, kill, delete, etc.) does not
  occur. Two parameters control the behavior of this mode: c_cc[VTIME]
  sets the character timer, and c_cc[VMIN] sets the minimum number of
  characters to receive before satisfying the read.

  If MIN > 0 and TIME = 0, MIN sets the number of characters to receive
  before the read is satisfied. As TIME is zero, the timer is not used.

  If MIN = 0 and TIME > 0, TIME serves as a timeout value. The read will
  be satisfied if a single character is read, or TIME is exceeded (t =
  TIME *0.1 s). If TIME is exceeded, no character will be returned.

  If MIN > 0 and TIME > 0, TIME serves as an inter-character timer. The
  read will be satisfied if MIN characters are received, or the time
  between two characters exceeds TIME. The timer is restarted every time
  a character is received and only becomes active after the first
  character has been received.

  If MIN = 0 and TIME = 0, read will be satisfied immediately. The
  number of characters currently available, or the number of characters
  requested will be returned. According to Antonino (see contributions),
  you could issue a fcntl(fd, F_SETFL, FNDELAY); before reading to get
  the same result.
  */

  if (t->c_lflag & ICANON)
    {
      vmin_ = 0;
      vtime_ = 0;
    }
  else
    {
      vtime_ = t->c_cc[VTIME];
      vmin_ = t->c_cc[VMIN];
    }

  debug_printf ("vtime %u, vmin %u", vtime_, vmin_);

  memset (&to, 0, sizeof (to));

  if ((vmin_ > 0) && (vtime_ == 0))
    {
      /* Returns immediately with whatever is in buffer on a ReadFile();
	 or blocks if nothing found.  We will keep calling ReadFile(); until
	 vmin_ characters are read */
      to.ReadIntervalTimeout = to.ReadTotalTimeoutMultiplier = MAXDWORD;
      to.ReadTotalTimeoutConstant = MAXDWORD - 1;
    }
  else if ((vmin_ == 0) && (vtime_ > 0))
    {
      /* set timeoout constant appropriately and we will only try to
	 read one character in ReadFile() */
      to.ReadTotalTimeoutConstant = vtime_ * 100;
      to.ReadIntervalTimeout = to.ReadTotalTimeoutMultiplier = MAXDWORD;
    }
  else if ((vmin_ > 0) && (vtime_ > 0))
    {
      /* time applies to the interval time for this case */
      to.ReadIntervalTimeout = vtime_ * 100;
    }
  else if ((vmin_ == 0) && (vtime_ == 0))
    {
      /* returns immediately with whatever is in buffer as per
	 Time-Outs docs in Win32 SDK API docs */
      to.ReadIntervalTimeout = MAXDWORD;
    }

  debug_printf ("ReadTotalTimeoutConstant %u, ReadIntervalTimeout %u, "
		"ReadTotalTimeoutMultiplier %u", to.ReadTotalTimeoutConstant,
		to.ReadIntervalTimeout, to.ReadTotalTimeoutMultiplier);

  if (!SetCommTimeouts(get_handle (), &to))
    {
      /* SetCommTimeouts() failed. Keep track of this so we
	 can set errno to EINVAL later and return failure */
      termios_printf ("SetCommTimeouts() failed, %E");
      __seterrno ();
      res = -1;
    }

  return res;
}

/* tcgetattr: POSIX 7.2.1.1 */
int
fhandler_serial::tcgetattr (struct termios *t)
{
  DCB state;

  /* Get current Win32 comm state */
  if (GetCommState (get_handle (), &state) == 0)
    return -1;

  /* for safety */
  memset (t, 0, sizeof (*t));

  t->c_cflag = 0;
  /* -------------- Baud rate ------------------ */
  switch (state.BaudRate)
    {
    case CBR_110:
	t->c_ospeed = t->c_ispeed = B110;
	break;
    case CBR_300:
	t->c_ospeed = t->c_ispeed = B300;
	break;
    case CBR_600:
	t->c_ospeed = t->c_ispeed = B600;
	break;
    case CBR_1200:
	t->c_ospeed = t->c_ispeed = B1200;
	break;
    case CBR_2400:
	t->c_ospeed = t->c_ispeed = B2400;
	break;
    case CBR_4800:
	t->c_ospeed = t->c_ispeed = B4800;
	break;
    case CBR_9600:
	t->c_ospeed = t->c_ispeed = B9600;
	break;
    case CBR_19200:
	t->c_ospeed = t->c_ispeed = B19200;
	break;
    case CBR_38400:
	t->c_ospeed = t->c_ispeed = B38400;
	break;
    case CBR_57600:
	t->c_ospeed = t->c_ispeed = B57600;
	break;
    case CBR_115200:
	t->c_ospeed = t->c_ispeed = B115200;
	break;
    case CBR_128000:
	t->c_ospeed = t->c_ispeed = B128000;
	break;
    case 230400: /* CBR_230400 - not defined */
	t->c_ospeed = t->c_ispeed = B230400;
	break;
    case CBR_256000:
	t->c_ospeed = t->c_ispeed = B256000;
	break;
    case 460800: /* CBR_460000 - not defined */
	t->c_ospeed = t->c_ispeed = B460800;
	break;
    case 500000: /* CBR_500000 - not defined */
	t->c_ospeed = t->c_ispeed = B500000;
	break;
    case 576000: /* CBR_576000 - not defined */
	t->c_ospeed = t->c_ispeed = B576000;
	break;
    case 921600: /* CBR_921600 - not defined */
	t->c_ospeed = t->c_ispeed = B921600;
	break;
    case 1000000: /* CBR_1000000 - not defined */
	t->c_ospeed = t->c_ispeed = B1000000;
	break;
    case 1152000: /* CBR_1152000 - not defined */
	t->c_ospeed = t->c_ispeed = B1152000;
	break;
    case 1500000: /* CBR_1500000 - not defined */
	t->c_ospeed = t->c_ispeed = B1500000;
	break;
    case 2000000: /* CBR_2000000 - not defined */
	t->c_ospeed = t->c_ispeed = B2000000;
	break;
    case 2500000: /* CBR_2500000 - not defined */
	t->c_ospeed = t->c_ispeed = B2500000;
	break;
    case 3000000: /* CBR_3000000 - not defined */
	t->c_ospeed = t->c_ispeed = B3000000;
	break;
    default:
	/* Unsupported baud rate! */
	termios_printf ("Invalid baud rate %u", state.BaudRate);
	set_errno (EINVAL);
	return -1;
    }

  /* -------------- Byte size ------------------ */

  switch (state.ByteSize)
    {
    case 5:
      t->c_cflag |= CS5;
      break;
    case 6:
      t->c_cflag |= CS6;
      break;
    case 7:
      t->c_cflag |= CS7;
      break;
    case 8:
      t->c_cflag |= CS8;
      break;
    default:
      /* Unsupported byte size! */
      termios_printf ("Invalid byte size %u", state.ByteSize);
      set_errno (EINVAL);
      return -1;
    }

  /* -------------- Stop bits ------------------ */

  if (state.StopBits == TWOSTOPBITS)
    t->c_cflag |= CSTOPB;

  /* -------------- Parity ------------------ */

  if (state.Parity == ODDPARITY)
    t->c_cflag |= (PARENB | PARODD);
  if (state.Parity == EVENPARITY)
    t->c_cflag |= PARENB;
  if (state.Parity == MARKPARITY)
    t->c_cflag |= (PARENB | PARODD | CMSPAR);
  if (state.Parity == SPACEPARITY)
    t->c_cflag |= (PARENB | CMSPAR);

  /* -------------- Parity errors ------------------ */

  /* fParity combines the function of INPCK and NOT IGNPAR */
  if (state.fParity)
    t->c_iflag |= INPCK;
  else
    t->c_iflag |= IGNPAR;	/* not necessarily! */

  /* -------------- Software flow control ------------------ */

  /* transmission flow control */
  if (state.fOutX)
    t->c_iflag |= IXON;

  /* reception flow control */
  if (state.fInX)
    t->c_iflag |= IXOFF;

  t->c_cc[VSTART] = (state.XonChar ? state.XonChar : 0x11);
  t->c_cc[VSTOP] = (state.XoffChar ? state.XoffChar : 0x13);

  /* -------------- Hardware flow control ------------------ */
  /* Some old flavors of Unix automatically enabled hardware flow
     control when software flow control was not enabled.  Since newer
     Unices tend to require explicit setting of hardware flow-control,
     this is what we do. */

  /* Input flow-control */
  if ((state.fRtsControl == RTS_CONTROL_HANDSHAKE) && state.fOutxCtsFlow)
    t->c_cflag |= CRTSCTS;
  if (state.fRtsControl == RTS_CONTROL_HANDSHAKE)
    t->c_cflag |= CRTSXOFF;

  /* -------------- CLOCAL --------------- */
  /* DSR is only lead toggled only by CLOCAL.  Check it to see if
     CLOCAL was called. */
  /* FIXME: If tcsetattr() hasn't been called previously, this may
     give a false CLOCAL. */

  if (!state.fDsrSensitivity)
    t->c_cflag |= CLOCAL;

  /* FIXME: need to handle IGNCR */
#if 0
  if (!rbinary ())
    t->c_iflag |= IGNCR;
#endif

  if (!wbinary ())
    t->c_oflag |= ONLCR;

  t->c_cc[VTIME] = vtime_;
  t->c_cc[VMIN] = vmin_;

  debug_printf ("vmin_ %u, vtime_ %u", vmin_, vtime_);

  return 0;
}
