/* $XTermId: vms.c,v 1.15 2022/10/06 19:35:35 tom Exp $ */

/*  vms.c
 *
 * This module contains the VMS version of the routine SPAWN (from the module
 * MAIN.C) and the routines that do IO to the pseudo terminal.
 *
 * Modification History:
 * Stephan Jansen 1-Mar-1990  Original version
 * Hal R. Brand   5-Sep-1990  Added code to propagate DECW$DISPLAY
 * Aaron Leonard 11-Sep-1990  Fix string descriptor lengths
 * Stephan Jansen 2-Dec-1991  Modify to use new Pseudo terminal drivers
 *                            (patterned after photo.c by Forrest A. Kenney)
 * Patrick Mahan  7-Jan-1991  Removed reference to <dvidef.h> from VMS.C
 *			      Forced device type to be VT102 since that is
 *			      what we are emulating.
 */

#include <libdef.h>
#include <lnmdef.h>

#include <stdio.h>
#include <string.h>

#include "xterm.h"
#include "data.h"
#include "vms.h"

#define PTD$C_SEND_XON		0	/* Pseudo Terminal Driver event      */
#define PTD$C_SEND_BELL		1
#define PTD$C_SEND_XOFF 	2
#define PTD$C_STOP_OUTPUT	3
#define PTD$C_RESUME_OUTPUT 	4
#define PTD$C_CHAR_CHANGED 	5
#define PTD$C_ABORT_OUTPUT 	6
#define PTD$C_START_READ 	7
#define PTD$C_MIDDLE_READ 	8
#define PTD$C_END_READ 		9
#define PTD$C_ENABLE_READ 	10
#define PTD$C_DISABLE_READ 	11
#define PTD$C_MAX_EVENTS 	12

#define	BUFFERS		        6
#define	PAGE			512

typedef struct	tt_buffer
{
unsigned int	flink;
unsigned int	blink;
short	int	status;
short	int	length;
char		data[VMS_TERM_BUFFER_SIZE];
} TT_BUF_STRUCT;

TT_BUF_STRUCT		*tt_w_buff;
struct	q_head		 _align(QUADWORD)	buffer_queue = (0,0);
struct	q_head		 _align(QUADWORD)	read_queue = (0,0);

static char          tt_name[64];
static $DESCRIPTOR   (tt_name_desc, &tt_name);

static char          ws_name[64];
static $DESCRIPTOR   (ws_name_desc, &ws_name);

static struct        tt_char {
   char        class;
   char        type;
   short int   page_width;
   char        characteristics[3];
   char        length;
   int         extended;
 } tt_mode, tt_chars, orig_tt_chars;

struct mem_region
{
  TT_BUF_STRUCT *start;
  TT_BUF_STRUCT *end;
} ret_addr;

int read_stopped = False;
int write_stopped = False;

int tt_width;
int tt_length;
int tt_changed;
int tt_pasting=False;         /* drm */
int tt_new_output=False;      /* Cleared by flushlog(), set whenever something new
   goes to the screen through tt_write */

int trnlnm(char *in,int id,char *out);
void spawn (void);

static void tt_echo_ast(TT_BUF_STRUCT *buff_addr);
static void tt_read_ast(TT_BUF_STRUCT *buff_addr);

/*
static void tt_start_read(void);
*/
void tt_start_read(void);
int tt_read(char *buffer);
static void send_xon(void);
static void send_xoff(void);
static void send_bell(void);
static void char_change(void);
static void freeBuff (TT_BUF_STRUCT *buff_addr);
TT_BUF_STRUCT *getBuff(void);
static void CloseDown(int exit_status);
static void mbx_read_ast(void);
static void mbx_read(void);



#define DESCRIPTOR(name,string) struct dsc$descriptor_s name = \
{ strlen(string), DSC$K_DTYPE_T, DSC$K_CLASS_S, string }

int trnlnm(char *in, int id, char *out)
{
  int status, num, len, attr = LNM$M_CASE_BLIND, foo = id;
  short outlen;
  struct itemlist
    {
      short buffer_length;
      short item_code;
      char  *buffer_addr;
      int   *return_length;
    } itmlst[] =
      {
	4  , LNM$_INDEX    , &foo, 0,
	255, LNM$_STRING   , out , &outlen,
	4  , LNM$_MAX_INDEX, &num, &len,
	0  , 0
	};
  DESCRIPTOR(lognam,in);
  DESCRIPTOR(tabnam,"LNM$DCL_LOGICAL");

  status = sys$trnlnm(&attr,&tabnam,&lognam,0,itmlst);
  if(status != SS$_NORMAL) return(-1);   /* error status */
  out[outlen] = 0;         /* terminate the output string */
  return(++num);         /* return number of translations */
}

