/*	     XANT - A New TN3270 for the X Window System
			IBM Internal Use Only

   This is a telnet client for AIX with the X Window System,
   version 11.  The full telnet protocol is not implemented.  
   This program is only meant to work with a remote host that
   supports 3270 mode.

   I think that all source should be readily available, so if
   you change the source for this program, make the source code
   available to whoever gets the executable code.  If people 
   stopped making it so hard to get the source for things, I
   think we'd have a lot more good software around.

   Written by:  Tom Engelsiepen
   		IBM Research Division
   		Almaden Research Center
		August, 1988
   							*/

#ifndef lint
static char *sccsid = "@(#)xant.c	4.50	12/05/91";
static char *sec = "*** IBM Internal Use Only ***";
#endif
#define XANT_VERSION "4.50"

#include <stdio.h>
#include <string.h>
#include <varargs.h>
#include <errno.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/uio.h>
#include <sys/socket.h>
#define MAXFDS 32

#ifndef NULL
#define NULL 0
#endif

#ifdef _IBMR2
#define VOID_SIGNAL
#endif

#ifdef SUN
#define VOID_SIGNAL
#endif

#ifdef VOID_SIGNAL
typedef void (*HANDLER)();
#else
typedef int (*HANDLER)();
#endif
typedef char Boolean;


/* Exported variables */
Boolean debug;			/* Debugging if true */
Boolean trace;			/* Trace I/O if true */
char *progname;			/* The name of this program */


/* Global variables */
static Boolean usepvm, graphics;
static char *session;
extern int errno;


/* Forward routine declarations */
static void checkopt(), help();
static int quit();
extern int *api_init();


/* Main program */
main(argc, argv)
     int argc;
     char *argv[];
{
  register int i;
  int targc, j, k, port;
  Boolean extended;
  char *gateway, *secondary, *workstation;
  int adapter;
  int cols, rows, newcols, newrows;
  char **targv, *hostname;
  struct sigvec newvec;
  int xfd, tfd, *afd, fdsmask;
  struct timeval timeout;
  char c;

  /* Copy the argument list */
  targc = argc;
  if (!(targv = (char **) malloc((argc + 1) * sizeof(*targv))))
    error("Not enough memory");
  for (i = 0; i < argc; i++)
    targv[i] = argv[i];
  targv[argc] = NULL;

  /* Process program options */
  progname = targv[0];
  for (i = 1; i < targc; i++) 
    if (!strncmp(targv[i], "-help", strlen(targv[i]))) help();

#ifdef TESTING
  debug = 1;
#endif

  checkopt(&targc, targv, "-d", &debug);
  checkopt(&targc, targv, "-t", &trace);
  x_options(&targc, targv, &port, &extended, &graphics, &session, &hostname,
	    &usepvm, &gateway, &secondary, &workstation, &adapter);
  
  if (targc > 1) hostname = targv[1];
  if (!hostname || strlen(hostname) == 0)
    error("Remote host name is missing");
  if (targc > 2)
    {
      x_usage();
      exit(1);
    }

  /* Connect to the remote host */
  tfd = net_init(usepvm, hostname, extended, port, gateway, secondary,
		 workstation, adapter);
  if (tfd > MAXFDS) error("Too many file descriptors");

  /* Handle termination signals */
  if (!debug) 
    {
      newvec.sv_handler = (HANDLER) quit;
      newvec.sv_mask = 0;
      newvec.sv_onstack = 0;
      sigvec(SIGHUP, &newvec, NULL);
      sigvec(SIGINT, &newvec, NULL);
      sigvec(SIGQUIT, &newvec, NULL);
      sigvec(SIGTERM, &newvec, NULL);
      sigvec(SIGPIPE, &newvec, NULL);
    }

  /* Create the terminal emulation window */
  xfd = x_init(hostname, &cols, &rows);
  if (xfd > MAXFDS) error("Too many file descriptors");

  /* Start the session */
  net_setup(cols, rows, &newcols, &newrows);

  /* Initialize the screen */
  x_setup(newcols, newrows);
  s3270_init(newcols, newrows, extended, graphics);
  if (session) 
    {
      afd = api_init(session);
      if (*afd > MAXFDS) error("Too many file descriptors");
    }
  else afd = NULL;

  /* Fork off another process so that "&" isn't needed on command line */
  if (!debug) 
    {
      if ((i = fork()) < 0)
	error("Error creating a new process");
      if (i) exit(0);

      /* Disconnect from controlling terminal */
      close(0);
      close(1);
      close(2);
      (void) setpgrp();
    }
  
  /* Main loop */
  while (1) 
    {
      /* Process all pending X events and network input */
      i = x_check();
      if (i < 0) break;
      j = net_check();
      if (j < 0) break;
      k = session ? api_check() : 0;
      if (i || j || k) continue;

      /* All events and network input has been processed, so handle some
	 of the work that might have been generated and then go back and
	 check for more input.  */

      i = x_flush();
      if (i < 0) continue;

      /* If no work was found, wait for more input from X or from network */
      fdsmask = (1 << xfd);
      if (tfd) fdsmask |= (1 << tfd);
      if (afd) fdsmask |= (1 << *afd);
      timeout.tv_sec = (i > 0 ? i : 30);
      timeout.tv_usec = 0;
      i = select(MAXFDS, &fdsmask, 0, 0, &timeout);

      /* Check for error in select */
      if (i < 0 && errno != EINTR) errorm("Select failed");

      /* If an X window is canceled, the socket will be closed and
	 select will fall through even though there's no work to do.
	 Peek at the message to make sure some data was really sent.
	 This isn't necessary for telnet because telnet does its own
	 receive.  Apparently, the X library doesn't check the length
	 for zero when it does a receive.  */

      else if (i >= 0 && fdsmask & (1 << xfd))
	{
	  if ((i = recv(xfd, &c, 1, MSG_PEEK)) < 0)
	    errorm("Receive data from X socket failed");
	  if (!i) break;
	}
    }
  if (session) api_close();
  net_close();
  x_close();
  return 0;
}


