/* termio.c (emx+gcc) */

/* Test termio, termios, and signals. */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <io.h>
#include <errno.h>
#include <signal.h>
#include <process.h>
#include <setjmp.h>
#include <getopt.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/termio.h>         /* include before termios.h to get IDEFAULT */
#include <termios.h>

#define FALSE 0
#define TRUE  1

static int jump_flag = FALSE;
static int nread_flag = FALSE;
static int hex_flag = FALSE;
static int select_flag = FALSE;
static int posix_flag = FALSE;

static jmp_buf jump;

static void handler (int sig)
{
  if (sig == SIGINT)
    {
      printf ("(SIGINT)");
      signal (sig, SIG_ACK);
    }
  else if (sig == SIGALRM)
    {
      printf ("(SIGALRM)");
      signal (sig, SIG_ACK);
    }
  else
    {
      printf ("Signal %d received. Process stopped.\n", sig);
      exit (1);
    }
  if (jump_flag)
    longjmp (jump, 1);
}


static void hexb (unsigned char c)
{
  static char const hexd[] = "0123456789ABCDEF";
  char buf[2];

  buf[0] = hexd[(c >> 4) & 0x0f];
  buf[1] = hexd[c & 0x0f];
  write (1, buf, 2);
}


static void show_vchar (unsigned char c, const char *text, int decimal)
{
  printf ("%s:%*s", text, (int)(7 - strlen (text)), "");
  if (decimal)
    printf ("%d", c);
  else if (c == 0)
    printf ("DISABLED");
  else if (c < 0x20)
    printf ("^%c", c + 'A' - 1);
  else
    printf ("0x%x", c);
  putchar ('\n');
}


static void show_iflag (int iflag)
{
  printf ("c_iflag:");
  if (iflag & IGNBRK) printf (" IGNBRK");
  if (iflag & BRKINT) printf (" BRKINT");
  if (iflag & IGNPAR) printf (" IGNPAR");
  if (iflag & PARMRK) printf (" PARMRK");
  if (iflag & INPCK)  printf (" INPCK");
  if (iflag & ISTRIP) printf (" ISTRIP");
  if (iflag & INLCR)  printf (" INLCR");
  if (iflag & IGNCR)  printf (" IGNCR");
  if (iflag & ICRNL)  printf (" ICRNL");
  if (iflag & IUCLC)  printf (" IUCLC");
  if (iflag & IXON)   printf (" IXON");
  if (iflag & IXANY)  printf (" IXANY");
  if (iflag & IXOFF)  printf (" IXOFF");
  if (iflag & IDELETE)  printf (" IDELETE");
  putchar ('\n');

}


static void show_oflag (int oflag)
{
  printf ("c_oflag:");
  if (oflag & OPOST)  printf (" OPOST");
  if (oflag & ONLCR)  printf (" ONLCR");
  if (oflag & OCRNL)  printf (" OCRNL");
  if (oflag & ONOCR)  printf (" ONOCR");
  if (oflag & ONLRET) printf (" ONLRET");
  if (oflag & OFILL)  printf (" OFILL");
  if (oflag & OFDEL)  printf (" OFDEL");
  switch (oflag & NLDLY)
    {
    case NL0: printf (" NL0"); break;
    case NL1: printf (" NL1"); break;
    }
  switch (oflag & CRDLY)
    {
    case CR0: printf (" CR0"); break;
    case CR1: printf (" CR1"); break;
    case CR2: printf (" CR2"); break;
    case CR3: printf (" CR3"); break;
    }
  switch (oflag & TABDLY)
    {
    case TAB0: printf (" TAB0"); break;
    case TAB1: printf (" TAB1"); break;
    case TAB2: printf (" TAB2"); break;
    case TAB3: printf (" TAB3"); break;
    }
  switch (oflag & BSDLY)
    {
    case BS0: printf (" BS0"); break;
    case BS1: printf (" BS1"); break;
    }
  switch (oflag & VTDLY)
    {
    case VT0: printf (" VT0"); break;
    case VT1: printf (" VT1"); break;
    }
  switch (oflag & FFDLY)
    {
    case FF0: printf (" FF0"); break;
    case FF1: printf (" FF1"); break;
    }
  putchar ('\n');
}