static int           pty;
static int           Xsocket;

void spawn (void)
{
  int                  status;
  static $DESCRIPTOR   (dtime, "0 00:00:00.01");
  static int           delta[2];
  register TScreen     *screen = TScreenOf(term);
  static struct IOSB   iosb;
  static unsigned int  flags;
  static unsigned int  uic;
  static char          imagename[64];
  static int           privs;
  static $DESCRIPTOR(device, "FTA0:");
  static int           type;
  static int           class;
  static int           devdepend;
  static int           mem_size;
  int                  i;

  /* if pid and mbx_chan are nonzero then close them in CloseDown() */
  pid = 0;
  mbx_chan = 0;

  status = SYS$EXPREG (BUFFERS, &ret_addr, 0, 0);
  if(!(status & SS$_NORMAL)) lib$signal(status);

  tt_w_buff = (char *)ret_addr.end - PAGE + 1;

  /* use one buffer for writing, the reset go in the free buffer queue */
  for(i=0; i < BUFFERS-1; i++)
    {
      freeBuff((char *)ret_addr.start +i*PAGE);
    }

  /* avoid double MapWindow requests, for wm's that care... */
  XtSetMappedWhenManaged( screen->TekEmu ? XtParent(tekWidget) :
			 XtParent(term), False );
  /* Realize the Tek or VT widget, depending on which mode we're in.
     If VT mode, this calls VTRealize (the widget's Realize proc) */
  XtRealizeWidget (screen->TekEmu ? XtParent(tekWidget) :
		   XtParent(term));

  /* get the default device characteristics of the pseudo terminal */

  itemlist[0].buflen      = 4;
  itemlist[0].code        = DVI$_DEVTYPE;
  itemlist[0].buffer      = &type;
  itemlist[0].return_addr = &tt_name_desc.dsc$w_length;

  itemlist[1].buflen      = 4;
  itemlist[1].code        = DVI$_DEVCLASS;
  itemlist[1].buffer      = &class;
  itemlist[1].return_addr = &tt_name_desc.dsc$w_length;

  itemlist[2].buflen      = 4;
  itemlist[2].code        = DVI$_DEVDEPEND;
  itemlist[2].buffer      = &devdepend;
  itemlist[2].return_addr = &tt_name_desc.dsc$w_length;

  itemlist[3].buflen      = 4;
  itemlist[3].code        = DVI$_DEVDEPEND2;
  itemlist[3].buffer      = &tt_chars.extended;
  itemlist[3].return_addr = &tt_name_desc.dsc$w_length;

  itemlist[4].buflen      = 0;
  itemlist[4].code        = 0;


  status = sys$getdviw(0,0,&device,&itemlist,&iosb,0,0,0);
  if(!(status & SS$_NORMAL)) lib$signal(status);
  if(!(iosb.status & SS$_NORMAL)) lib$signal(iosb.status);

  tt_chars.type        = DT$_VT102; /* XTerm supports VT102 mode */
  tt_chars.class       = class;
  tt_chars.page_width  = screen->max_col+1;
  tt_chars.length      = screen->max_row+1;

  /* copy the default char's along with the created window size */

  bcopy(&devdepend, &tt_chars.characteristics, 3);

  tt_chars.extended |= TT2$M_ANSICRT | TT2$M_AVO | TT2$M_DECCRT;


  /* create the pseudo terminal with the proper char's */
  status = ptd$create(&tt_chan,0,&tt_chars,12,0,0,0,&ret_addr);
  if(!(status & SS$_NORMAL)) lib$signal(status);


  /* get the device name of the Pseudo Terminal */

  itemlist[0].buflen      = 64;
  itemlist[0].code        = DVI$_DEVNAM;
  itemlist[0].buffer      = &tt_name;
  itemlist[0].return_addr = &tt_name_desc.dsc$w_length;

  /* terminate the list */
  itemlist[1].buflen      = 0;
  itemlist[1].code        = 0;

  status = sys$getdviw(0,tt_chan,0,&itemlist,&iosb,0,0,0);
  if(!(status & SS$_NORMAL)) CloseDown(status);
  if(!(iosb.status & SS$_NORMAL)) CloseDown(iosb.status);

  /*
   * set up AST's for XON, XOFF, BELL and characteristics change.
   */

  status = ptd$set_event_notification(tt_chan,&send_xon,0,0,PTD$C_SEND_XON);
  if(!(status & SS$_NORMAL)) CloseDown(status);

  status = ptd$set_event_notification(tt_chan,&send_xoff,0,0,PTD$C_SEND_XOFF);
  if(!(status & SS$_NORMAL)) CloseDown(status);

  status = ptd$set_event_notification(tt_chan,&send_bell,0,0,PTD$C_SEND_BELL);
  if(!(status & SS$_NORMAL)) CloseDown(status);

  status = ptd$set_event_notification(tt_chan,&char_change,0,0,PTD$C_CHAR_CHANGED);
  if(!(status & SS$_NORMAL)) CloseDown(status);

  /* create a mailbox for the detached process to detect hangup */

  status = sys$crembx(0,&mbx_chan,ACC$K_TERMLEN,0,255,0,0);
  if(!(status & SS$_NORMAL)) CloseDown(status);


  /*
   * get the device unit number for created process completion
   * status to be sent to.
   */

  itemlist[0].buflen      = 4;
  itemlist[0].code        = DVI$_UNIT;
  itemlist[0].buffer      = &mbxunit;
  itemlist[0].return_addr = 0;

  /* terminate the list */
  itemlist[1].buflen      = 0;
  itemlist[1].code        = 0;

  status = sys$getdviw(0,mbx_chan,0,&itemlist,&iosb,0,0,0);
  if(!(status & SS$_NORMAL)) CloseDown(status);
  if(!(iosb.status & SS$_NORMAL)) CloseDown(iosb.status);


  tt_start_read();

  /*
   * find the current process's UIC so that it can be used in the
   * call to sys$creprc
   */
  itemlist[0].buflen = 4;
  itemlist[0].code = JPI$_UIC;
  itemlist[0].buffer = &uic;
  itemlist[0].return_addr = 0;

  /* terminate the list */
  itemlist[1].buflen      = 0;
  itemlist[1].code        = 0;

  status = sys$getjpiw(0,0,0,&itemlist,0,0,0);
  if(!(status & SS$_NORMAL)) CloseDown(status);

  /* Complete a descriptor for the WS (DECW$DISPLAY) device */

  trnlnm("DECW$DISPLAY",0,ws_name);
  ws_name_desc.dsc$w_length = strlen(ws_name);

  /* create the process */
  /*  Set sys$error to be the WS (DECW$DISPLAY) device. LOGINOUT  */
  /*  has special code for DECWINDOWS that will:                  */
  /*    1) do a DEFINE/JOB DECW$DISPLAY 'f$trnlnm(sys$error)'     */
  /*    2) then redefine SYS$ERROR to match SYS$OUTPUT!           */
  /*  This will propagate DECW$DISPLAY to the XTERM process!!!    */
  /*  Thanks go to Joel M Snyder who posted this info to INFO-VAX */

  flags = PRC$M_INTER | PRC$M_NOPASSWORD | PRC$M_DETACH;
  status = sys$creprc(&pid,&image,&tt_name_desc,&tt_name_desc,
		      &ws_name_desc,0,0,0,4,uic,mbxunit,flags);
  if(!(status & SS$_NORMAL)) CloseDown(status);


  /* hang a read on the mailbox waiting for completion */
  mbx_read();


/* set time value and schedule a periodic wakeup (every 1/100 of a second)
 * this is used to prevent the controlling process from using up all the
 * CPU.  The controlling process will hibernate at strategic points in
 * the program when it is just waiting for input.
 */

  status = sys$bintim(&dtime,&delta);
  if (!(status & SS$_NORMAL)) CloseDown(status);

  status = sys$schdwk(0,0,&delta,&delta);
  if (!(status & SS$_NORMAL)) CloseDown(status);


  /*
   * This is rather funky, but it saves me from having to totally
   * rewrite some parts of the code (namely in_put in module CHARPROC.C)
   */
  pty = 1;
  screen->respond = pty;
  pty_mask = 1 << pty;
  Select_mask = pty_mask;
  X_mask = 1 << Xsocket;

}