/* Routine to check the command options for some switch */
static void checkopt(argc, argv, sw, opt)
     int *argc;
     char *argv[];
     char *sw;
     Boolean *opt;
{
  register int i, j;
  for (i = 1; i < *argc; i++)
    if (!strcmp(argv[i], sw))
      {
	(*opt)++;
	for (j = i; j < *argc - 1; j++)
	  argv[j] = argv[j+1];
	(*argc)--;
	break;
      }
}


/* Routine to handle signals */
/* ARGSUSED */ static int quit(sig, code, scp)
     int sig, code;
     struct sigcontext *scp;
{
  if (session) api_close();
  net_close();
  x_close();
  exit(0);
}


static void help()
{
  x_usage();
  fprintf(stderr, "\n\
*** XANT Version %s ***\n\
IBM Internal Use Only\n\
\n\
This is a 3270 telnet client for the X Window System.\n\
\n\
  -- Tom Engelsiepen, IBM Almaden Research Center\n", XANT_VERSION);
  exit(1);
}


/* Print out an EBCDIC data buffer */
dumpbuff(buff, len)
     char *buff;
     int len;
{
  int i, j, numrows;
  unsigned char c;
  char tc;
  extern char etoa[];

  numrows = (len + 15) / 16;
  for (i = 0; i < numrows; i++) 
    {
      printf("    ");
      for (j = 0; j < 16; j++) 
	{
	  if (j + i * 16 >= len) printf("  ");
	  else printf("%02X", buff[j + i * 16]);
	  if (j % 4 == 3) putchar(' ');
	}
      printf(" |");
      for (j = 0; j < 16; j++)
	{
	  if (j + i * 16 >= len) putchar(' ');
	  else 
	    {
	      c = buff[j + i * 16];
	      tc = etoa[c];
	      if (tc >= 32 && tc < 127 && tc != ' ') putchar(tc);
	      else if (c == 0x40) putchar(' ');
	      else putchar('.');
	    }
	}
      printf("|\n");
    }
}


/* Print an error message to stderr and halt */
error(va_alist)
     va_dcl
{
  va_list ap;
  char *format;

  fprintf(stderr, "%s: ", progname);
  va_start(ap);
  format = (char *) va_arg(ap, char *);
#ifdef BSD
   _doprnt(format, ap, stderr);
#else
  vfprintf(stderr, format, ap);
#endif
  va_end(ap);
  putc('\n', stderr);
  exit(1);
}


/* Print an error message to stderr, call perror(), and halt */
errorm(va_alist)
     va_dcl
{
  va_list ap;
  char *format;
  char buff[200];

  sprintf(buff, "%s: ", progname);
  va_start(ap);
  format = (char *) va_arg(ap, char *);
  vsprintf(buff + strlen(buff), format, ap);
  va_end(ap);
  perror(buff);
  exit(1);
}


#ifdef BSD
int vsprintf(str, fmt, args, DUMMY)
     register char *str, *fmt;
{
  FILE _strbuf;
  _strbuf._flag = _IOWRT+_IOSTRG;
  _strbuf._ptr = (unsigned char *) str;
  _strbuf._cnt = 32767;
  _doprnt((unsigned char *) fmt, args, &_strbuf);
  putc('\0', &_strbuf);
  return (int) str;
}
#endif