static void show_cflag (int cflag)
{
  printf ("c_cflag:");
  switch (cflag & CBAUD)
    {
    case B0: printf (" B0"); break;
    case B50: printf (" B50"); break;
    case B75: printf (" B75"); break;
    case B110: printf (" B110"); break;
    case B134: printf (" B134"); break;
    case B150: printf (" B150"); break;
    case B200: printf (" B200"); break;
    case B300: printf (" B300"); break;
    case B600: printf (" B600"); break;
    case B1200: printf (" B1200"); break;
    case B1800: printf (" B1800"); break;
    case B2400: printf (" B2400"); break;
    case B4800: printf (" B4800"); break;
    case B9600: printf (" B9600"); break;
    case B19200: printf (" B19200"); break;
    case B38400: printf (" B38400"); break;
    }
  switch (cflag & CSIZE)
    {
    case CS5: printf (" CS5"); break;
    case CS6: printf (" CS6"); break;
    case CS7: printf (" CS7"); break;
    case CS8: printf (" CS8"); break;
    }
  if (cflag & CSTOPB) printf (" CSTOPB");
  if (cflag & CREAD)  printf (" CREAD");
  if (cflag & PARENB) printf (" PARENB");
  if (cflag & PARODD) printf (" PARODD");
  if (cflag & HUPCL)  printf (" HUPCL");
  if (cflag & CLOCAL) printf (" CLOCAL");
  if (cflag & LOBLK)  printf (" LOBLK");
  putchar ('\n');
}


static void show_lflag (int lflag)
{
  printf ("c_lflag:");
  if (lflag & ISIG)   printf (" ISIG");
  if (lflag & ICANON) printf (" ICANON");
  if (lflag & XCASE)  printf (" XCASE");
  if (lflag & ECHO)   printf (" ECHO");
  if (lflag & ECHOE)  printf (" ECHOE");
  if (lflag & ECHOK)  printf (" ECHOK");
  if (lflag & ECHONL) printf (" ECHONL");
  if (lflag & NOFLSH) printf (" NOFLSH");
  if (lflag & IDEFAULT) printf (" IDEFAULT");
  putchar ('\n');
}


static void show_cc (const unsigned char *cc)
{
  show_vchar (cc[VINTR],  "VINTR",  FALSE);
  show_vchar (cc[VQUIT],  "VQUIT",  FALSE);
  show_vchar (cc[VERASE], "VERASE", FALSE);
  show_vchar (cc[VKILL],  "VKILL",  FALSE);
  show_vchar (cc[VEOF],   "VEOF",   FALSE);
  show_vchar (cc[VEOL],   "VEOL",   FALSE);
  show_vchar (cc[VMIN],   "VMIN",   TRUE);
  show_vchar (cc[VTIME],  "VTIME",  TRUE);
}


static void show_param (int fd)
{
  int n;

  if (posix_flag)
    {
      struct termios tios;

      if (tcgetattr (fd, &tios) != 0)
        {
          perror ("tcgetattr()");
          return;
        }
      show_iflag (tios.c_iflag);
      show_oflag (tios.c_oflag);
      show_cflag (tios.c_cflag);
      show_lflag (tios.c_lflag);
      show_cc (tios.c_cc);
    }
  else
    {
      struct termio tio;

      if (ioctl (fd, TCGETA, &tio) != 0)
        {
          perror ("ioctl TCGETA");
          return;
        }
      show_iflag (tio.c_iflag);
      show_oflag (tio.c_oflag);
      show_cflag (tio.c_cflag);
      show_lflag (tio.c_lflag);
      show_cc (tio.c_cc);
    }

  n = fcntl (fd, F_GETFL, 0);
  if (n < 0)
    {
      perror ("fcntl F_GETFL");
      return;
    }
  printf ("fcntl:");
  if (n & O_NDELAY) printf (" O_NDELAY");
  n = setmode (fd, O_BINARY);
  setmode (fd, n);
  if (n == O_BINARY)
    printf (" O_BINARY");
  else if (n == O_TEXT)
    printf (" O_TEXT");
  putchar ('\n');
}