/*
 * This routine handles completion of write with echo.  It takes the
 * echo buffer and puts it on the read queue.  It will then be processed
 * by the routine tt_read.  If the echo buffer is empty, it is put back
 * on the free buffer queue.
 */

static void tt_echo_ast(TT_BUF_STRUCT *buff_addr)
{
  int status;

  if (buff_addr->length != 0)
    {
      status = LIB$INSQTI(buff_addr, &read_queue);
      if((status != SS$_NORMAL) && (status != LIB$_ONEENTQUE))
	{
	  CloseDown(status);
	}
    }
  else
    {
      freeBuff(buff_addr);
    }
}


/*
 * This routine writes to the pseudo terminal.  If there is a free
 * buffer then write with an echo buffer completing asynchronously, else
 * write synchronously using the buffer reserved for writing.  All errors
 *  are fatal, except DATAOVERUN and DATALOST,these errors can be ignored.

 CAREFUL! Whatever calls this must NOT pass more than VMS_TERM_BUFFER_SIZE
 bytes at a time.  This definition has been moved to VMS.H

 */

int tt_write(const char *tt_write_buf, size_t size)
{
  int status;
  TT_BUF_STRUCT *echoBuff;

  /* if writing stopped, return 0 until Xon */
  if(write_stopped) return (0);

  memmove(&tt_w_buff->data,tt_write_buf,size);

  echoBuff = getBuff();
  if (echoBuff != LIB$_QUEWASEMP)
    {
      status = PTD$WRITE (tt_chan, &tt_echo_ast, echoBuff,
			  &tt_w_buff->status, size,
			  &echoBuff->status, VMS_TERM_BUFFER_SIZE);
    }
  else
    {
      status = PTD$WRITE (tt_chan, 0, 0, &tt_w_buff->status, size, 0, 0);
    }
  if (status & SS$_NORMAL)
    {
      if ((tt_w_buff->status != SS$_NORMAL) &&
	  (tt_w_buff->status != SS$_DATAOVERUN) &&
	  (tt_w_buff->status != SS$_DATALOST))
	{
	  CloseDown(tt_w_buff->status);
	}
    }
  else
    {
      CloseDown(status);
    }

  return(size);
}