static void change_flag (int *p, int mask, char change)
{
  switch (change)
    {
    case '+':
      *p |= mask;
      break;

    case '-':
      *p &= ~mask;
      break;

    default:
      *p ^= mask;
      break;
    }
}


static int change_iflag (int fd, int mask, char change)
{
  if (posix_flag)
    {
      struct termios tios;

      if (tcgetattr (fd, &tios) != 0)
        {
          perror ("tcgetattr()");
          return FALSE;
        }
      change_flag (&tios.c_iflag, mask, change);
      if (tcsetattr (fd, TCSANOW, &tios) != 0)
        {
          perror ("tcsetattr()");
          return FALSE;
        }
      return (tios.c_iflag & mask) ? TRUE : FALSE;
    }
  else
    {
      struct termio tio;

      if (ioctl (fd, TCGETA, &tio) != 0)
        {
          perror ("ioctl TCGETA");
          return FALSE;
        }
      change_flag (&tio.c_iflag, mask, change);
      if (ioctl (fd, TCSETA, &tio) != 0)
        {
          perror ("ioctl TCSETA");
          return FALSE;
        }
      return (tio.c_iflag & mask) ? TRUE : FALSE;
    }
}


static int change_lflag (int fd, int mask, char change)
{
  if (posix_flag)
    {
      struct termios tios;

      if (tcgetattr (fd, &tios) != 0)
        {
          perror ("tcgetattr()");
          return FALSE;
        }
      change_flag (&tios.c_lflag, mask, change);
      if (tcsetattr (fd, TCSANOW, &tios) != 0)
        {
          perror ("tcsetattr()");
          return FALSE;
        }
      return (tios.c_lflag & mask) ? TRUE : FALSE;
    }
  else
    {
      struct termio tio;

      if (ioctl (fd, TCGETA, &tio) != 0)
        {
          perror ("ioctl TCGETA");
          return FALSE;
        }
      change_flag (&tio.c_lflag, mask, change);
      if (ioctl (fd, TCSETA, &tio) != 0)
        {
          perror ("ioctl TCSETA");
          return FALSE;
        }
      return (tio.c_lflag & mask) ? TRUE : FALSE;
    }
}


static void change_cc (int fd, int index, unsigned char value)
{
  if (posix_flag)
    {
      struct termios tios;

      if (tcgetattr (fd, &tios) != 0)
        {
          perror ("tcgetattr()");
          return;
        }
      tios.c_cc[index] = value;
      if (tcsetattr (fd, TCSANOW, &tios) != 0)
        perror ("tcsetattr()");
    }
  else
    {
      struct termio tio;

      if (ioctl (fd, TCGETA, &tio) != 0)
        {
          perror ("ioctl TCGETA");
          return;
        }
      tio.c_cc[index] = value;
      if (ioctl (fd, TCSETA, &tio) != 0)
        perror ("ioctl TCSETA");
    }
}


static void usage (void)
{
  puts ("Usage: termio [-hijnp] [-f<file>] [-s(<seconds>|-)]\n\n"
        "Options:\n"
        "  -f<file>        Input from <file>\n"
        "  -s-             Use select() without time-out\n"
        "  -s<seconds>     Use select() with time-out\n"
        "  -h              Display input as hex dump\n"
        "  -i              Show initial attributes\n"
        "  -j   Use longjmp() in signal handler\n"
        "  -n   Call ioctl FIONREAD\n"
        "  -p   Use POSIX.1 termios instead of termio\n");
  exit (1);
}