/*
 * This routine is called when a read to the pseudo terminal completes.
 * Put the newly read buffer onto the read queue.  It will be processed
 * and freed in the routine tt_read.
 */

static void tt_read_ast(TT_BUF_STRUCT *buff_addr)
{
  int status;

  if (buff_addr->status & SS$_NORMAL)
    {
      status = LIB$INSQTI(buff_addr, &read_queue);
      if ((status != SS$_NORMAL) && (status != LIB$_ONEENTQUE))
	{
	  CloseDown(status);
	}
    }
  else
    CloseDown(buff_addr->status);

  tt_start_read();
  sys$wake(0,0);
  return;
}


/*
 * If there is a free buffer on the buffer queue then Start a read from
 * the pseudo terminal, otherwise set a flag, the reading will be restarted
 * in the routine freeBuff when a buffer is freed.
 */

void tt_start_read(void)
{
  int status;
  static int size;
  TT_BUF_STRUCT *buff_addr;

  buff_addr = getBuff();
  if (buff_addr != LIB$_QUEWASEMP)
    {
      if(!tt_pasting){
      status = PTD$READ (0, tt_chan, &tt_read_ast, buff_addr,
			 &buff_addr->status, VMS_TERM_BUFFER_SIZE);
      if ((status & SS$_NORMAL) != SS$_NORMAL)
	{
	  CloseDown(status);
	}
      }
      }
  else
    {
      read_stopped = True;
    }
  return;
}


/*
 * Get data from the pseudo terminal.  Return the data from the first item
 * on the read queue, and put that buffer back onto the free buffer queue.
 * Return the length or zero if the read queue is empty.
 *
 */

int tt_read(char *buffer)
{
  TT_BUF_STRUCT *read_buff;
  int status;
  int len;

   status = LIB$REMQHI(&read_queue, &read_buff);
   if(status == LIB$_QUEWASEMP){
     return(0);
   }
   else if (status & SS$_NORMAL)
     {
       len = read_buff->length;
       memmove(buffer,&read_buff->data,len);
       freeBuff(read_buff);
       tt_new_output=True; /* DRM something will be written */
     }
   else
     CloseDown(status);

   return(len);
}


/*
 * if xon then it is safe to start writing again.
 */

static void send_xon(void)
{
  write_stopped = False;
}


/*
 * If Xoff then stop writing to the pseudo terminal until you get Xon.
 */
static void send_xoff(void)
{
  write_stopped = True;
}