int main (int argc, char *argv[])
{
  char buf[100], *p;
  int c, i, n, req, fd;
  fd_set rfds;
  struct timeval tv, *tvp;
  char *file_name = NULL;
  char init_show_param = FALSE;

  while ((c = getopt (argc, argv, "f:hijnps:")) != -1)
    switch (c)
      {
      case 'f':
        file_name = optarg;
        break;

      case 'i':
        init_show_param = TRUE;
        break;

      case 'j':
        jump_flag = TRUE;
        break;

      case 'h':
        hex_flag = TRUE;
        break;

      case 'n':
        nread_flag = TRUE;
        break;

      case 'p':
        posix_flag = TRUE;
        break;

      case 's':
        select_flag = TRUE;
        if (strcmp (optarg, "-") == 0)
          tvp = NULL;
        else
          {
            tv.tv_sec = strtol (optarg, &p, 10);
            if (p == optarg || *p != 0)
              usage ();
            tv.tv_usec = 0;
            tvp = &tv;
          }
        break;

      default:
        usage ();
      }

  if (optind != argc)
      usage ();

  if (file_name != NULL)
    {
      fd = open (file_name, O_RDWR);
      if (fd < 0)
        {
          perror (file_name);
          exit (2);
        }
    }
  else
    fd = 0;

  if (init_show_param)
    show_param (fd);
  if (setjmp (jump) != 0)
    puts ("jumped");
  signal (SIGINT, handler);
  signal (SIGBREAK, handler);
  signal (SIGQUIT, handler);
  signal (SIGALRM, handler);
  req = sizeof (buf) - 1;
  for (;;)
    {
      if (nread_flag)
        {
          if (ioctl (fd, FIONREAD, &n) == -1)
            perror ("ioctl FIONREAD");
          else
            printf ("%d> ", n);
        }
      if (select_flag)
        {
          FD_ZERO (&rfds);
          FD_SET (fd, &rfds);
          i = select (FD_SETSIZE, &rfds, NULL, NULL, tvp);
          if (i < 0)
            perror ("select");
          else if (i == 0)
            printf ("0> ");
          else
            {
              printf ("%d:", i);
              for (i = 7; i >= 0; --i)
                printf ("%d", (FD_ISSET (i, &rfds) ? 1 : 0));
              printf ("> ");
            }
        }
      n = read (fd, buf, req);
      if (n < 0)
        {
          if (errno == EAGAIN)
            {
              puts ("No data available -- sleeping for 2 seconds");
              sleep (2);
            }
          else
            perror ("read");
        }
      if (n >= 0)
        {
          write (1, "<", 1);
          if (hex_flag)
            for (i = 0; i < n; ++i)
              hexb (buf[i]);
          else
            write (1, buf, n);
          write (1, ">\n", 2);
        }
      if (n > 0)
        {
          buf[n] = 0;
          p = strchr (buf, '\n');
          if (p != NULL) *p = 0;
          if (buf[0] == '?')
            {
              puts ("?          Help");
              puts ("$          Quit");
              puts ("/          Toggle ICANON");
              puts ("+x         Set flag");
              puts ("-x         Clear flag");
              puts ("           Flags:");
              puts ("           b  O_BINARY    i  IGNCR");
              puts ("           c  ICANON      l  INLCR");
              puts ("           d  IDELETE     r  ICRNL");
              puts ("           e  ECHO        s  ISIG");
              puts ("           f  IDEFAULT    u  IUCLC");
              puts ("           n  O_NDELAY");
              puts ("&          Show parameters");
              puts ("!CMD       Shell escape");
              puts ("%SEC       Set alarm clock");
              puts ("#SEC       Sleep (append 'f' to flush buffer)");
              puts ("pause      Call pause()");
              puts ("=CHARS     Set number of characters to read");
              puts (">CHARS     Set VMIN");
              puts ("<TENTHS    Set VTIME");
              puts (".SIG       Ignore signal");
              puts ("*SIG       Handle signal");
              puts ("^SIG       Set default processing for signal");
              puts ("~SIG       Raise signal");
            }
          else if (buf[0] == '$')
            break;
          else if (buf[0] == '&')
            show_param (fd);
          else if (buf[0] == '!')
            system (buf+1);
          else if (buf[0] == '%' && isdigit ((unsigned char)buf[1]))
            printf ("alarm=%u\n", alarm (atoi (buf+1)));
          else if (buf[0] == '#' && isdigit ((unsigned char)buf[1]))
            {
              n = atoi (buf+1);
              if (n > 0)
                sleep (n);
              if (strchr (buf+1, 'f'))
                {
                  if (ioctl (fd, TCFLSH, 0) != 0)
                    perror ("ioctl TCFLSH");
                }
            }
          else if (buf[0] == '/')
            {
              printf ("ICANON %s\n",
                      change_lflag (fd, ICANON, 0) ? "on" : "off");
            }
          else if (buf[0] == '+' || buf[0] == '-')
            switch (buf[1])
              {
              case 'b':
                if (buf[0] == '+')
                  setmode (fd, O_BINARY);
                else
                  setmode (fd, O_TEXT);
                break;
              case 'c':
                change_lflag (fd, ICANON, buf[0]);
                break;

              case 'd':
                change_iflag (fd, IDELETE, buf[0]);
                break;

              case 'e':
                change_lflag (fd, ECHO, buf[0]);
                break;

              case 'f':
                change_lflag (fd, IDEFAULT, buf[0]);
                break;

              case 'i':
                change_iflag (fd, IGNCR, buf[0]);
                break;

              case 'l':
                change_iflag (fd, INLCR, buf[0]);
                break;

              case 'n':
                n = fcntl (fd, F_GETFL, 0);
                if (n < 0)
                  perror ("fcntl F_GETFL");
                else
                  {
                    if (buf[0] == '+')
                      n |= O_NDELAY;
                    else
                      n &= ~O_NDELAY;
                    if (fcntl (fd, F_SETFL, n) < 0)
                      perror ("fcntl F_SETFL");
                  }
                break;

              case 'r':
                change_iflag (fd, ICRNL, buf[0]);
                break;

              case 's':
                change_lflag (fd, ISIG, buf[0]);
                break;

              case 'u':
                change_iflag (fd, IUCLC, buf[0]);
                break;
              }
          else if (buf[0] == '>' && isdigit ((unsigned char)buf[1]))
            change_cc (fd, VMIN, (unsigned char)atoi (buf+1));
          else if (buf[0] == '<' && isdigit ((unsigned char)buf[1]))
            change_cc (fd, VTIME, (unsigned char)atoi (buf+1));
          else if (buf[0] == '.' && isdigit ((unsigned char)buf[1]))
            signal (atoi (buf+1), SIG_IGN);
          else if (buf[0] == '*' && isdigit ((unsigned char)buf[1]))
            signal (atoi (buf+1), handler);
          else if (buf[0] == '^' && isdigit ((unsigned char)buf[1]))
            signal (atoi (buf+1), SIG_DFL);
          else if (buf[0] == '~' && buf[1] == 'a')
            abort ();
          else if (buf[0] == '~' && isdigit ((unsigned char)buf[1]))
            raise (atoi (buf+1));
          else if (buf[0] == '=' && isdigit ((unsigned char)buf[1]))
            {
              n = atoi (buf+1);
              if (n >= 1 && n <= sizeof (buf) - 1)
                req = n;
              else
                req = sizeof (buf) - 1;
            }
          else if (strcmp (buf, "pause") == 0)
            pause ();
        }
    }
  return 0;
}