/*
 * Beep the terminal to let the user know data will be lost because
 * of too much data.
 */

static void send_bell(void)
{
   Bell(term);
}

/*
 * if the pseudo terminal's characteristics change, check to see if the
 * page size changed.  If it did, resize the widget, otherwise, ignore
 * it!  This routine just gets the new term dimensions and sets a flag
 * to indicate the term chars have changed.  The widget gets resized in
 * the routine in_put in the module CHARPROC.C.  You can't resize the
 * widget in this routine because this is an AST and X is not reenterent.
 */

static void char_change(void)
{
  int status;

  /*
   * Don't do anything if in Tek mode
   */

  if(!(TScreenOf(term)->TekEmu))
    {
      status = sys$qiow(0,tt_chan,IO$_SENSEMODE,0,0,0,&tt_mode,8,0,0,0,0);
      if(!(status & SS$_NORMAL)) CloseDown(status);

      if((TScreenOf(term)->max_row != tt_mode.length) ||
	 (TScreenOf(term)->max_col != tt_mode.page_width))
	{
	  tt_length = tt_mode.length;
	  tt_width =  tt_mode.page_width;

	  tt_changed = True;

	}
    }
}


/*
 * Put a free buffer back onto the buffer queue.  If reading was
 * stopped for lack of free buffers, start reading again.
 */

static void freeBuff (TT_BUF_STRUCT *buff_addr)
{
  int ast_stat;
  int status;

  ast_stat = SYS$SETAST(0);
  if (!read_stopped)
    {
      LIB$INSQHI(buff_addr, &buffer_queue);
    }
  else
    {
      status = PTD$READ (0, tt_chan, &tt_read_ast, buff_addr,
			 &buff_addr->status, VMS_TERM_BUFFER_SIZE);
      if (status & SS$_NORMAL)
	{
	  read_stopped = False;
	}
      else
	{
	  CloseDown(status);
	}
    }
  if (ast_stat == SS$_WASSET) ast_stat = SYS$SETAST(1);
}


/*
 * return a free buffer from the buffer queue.
 */

TT_BUF_STRUCT *getBuff(void)
{
  int status;
  TT_BUF_STRUCT *buff_addr;

  status = LIB$REMQHI(&buffer_queue, &buff_addr);
  if (status & SS$_NORMAL)
    {
      return(buff_addr);
    }
  else
    {
      return(status);
    }
}


/*
 * Close down and exit.  Kill the detached process (if it still
 * exists), deassign mailbox channell (if assigned), cancel any
 * waiting IO to the pseudo terminal and delete it, exit with any
 * status information.
 */

static void CloseDown(int exit_status)
{
  int status;

  /* if process has not terminated, do so now! */
  if(pid != 0)
    {
      status = sys$forcex(&pid,0,0);
      if(!(status & SS$_NORMAL)) lib$signal(status);
    }

  /* if mbx_chan is assigned, deassign it */
  if(mbx_chan != 0)
    {
      sys$dassgn(mbx_chan);
    }

  /* cancel pseudo terminal IO requests */
  status = ptd$cancel(tt_chan);
  if(!(status & SS$_NORMAL)) lib$signal(status);

  /* delete pseudo terminal */
  status = ptd$delete(tt_chan);
  if(!(status & SS$_NORMAL)) lib$signal(status);

  if(!(exit_status & SS$_NORMAL)) lib$signal(exit_status);

  exit(1);

}


/*
 * This routine gets called when the detached process terminates (for
 * whatever reason).  The mailbox buffer has final exit status.  Close
 * down and exit.
 */

static void mbx_read_ast(void)
{
  int status;

  pid = 0;

  status = mbx_read_iosb.status;
  if (!(status & SS$_NORMAL)) CloseDown(status);

  status = (unsigned long int) mbx_buf.acc$l_finalsts;
  if (!(status & SS$_NORMAL)) CloseDown(status);

  CloseDown(1);

}


/*
 * This routine starts a read on the mailbox associated with the detached
 * process.  The AST routine gets called when the detached process terminates.
 */

static void mbx_read(void)
{
int status;
static int size;

   size = ACC$K_TERMLEN;
   status = sys$qio(0,mbx_chan,
          IO$_READVBLK,
          &mbx_read_iosb,
          &mbx_read_ast,
          0,
          &mbx_buf,
          size,0,0,0,0);

   if (!(status & SS$_NORMAL)) CloseDown(status);

   return;
}
