#include "ckcsym.h" 	/* emulate most compilers -DSYMBOL feature */

char *ckxv = "Stratus VOS Communications support 6.1.007, 15 January 1998";

/*  C K L T I O  */

/* C-Kermit interrupt, terminal control & i/o functions for STRATUS VOS */

/*
  Author: Frank da Cruz (fdc@columbia.edu),
  The Kermit Project, Columbia University
  Adapted to Stratus VOS by David R. Lane, SoftCom Systems, Inc., Nov 93.

  Copyright (C) 1985, 1999,
    Trustees of Columbia University in the City of New York.
    All rights reserved.  See the C-Kermit COPYING.TXT file or the 
    copyright text in the ckcmai.c module for disclaimer and permissions.
*/

/* Includes */

#include "ckcdeb.h"			/* This moved to here. */
#include <errno.h>			/* System error numbers */
#include "ckcnet.h"			/* Symbols for network types. */
#include <signal.h>                     /* Signals */
#include <setjmp.h>
#include <system_io_constants.h>
#include <term_control_opcodes.h>
#include <get_port_info.h>
#include <terminal_info.h>
#include <process_info_structures.h>
#include <fcntl.h>
#include <time.h>
#include <terminal_modes.h>
#include <error_codes.h>

/* In releases prior to 13, get_port_info is a struct tag.  In 13, it
   becomes a typedef.  No, it's not in the SRB, it's just an incompatible
   change in a system include file. (YECH!)  The cklmak.cm macro tries
   to set this define on releases before 13.  YMMV, unfortunately.
*/
#ifdef GET_PORT_INFO_IS_STRUCT
typedef struct get_port_info get_port_info;
#endif

typedef struct
{
  unsigned  filler  : 8;
  unsigned  baud    : 8;
  unsigned  stop    : 8;
  unsigned  parity  : 8;
} term_configure;

typedef struct
{
  short			version;
  unsigned short	flags;
} DATA_SET_STATUS_INFO;

#define DSSI_CLEAR_TO_SEND	0x8000
#define DSSI_CARRIER_DETECT	0x4000
#define DSSI_DATA_SET_READY	0x2000

/* We know these are set here.  MUST unset them before the definitions. */
#define signal vsignal
#define alarm valarm
SIGTYP (*vsignal(int type, SIGTYP (*func)(int)))(int);
int valarm(int interval);

/* Maximum length for the name of a tty device */

#ifndef DEVNAMLEN
#define DEVNAMLEN 66
#endif

#ifdef	NETCONN
#undef DEVNAMLEN
#define DEVNAMLEN 132			/* longer field for host:service */
#endif  /* NETCONN */

#include "ckuver.h"			/* Version herald */
char *ckxsys = HERALD;

/*
 Variables available to outside world:

   dftty  -- Pointer to default tty name string, like "/dev/tty".
   dfloc  -- 0 if dftty is console, 1 if external line.
   dfprty -- Default parity
   dfflow -- Default flow control
   ckxech -- Flag for who echoes console typein:
     1 - The program (system echo is turned off)
     0 - The system (or front end, or terminal).
   functions that want to do their own echoing should check this flag
   before doing so.

   backgrd -- Flag indicating program executing in background, such as
		as a batch server. Used to ignore INT and QUIT signals.

   myttystr -- name of terminal tty

 Functions for assigned communication line (either external or console tty):

   sysinit()               -- System dependent program initialization
   syscleanup()            -- System dependent program shutdown
   ttopen(ttname,local,mdmtyp,timo) -- Open the named tty for exclusive access.
   ttclos()                -- Close & reset the tty, releasing any access lock.
   ttsspd(cps)             -- Set the transmission speed of the tty.
   ttgspd()                -- Get (read) the the transmission speed of the tty.
   ttpkt(speed,flow,parity) -- Put the tty in packet mode and set the speed.
   ttvt(speed,flow)        -- Put the tty in virtual terminal mode.
                                or in DIALING or CONNECTED modem control state.
   ttres()                 -- Restore original tty modes.
   ttscarr(carrier)        -- Set carrier control mode, on/off/auto.
   ttinl(dest,max,timo)    -- Timed read line from the tty.
   ttinc(timo)             -- Timed read character from tty.
   ttchk()                 -- See how many characters in tty input buffer.
   ttxin(n,buf)            -- Read n characters from tty (untimed).
   ttol(string,length)     -- Write a string to the tty.
   ttoc(c)                 -- Write a character to the tty.
   ttflui()                -- Flush tty input buffer.
   ttsndb()                -- Send BREAK signal.
   ttsndlb()               -- Send Long BREAK signal.
*/

/*
Functions for console terminal:

   congm()   -- Get console terminal modes.
   concb(esc) -- Put the console in single-character wakeup mode with no echo.
   conbin(esc) -- Put the console in binary (raw) mode.
   conres()  -- Restore the console to mode obtained by congm().
   conoc(c)  -- Unbuffered output, one character to console.
   conol(s)  -- Unbuffered output, null-terminated string to the console.
   conola(s) -- Unbuffered output, array of strings to the console.
   conxo(n,s) -- Unbuffered output, n characters to the console.
   conchk()  -- Check if characters available at console.
   coninc(timo)  -- Timed get a character from the console.
   congks(timo)  -- Timed get keyboard scan code.
   conint()  -- Enable terminal interrupts on the console if not background.
   connoi()  -- Disable terminal interrupts on the console if not background.

Time functions

   msleep(m) -- Millisecond sleep
   ztime(&s) -- Return pointer to date/time string
   rtimer() --  Reset timer
   gtimer()  -- Get elapsed time since last call to rtimer()
*/

/* Declarations */

/* dftty is the device name of the default device for file transfer */
/* dfloc is 0 if dftty is the user's console terminal, 1 if an external line */

#ifndef DFTTY
    char *dftty = "(terminal_name)";
    char *dfmdm = "none";
    int dfloc = 0;
#else
    char *dftty = DFTTY;		/* Default location specified on */
    char *dfmdm = "none";		/* command line. */
    int dfloc = 0;                      /* controlling terminal name. */
#endif /* DFTTY */

    int dfprty = 0;                     /* Default parity (0 = none) */
    int ttprty = 0;                     /* The parity that is in use. */
    int ttpflg = 0;			/* Parity not sensed yet. */
    static int ttpmsk = 0377;		/* Parity stripping mask. */
    int ttmdm = 0;                      /* Modem in use. */
    int ttcarr = CAR_AUT;		/* Carrier handling mode. */
    int dfflow = FLO_XONX;		/* Default is Xon/Xoff */
    int backgrd = 0;                    /* Assume in foreground (no '&' ) */

    int fdflag = 0;			/* Flag for redirected stdio */
    int ttfdflg = 0;			/* Open File descriptor was given */
    int tvtflg = 0;			/* Flag that ttvt has been called */
    long ttspeed = -1;			/* For saving speed */
    long comspd = -1;			/* speed of console device */
    int ttflow = -9;			/* For saving flow */
    int ttld = -1;			/* Line discipline */

extern int ttnproto;			/* Defined in ckcnet.c */
extern int ttnet;			/* Defined in ckcnet.c */
extern int xfrcan, xfrchr, xfrnum;	/* Defined in ckcmai.c */

int ckxech = 0; /* 0 if system normally echoes console characters, else 1 */

/* Declarations of variables global within this module */

static time_t tcount;			/* Elapsed time counter */
static SIGTYP (*saval)() = NULL;	/* For saving alarm() handler */
static long vosbaud[] = {0, 50, 75, 110, 134, 150, 300, 600, 1200, 1800,
	2400, 3600, 4800, 7200, 9600, 19200, 38400, 76800, 56000, 64000};
#define NUMBAUDS	((sizeof vosbaud) / (sizeof vosbaud[0]))

/*
  BREAKNULS is defined for systems that simulate sending a BREAK signal
  by sending a bunch of NUL characters at low speed.  This may be all we
  can do, if we don't have support for TERM_SEND_BREAK.
*/

static char				/* A string of nulls */
*brnuls = "\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0";

static jmp_buf sjbuf;			/* Longjump buffers */
static jmp_buf jjbuf;			/* Longjump buffers */

/* these are for CONNECT processing */
static long eventids[2];	/* 0 is con, 1 is tty, same as src argument */
static long eventcnts[2];
static int no_wait = 0; 

static char cinbuff[256];
static int  cincnt;
static int  cinbp;

char myttystr[DEVNAMLEN+1];		/* name of console device */

/* static */				/* (Not static any more) */
int ttyfd = -1;				/* TTY file descriptor */

int telnetfd = 0;			/* File descriptor is for telnet */
int x25fd = 0;				/* File descriptor is for X.25 */

static int lkf = 0,                     /* Line lock flag */
    cgmf = 0,                           /* Flag that console modes saved */
    xlocal = 0,                         /* Flag for tty local or remote */
    curcarr = 0;			/* Carrier mode: require/ignore. */

static int netconn = 0;			/* 1 if network connection active */
static char escchr;                     /* Escape or attn character */

/* this is for our incredible ALARM hack.  it sucks wind, but it works. */
/* if Stratus would add alarm() to the runtime, we wouldn't have to do this. */
/* at least SIGALRM is defined, and kill() works... */

static time_t	deadline = 0;
#define chkalrm() ((deadline) ? chkalarm () : 0)
_PROTOTYP(chkalarm, (void));

/* these are various terminal info modes, when filled in */

static long
    ttold, ttraw, tttvt, ttcur,
    ccold, ccraw, cccbrk;

static int plines = 0;

static int conraw = 0;	/* is console in a raw mode?  0=no, 1=cb, 2=bin */
static int ttyraw = 0;			/* ditto for the tty */

static int conesc = 0;                  /* set to 1 if esc char (^\) typed */

static char ttnmsv[DEVNAMLEN];		/* Copy of open path for tthang */
static char ttnmful[DEVNAMLEN];		/* full path name of device     */

/* ANSI-style prototypes for internal functions. */
/* Functions used outside this module are prototyped in ckcker.h. */

_PROTOTYP( VOID cancio, (void) );

_PROTOTYP( SIGTYP timerh, (int) );
_PROTOTYP( SIGTYP cctrap, (int) );
_PROTOTYP( SIGTYP esctrp, (int) );

_PROTOTYP( int do_open, (char *) );
_PROTOTYP( VOID conbgt, (int) );
#ifdef ACUCNTRL
_PROTOTYP( VOID acucntrl, (char *, char *) );
#endif /* ACUCNTRL */
_PROTOTYP( int netwait, (int, int, long *, long *) );
_PROTOTYP( int carrctl, (long *, int) );
_PROTOTYP( static int conout, (char *, int) );

/* same as isatty, but uses port ids instead of file descriptors.
 * file descriptors are an artifact of the c-runtime on VOS, so we use
 * ports to get some reasonable performance.  open/close/read/write use
 * file descriptors, and the s$ calls use ports.  The control operations
 * involved in changing from binary to normal modes are not in the c 
 * runtime (ioctl and its kin), so we need to use ports for that, and the
 * c runtime loses its mind if you change the modes behind its back.
 */
_PROTOTYP( int vostty, (int) );
_PROTOTYP( static char *connam, (void) ); /* roll our own ttyname() */
_PROTOTYP( long congspd, (void) ); /* returns speed of console device */

/* prototypes for VOS calls */

_PROTOTYP( extern VOID s$attach_port, (CV(32) *port_name, 
	CV(256)* path, short *hold, short *port_id, short *status) );

_PROTOTYP( extern VOID s$close, (short *port_id, short *status) );

_PROTOTYP( extern VOID s$control, (short *port_id, short *opcode,
	VOID *control, short *status) );

_PROTOTYP( extern VOID s$detach_port, (short *port_id, short *status) );

_PROTOTYP( extern VOID s$get_data_set_status, (CV(66) *channel,
	VOID *info, short *status) );

_PROTOTYP( extern VOID s$get_lockers, (CV(256) *path, short *maxlock,
	short *nbrlock, long *pids, short *locktyp, short *status) );

_PROTOTYP (extern VOID s$get_port_info, (short *port,
	get_port_info *pinfo, short *status) );

_PROTOTYP( extern VOID s$get_process_id, (long *process_id) );

_PROTOTYP( extern VOID s$get_process_info, (long *process_id, VOID *info,
	short *status) );

_PROTOTYP( extern VOID s$expand_module_name, (CV(66) *src,
	CV(66) *dest, short *status) );

_PROTOTYP( extern VOID s$get_home_dir, (CV(256) *path) );

_PROTOTYP( extern int s$is_file_type_a_terminal, (short *type) );

_PROTOTYP( extern void s$jiffy_date_time, (void *jiffy) );

_PROTOTYP( extern VOID s$open, (short *port_id, short *organization,
	short *max_rec_len, short *io_type, short *locking_type,
	short *access_mode, CV(32) *index_name, short *status) );

_PROTOTYP( extern VOID s$read_event, (long *event_id, long *event_count,
	short *event_status, short *status) );

_PROTOTYP( extern VOID s$read_raw, (short *port_id, short *buff_size,
	short *rec_len, VOID *buffer, short *status) );

_PROTOTYP( extern VOID s$seq_open, (CV(256) *port_id, short *io_type, 
	short *port, short *status) );

_PROTOTYP( extern VOID s$seq_read, (short *port_id, short *buff_size,
	 short *rec_len, VOID *buffer, short *status) );

_PROTOTYP( extern VOID s$seq_write_partial, (short *port_id,
	 short *rec_len, VOID *buffer, short *status) );

_PROTOTYP( extern VOID s$set_io_time_limit, (short *port, long *timeout,
	short *status) );

_PROTOTYP( extern VOID s$set_no_wait_mode, (short *port_id, long *event_id,
	short *status) );

_PROTOTYP( extern VOID s$set_wait_mode, (short *port_id, short *status) );

_PROTOTYP( extern VOID s$sleep, (int *ticks, short *status) );

_PROTOTYP( extern VOID s$wait_event, (short *count, long *event_id_array,
	long *event_count_array, long *timeout, short *which_event,
	short *status) );

_PROTOTYP( extern VOID s$write_raw, (short *port_id, short *length,
	 void *buffer, short *status) );

#ifdef CK_ANSIC
static char *
xxlast(char *s, char c)
#else
static char *
xxlast(s,c) char *s; char c;
#endif /* CK_ANSIC */
/* xxlast */ {		/*  Last occurrence of character c in string s. */
    return(strrchr (s, c));
}

/* Timeout handler for communication line input functions */

SIGTYP
timerh(foo) int foo; {
    ttimoff();
    longjmp(sjbuf,1);
}

/* Control-C trap for communication line input functions */

int cc_int;				/* Flag */
SIGTYP (* occt)();			/* For saving old SIGINT handler */

SIGTYP
cctrap(foo) int foo; {			/* Needs arg for ANSI C */
  cc_int = 1;				/* signal() prototype. */
  debug(F101,"cctrap arg","",foo);
  SIGRETURN;
}

/*  S Y S I N I T  --  System-dependent program initialization.  */

int
sysinit() {
    int x;
    short conport = TERMINAL_PORT_ID;
    short opcode = SET_PAUSE_LINES_OPCODE;
    short status;
    short lines = 0;

    strcpy (myttystr, connam());
    dftty = myttystr;
    strcpy (ttnmful, myttystr);

    conbgt(0);				/* See if we're in the background */
    congm();				/* save console modes */

/* Initialize the setuid package. */
/* Change to the user's real user and group id. */
/* If this can't be done, don't run at all. */

    if (x = priv_ini()) {
	if (x | 1) fprintf(stderr,"Fatal: setuid failure.\n");
	if (x | 2) fprintf(stderr,"Fatal: setgid failure.\n");
	if (x | 4) fprintf(stderr,"Fatal: C-Kermit setuid to root!\n");
	exit(1);
    }

    if (!backgrd && vostty(conport)) {	/* only if at terminal */
	s$control (&conport, &opcode, &lines, &status);
    }

    return(0);
}

/*  S Y S C L E A N U P  --  System-dependent program cleanup.  */

int
syscleanup() {
    short conport = TERMINAL_PORT_ID;
    short opcode = SET_PAUSE_LINES_OPCODE;
    short status;
    short lines = plines;

    /* No need to call anything in the suid package here, right? */

    if (!backgrd && vostty(conport)) {	/* only if at terminal */
	s$control (&conport, &opcode, &lines, &status);
    }

    return(0);
}

/*  T T O P E N  --  Open a tty for exclusive access.  */

/*
  Call with:
    ttname: character string - device name or network host name.
    lcl:
  If called with lcl < 0, sets value of lcl as follows:
  0: the terminal named by ttname is the job's controlling terminal.
  1: the terminal named by ttname is not the job's controlling terminal.
  But watch out: if a line is already open, or if requested line can't
  be opened, then lcl remains (and is returned as) -1.
    modem:
  Less than zero: ttname is a network host name.
  Zero or greater: ttname is a terminal device name.    
  Zero means a local connection (don't use modem signals).
  Positive means use modem signals.  
   timo:
  0 = no timer.
  nonzero = number of seconds to wait for open() to return before timing out.

  Returns:
    0 on success
   -5 if device is in use
   -4 if access to device is denied
   -3 if access to lock directory denied
   -2 upon timeout waiting for device to open
   -1 on other error
*/
int
ttopen(ttname,lcl,modem,timo) char *ttname; int *lcl, modem, timo; {

    char *x;
    int  temp;

    char cname[DEVNAMLEN+4];

    debug(F111,"ttopen called ttname & timo",ttname,timo);
    if (ttyfd > -1) {			/* if device already opened */
	debug(F111,"ttopen ttyfd set, ttnmsv",ttnmsv,ttyfd);
        if (strncmp(ttname,ttnmsv,DEVNAMLEN)) { /* are new & old names same? */
	    ttclos(ttyfd);		/* no, close old ttname, open new */
        }
        else 				/* else same, ignore this call, */
	    return(0);			/* and return. */
    }

    chkalrm();

    /* we save the name we were called with, full name goes elsewhere */
    strcpy(ttnmsv,ttname);

#ifdef	NETCONN
    if (modem < 0) {			/* modem < 0 = special code for net */
	int x;
	ttmdm = modem;
	modem = -modem;			/* Positive network type number */
	ttnet = modem;			/* save this */
	fdflag = 0;			/* Stdio not redirected. */
	netconn = 1;			/* And it's a network connection */
	debug(F111,"ttopen net",ttname,modem);
#ifdef NAMEFD
	for (p = ttname; isdigit(*p); p++) ; /* Check for all digits */
 	if (*p == '\0' && (telnetfd || x25fd)) { /* Avoid X.121 addresses */
	    ttyfd = atoi(ttname);	/* Is there a way to test it's open? */
	    ttfdflg = 1;		/* We got an open file descriptor */
	    debug(F111,"ttopen got open network fd",ttname,ttyfd);
	    x = 1;			/* Return code is "good". */
	    if (telnetfd) {
		ttnet = NET_TCPB;
		ttnproto = NP_TELNET;
#ifdef STRATUSX25
	    } else if (x25fd) {
		ttnet = NET_VX25;
		ttnproto = NP_NONE;
#endif /* STRATUSX25 */
	    }
	} else {			/* Host name or address given */
#endif /* NAMEFD */
	    x = netopen(ttname, lcl, modem); /* (see ckcnet.h) */
	    if (x > -1) {
		strncpy(ttnmsv,ttname,DEVNAMLEN);
	    } else netconn = 0;
#ifdef NAMEFD
	}
#endif /* NAMEFD */

        chkalrm();

	xlocal = *lcl = 1;		/* Network connections are local. */
	debug(F101,"ttopen net x","",x);

#ifdef TCPSOCKET
	if (x > -1 && ttnet == NET_TCPB)
	  x = tn_ini();			/* Initialize TELNET protocol */
#endif /* TCPSOCKET */
	return(x);
    } else {				/* Terminal device */
#endif	/* NETCONN */

#ifdef NAMEFD
/*
  This code lets you give Kermit an open file descriptor for a serial
  communication device, rather than a device name.  Kermit assumes that the
  line is already open, locked, conditioned with the right parameters, etc.
*/
	for (p = ttname; isdigit(*p); p++) ; /* Check for all digits */
	if (*p == '\0') {
	    ttyfd = atoi(ttname);	/* Is there a way to test it's open? */
	    debug(F111,"ttopen got open fd",ttname,ttyfd);
	    strncpy(ttnmsv,ttname,DEVNAMLEN); /* Remember the "name". */
	    xlocal = *lcl = 1;		/* Assume it's local. */
	    netconn = 0;		/* Assume it's not a network. */
	    tvtflg = 0;			/* Might need to initialize modes. */
	    ttmdm = modem;		/* Remember modem type. */
	    fdflag = 0;			/* Stdio not redirected. */
	    ttfdflg = 1;		/* Flag we were opened this way. */

	    return(0);			/* Return success */
	}
#endif /* NAMEFD */
#ifdef NETCONN
    }
#endif /* NETCONN */

/* Here we have to open a serial device of the given name. */

    occt = signal(SIGINT, cctrap);	/* Set Control-C trap, save old one */

    tvtflg = 0;			/* Flag for use by ttvt(). */
				/* 0 = ttvt not called yet for this device */

    ttmdm = modem;                      /* Make this available to other fns */
    xlocal = *lcl;                      /* Make this available to other fns */

    /* do_open need only handle TERMINAL/PRINTER/MODEM ports */

    if (0 > (temp = do_open(ttname))) {
        debug(F111,"  do_open returned error",ttname,temp);
        return(temp);
    }

    chkalrm();

    /* Make sure it's a real tty. */
    if (!vostty(ttyfd)) {
	short ttyport = ttyfd;
	short status;

	fprintf(stderr,"%s is not a tty!\n",ttname);
	debug(F110,"ttopen not a tty",ttname,0);
	s$close (&ttyport, &status);
	s$detach_port (&ttyport, &status);
	ttyfd = -1;
	signal(SIGINT,occt);
	return(-1);
    }

    if (*lcl != 0) *lcl = xlocal;

    strcpy(cname,connam());
    if (*lcl == -1) {
	if (strcmp(cname, ttname) == 0) /* this is the controlling terminal */
	    *lcl = 0; /* that means remote mode */
	else if (strcmp("(terminal_name)", ttname) == 0)
	    *lcl = 0;
	else
	    *lcl = 1;
    }

    /* Get current speed */

    ttspeed = ttgspd();
    debug(F101,"ttopen ttspeed","",ttspeed);

    msleep (250);			/* give it a little bit */

#ifdef COMMENT
    /* the modes have not been set yet, so don't do this */
    ttflui ();				/* get rid of noise in tty buffer */
#endif	

    /* Done, make entries in debug log, restore Ctrl-C trap, and return. */
    debug(F101,"ttopen, ttyfd","",ttyfd);
    debug(F101,"        lcl","",*lcl);
    if (*lcl != -1)
	xlocal = *lcl;
    signal(SIGINT,occt);
    chkalrm();
    return(0);
}


/*  D O _ O P E N  --  Actually do an open for the tty. */

int
do_open(ttname) char *ttname; {
    CV(66) ttncv;
    CV(66) expnam;
    CV(32) portnam;
    CV(32) index;
    char buf[300];
    short ttyport;
    short status;
    short lock;
    short hold;
    short io_type;
    short access;
    short reclen;
    short org;
    short opcode;
    short maxlock;
    short nbrlock;
    long  pids[1];	/* one is enough */
    TERMINAL_INFO tinfo;

    ttyfd = -1;

    if (0 == strcmp (ttname, "(terminal_name)"))
	ttname = myttystr;

    if (strcmp (myttystr, ttname)) { /* not console */
	strcpy (&ttncv, ttname);
	s$expand_module_name (&ttncv, &expnam, &status);
	debug(F111,"do_open s$expand_module_name status",ttname,status);
	if (status)
	    return -1;

	strcpy (ttnmful, &expnam);	/* save this for later */
	maxlock = 0;
	nbrlock = 0;
	pids[0] = 0;
	lock = ANY_LOCK;
	s$get_lockers ((CV(256) *) &expnam, &maxlock, &nbrlock,
	    pids, &lock, &status);

	if (status) {
	    debug(F111,"do_open s$get_lockers status",ttnmful,status);
	    return -1;		/* an error occurred */
	}

	if (nbrlock) {
	    debug(F111,"do_open s$lockers in use",ttnmful,nbrlock);
	    return -5;		/* device is in use */
        }

	strcpy (&portnam, "kermtty");
	hold = DONT_HOLD;
	s$attach_port (&portnam, (CV(256) *) &expnam,
	    &hold, &ttyport, &status);
	if (status) {
	    debug(F111,"do_open s$attach_port status",ttnmful,status);
	    return -1;
	}

	io_type = UPDATE_TYPE;
	lock = IMPLICIT_LOCKING;
	reclen = 2048;
	access = SEQUENTIAL_MODE;
	org = SEQUENTIAL_FILE;
	strcpy (&index, "");
	s$open (&ttyport, &org, &reclen, &io_type, &lock,
	     &access, &index, &status);
	if (status) {
	    debug(F111,"do_open s$attach_port status",ttnmful,status);
	    s$detach_port(&ttyport, &status);	/* make sure it's detached */
	    return -1;
	}

	ttyfd = ttyport;
    }
    else {
	ttyfd = TERMINAL_PORT_ID;
    }

    if (ttyfd == -1)
	return(-1);

    debug(F100,"do_open getting modes","",0); /* Need to do it. */

    ttyport = ttyfd; /* make sure it is set here. */
    opcode = GET_INFO_OPCODE;
    tinfo.version = TERMINAL_INFO_VERSION_2;
    s$control (&ttyport, &opcode, &tinfo, &status);
    if (status) {
	debug(F111,"do_open s$control get_info status",ttnmful,status);
	s$close (&ttyport, &status);
	s$detach_port (&ttyport, &status);
	ttyfd = -1;
	return -1;
    }

    ttold = tinfo.modes;
    ttcur = tinfo.modes;
    ttraw = tinfo.modes;

    ttraw &= ~(OS_FUNCTION_KEY_INPUT | OS_BREAK_TABLE_RECORD
	| OS_GENERIC_INPUT | OS_FORMS_INPUT);
    ttraw |= OS_RAW_INPUT | OS_BULK_RAW_INPUT;

    tttvt = ttraw;

    debug(F111,"do_open return success, fd",ttnmful,ttyfd);

    return(0);
}

/*  T T C L O S  --  Close the TTY, releasing any lock.  */

int
ttclos(foo) int foo; {
    short status;
    short ttyport;

    debug(F110,"ttclos called",ttnmsv,0);
    strcpy (ttnmsv, ""); /* nothing opened right now */
    chkalrm();

#ifdef NETCONN
    if (netconn)
	return netclos();
#endif

    ttyport = ttyfd;
    ttyfd = -1;					/* wipe it out here */

    /* don't close the console.  That would be bad. */

    if (ttyport >= 0 && ttyport != TERMINAL_PORT_ID) {
	s$close (&ttyport, &status);
	if (status) {
	    debug(F101,"ttclos s$close status","",status);
	    return (-1);
	}
	s$detach_port (&ttyport, &status);
	if (status) {
	    debug(F101,"ttclos s$detach_port status","",status);
	    return (-1);
	}
    }

    chkalrm();
    return(0);
}

/*  T T H A N G  --  Hangup phone line or network connection.  */
/*
  Returns:
  0 if it does nothing.
  1 if it believes that it hung up successfully.
 -1 if it believes that the hangup attempt failed.
*/

#define HUPTIME 500			/* Milliseconds for hangup */

int
tthang() {
    short ttyport;
    short status;
    short opcode;
    short dummy;
    char  oldtty[DEVNAMLEN+1];
    int   rv;   /* return value */
    long  oldspeed;
    int   oldflow;

    if (ttyfd < 0)
	return 0; 	/* no action taken */

    chkalrm();

#ifdef NETCONN
    if (netconn)
        return((netclos() < 0) ? -1 : 1);	/* Just close it. */
#endif

    ttyport = ttyfd;
    if (ttyport == TERMINAL_PORT_ID)
	return 0;	/* don't hang up console */

    if (ttfdflg) return(0);		/* Don't mess with terminal if */
					/* we got ttyfd from another process */

    opcode = ASYNC_HANGUP_OPCODE;
    dummy = 0;
    s$control (&ttyport, &opcode, &dummy, &status);
    if (status) {
	debug(F111,"tthang s$control ASYNC_HANGUP status",ttnmful,status);
	return -1;
    }

    msleep(HUPTIME);

    chkalrm();
    rv = 1;

#ifdef CLSOPN
/*
 * This may be needed to re-arm the async port.
 * This will REALLY hammer a vterm connection, though.
 */
    strcpy (oldtty, ttnmsv);
    debug(F111,"tthang CLSOPN xlocal",oldtty,xlocal);
    oldspeed = ttspeed;
    oldflow = ttflow;
    ttclos ();
    sleep (2);	/* wait a little bit, to let modem figure it out */
    rv = ttopen (oldtty, &xlocal, ttmdm, 5);
    if (!rv)
	rv = ttvt(oldspeed,oldflow);
#endif

    return rv;		/* success */
}

/*  T T R E S  --  Restore terminal to "normal" mode.  */

/* ske@pkmab.se: There are two choices for what this function should do.
 * (1) Restore the tty to current "normal" mode, with carrier treatment
 * according to ttcarr, to be used after every kermit command. (2) Restore
 * the tty to the state it was in before kermit opened it. These choices
 * conflict, since ttold can't hold both choices of tty parameters.  ttres()
 * is currently being called as in choice (1), but ttold basically holds
 * the initial parameters, as in (2), and the description at the beginning
 * of this file says (2).
 *
 * I don't think restoring tty parameters after all kermit commands makes
 * much of a difference.  Restoring them upon exit from kermit may be of
 * some use in some cases (when the line is not restored automatically on
 * close, by the operating system).
 *
 * I can't choose which one it should be, so I haven't changed it. It
 * probably works as it is, too. It would probably even work even with
 * ttres() entirely deleted...
 *
 * (from fdc: Actually, this function operates in remote mode too, so
 * it restores the console (command) terminal to whatever mode it was
 * in before packet operations began, so that commands work right again.)
 */
int
ttres() {                               /* Restore the tty to normal. */
    short ttyport;
    short opcode;
    short status;

    chkalrm();
    if (ttyfd < 0) return(-1);          /* Not open. */

    if (ttfdflg) return(0);		/* Don't mess with terminal modes if */
					/* we got ttyfd from another process */
#ifdef	NETCONN
    if (netconn) return (0);		/* Network connection, do nothing */
#endif	/* NETCONN */

    tvtflg = 0;				/* Invalidate terminal mode settings */

    ttyport = ttyfd;
    opcode = SET_MODES_OPCODE;
    s$control (&ttyport, &opcode, &ttold, &status);
    if (status) {
	debug(F111,"ttres s$control SET_MODES status",ttnmful,status);
	return -1;
    }

    ttcur = ttold;
    ttyraw = 0;
    chkalrm();
    return(0);
}

/*
  T T H F L O W  --  Set hardware flow control.
*/
static int
tthflow(flow) int flow; {
    int x = 0;				/* Return code */
    chkalrm();

    return(x);
}

/*  T T P K T  --  Condition the communication line for packets */
/*                 or for modem dialing */

/*
  If called with speed > -1, also set the speed.
  Returns 0 on success, -1 on failure.

  NOTE: the "xflow" parameter is supposed to be the currently selected
  type of flow control, but for historical reasons, this parameter is also
  used to indicate that we are dialing.  Therefore, when the true flow
  control setting is needed, we access the external variable "flow", rather
  than trusting our "xflow" argument.
*/
int
#ifdef CK_ANSIC
ttpkt(long speed, int xflow, int parity)
#else
ttpkt(speed,xflow,parity) long speed; int xflow, parity;
#endif /* CK_ANSIC */
/* ttpkt */ {
    int s2;
    int s = -1;
    int x;
    short status;
    short ttyport;
    short opcode;
    term_configure config;

    extern int flow;			/* REAL flow-control setting */

    chkalrm();
    if (ttyfd < 0) return(-1);          /* Not open. */

    debug(F101,"ttpkt parity","",parity);
    debug(F101,"ttpkt xflow","",xflow);
    debug(F101,"ttpkt speed","",(int) speed);

#ifdef NETCONN
    if (netconn) {
	chkalrm();
	tvtflg = 1;			/* Network connections */
	return(0);			/* require no special setup */
    }
#endif /* NETCONN */

    tvtflg = 0;

    ttprty = parity;                    /* Let other tt functions see these. */
    ttpflg = 0;				/* Parity not sensed yet */
    ttpmsk = ttprty ? 0177 : 0377;	/* Parity stripping mask */
    ttspeed = speed;			/* Make global copy for this module */

    if (ttfdflg && !vostty(ttyfd)) return(0);	/* Don't change if not a tty */

    ttyport = ttyfd;
    opcode = SET_MODES_OPCODE;
    s$control (&ttyport, &opcode, &ttraw, &status);
    if (status) {
	debug(F111,"ttpkt s$control SET_MODES status",ttnmful,status);
	return -1;
    }

    chkalrm();
    ttyraw = 1;		/* remember it's in raw mode */
    ttcur = ttraw;

    opcode = TERM_GET_CONFIG_OPCODE;
    s$control (&ttyport, &opcode, &config, &status);
    if (status) {
	debug(F111,"ttpkt s$control TERM_GET_CONFIG status",ttnmful,status);
	return -1;
    }

    if (ttspeed > -1 && xlocal) {
	switch (ttspeed) {
	    case 50:	config.baud = OS_BAUD_50;	break;
	    case 75:    config.baud = OS_BAUD_75;	break;
	    case 110:	config.baud = OS_BAUD_110;	break;
	    case 150:	config.baud = OS_BAUD_150;	break;
	    case 300:	config.baud = OS_BAUD_300;	break;
	    case 600:	config.baud = OS_BAUD_600;	break;
	    case 1200:	config.baud = OS_BAUD_1200;	break;
	    case 2400:	config.baud = OS_BAUD_2400;	break;
	    case 3600:	config.baud = OS_BAUD_3600;	break;
	    case 4800:	config.baud = OS_BAUD_4800;	break;
	    case 7200:	config.baud = OS_BAUD_7200;	break;
	    case 9600:	config.baud = OS_BAUD_9600;	break;
	    case 19200:	config.baud = OS_BAUD_19200;	break;
	    case 38400:	config.baud = OS_BAUD_38400;	break;
	    default:					break;
	}
    }
    switch (ttprty) {
	case 0:		config.parity = OS_NO_PARITY;	break;
	case 'e':	config.parity = OS_EVEN_PARITY; break;
	case 'o':	config.parity = OS_ODD_PARITY;	break;
	case 'm':	config.parity = OS_MARK_PARITY;	break;
	case 's':	config.parity = OS_SPACE_PARITY;break;
	default:					break;
    }

    opcode = TERM_CONFIGURE_OPCODE;
    s$control (&ttyport, &opcode, &config, &status);
    if (status) {
	debug(F111,"ttpkt s$control TERM_CONFIGURE status",ttnmful,status);
	/* DON'T RETURN FAILURE!  window_term's can't do this! */
    }

    /* we don't mess with the flow control */ /**/
    chkalrm();

    return (0);
}

/*  T T V T -- Condition communication line for use as virtual terminal  */

int
#ifdef CK_ANSIC
ttvt(long speed, int flow)
#else
ttvt(speed,flow) long speed; int flow;
#endif /* CK_ANSIC */
/* ttvt */ {
    int s, s2;
    short status;
    short ttyport;
    short opcode;
    term_configure config;

    debug(F101,"ttvt ttyfd","",ttyfd);
    debug(F101,"ttvt tvtflg","",tvtflg);
    debug(F101,"ttvt speed","",speed);
    chkalrm();
    if (ttyfd < 0) return(-1);          /* Not open. */

#ifdef NETCONN
    if (netconn) {
	tvtflg = 1;			/* Network connections */
	debug(F101,"ttvt netconn","",netconn);
	return(0);			/* require no special setup */
    }
#endif /* NETCONN */

    if (tvtflg != 0 && speed == ttspeed && flow == ttflow && ttcarr == curcarr)
      return(0);			/* Already been called. */

    if (ttfdflg && !vostty(ttyfd)) return(0);

    ttspeed = speed;
    ttflow = flow;

    chkalrm();
    if (xlocal) {			/* For external lines... */
	s2 = (int) (speed / 10L);
	s = ttsspd(s2);			/* Check/set the speed */
	carrctl(&tttvt, flow != FLO_DIAL /* Do carrier control */
		&& (ttcarr == CAR_ON || (ttcarr == CAR_AUT && ttmdm != 0)));
    } else s = s2 = -1;

    ttyport = ttyfd;
    opcode = SET_MODES_OPCODE;
    s$control (&ttyport, &opcode, &ttraw, &status);
    if (status) {
	debug(F111,"ttvt s$control SET_MODES status",ttnmful,status);
	return -1;
    }

    ttyraw = 1;		/* remember it's in raw mode */
    ttcur = ttraw;
    chkalrm();

    opcode = TERM_GET_CONFIG_OPCODE;
    s$control (&ttyport, &opcode, &config, &status);
    if (status)  {
	debug(F111,"ttvt s$control TERM_GET_CONFIG status",ttnmful,status);
	return -1;
    }

    if (ttspeed > -1 && xlocal) {
	switch (ttspeed) {
	    case 50:	config.baud = OS_BAUD_50;	break;
	    case 75:    config.baud = OS_BAUD_75;	break;
	    case 110:	config.baud = OS_BAUD_110;	break;
	    case 150:	config.baud = OS_BAUD_150;	break;
	    case 300:	config.baud = OS_BAUD_300;	break;
	    case 600:	config.baud = OS_BAUD_600;	break;
	    case 1200:	config.baud = OS_BAUD_1200;	break;
	    case 2400:	config.baud = OS_BAUD_2400;	break;
	    case 3600:	config.baud = OS_BAUD_3600;	break;
	    case 4800:	config.baud = OS_BAUD_4800;	break;
	    case 7200:	config.baud = OS_BAUD_7200;	break;
	    case 9600:	config.baud = OS_BAUD_9600;	break;
	    case 19200:	config.baud = OS_BAUD_19200;	break;
	    case 38400:	config.baud = OS_BAUD_38400;	break;
	    default:					break;
	}
    }
    switch (ttprty) {
	case 0:		config.parity = OS_NO_PARITY;	break;
	case 'e':	config.parity = OS_EVEN_PARITY; break;
	case 'o':	config.parity = OS_ODD_PARITY;	break;
	case 'm':	config.parity = OS_MARK_PARITY;	break;
	case 's':	config.parity = OS_SPACE_PARITY;break;
	default:					break;
    }

    opcode = TERM_CONFIGURE_OPCODE;
    s$control (&ttyport, &opcode, &config, &status);
    if (status) {
	debug(F111,"ttvt s$control TERM_CONFIGURE status",ttnmful,status);
	/* DON'T RETURN FAILURE!  window_term's can't do this! */
    }

    /* we don't mess with the flow control */ /**/

    chkalrm();
    tvtflg = 1;
    debug(F101,"ttvt done, setting flag","",tvtflg);

    return(0);
}

/*  T T S S P D  --  Checks and sets transmission rate.  */

/*  Call with speed in characters (not bits!) per second. */
/*  Returns internal speed code if successful, -1 otherwise. */

int
ttsspd(cps) int cps; {
    int s, s2;
    short ttyport;
    short status;
    short opcode;
    term_configure config;

    debug(F101,"ttsspd","",cps);
    chkalrm();

#ifdef NETCONN
    if (netconn) return (0);	
#endif /* NETCONN */

    if (cps < 0) return(-1);
    s = s2 = -1;

    /* First check that the given speed is valid. */
    switch (cps)  {
	case 5:		s = OS_BAUD_50;		break;
	case 7:		s = OS_BAUD_75;		break;
	case 11:	s = OS_BAUD_110;	break;
	case 15:	s = OS_BAUD_150;	break;
	case 30:	s = OS_BAUD_300;	break;
	case 60:	s = OS_BAUD_600;	break;
	case 120:	s = OS_BAUD_1200;	break;
	case 240:	s = OS_BAUD_2400;	break;
	case 360:	s = OS_BAUD_3600;	break;
	case 480:	s = OS_BAUD_4800;	break;
	case 720:	s = OS_BAUD_7200;	break;
	case 960:	s = OS_BAUD_9600;	break;
	case 1920:	s = OS_BAUD_19200;	break;
	case 3840:	s = OS_BAUD_38400;	break;
      default:
	debug(F111,"ttsspd","INVALID SPEED",cps);
	return(-1);
    }
    /* Actually set the speed */

    chkalrm();
    if (ttyfd > -1 && s > -1 && xlocal != 0) {
	if (s2 == -1) s2 = s;
	ttyport = ttyfd;
	opcode = TERM_GET_CONFIG_OPCODE;
	s$control (&ttyport, &opcode, &config, &status);
	if (status) {
	    debug(F101,"ttsspd s$control get config status","",status);
	    return (-1);
	}
	config.baud = s;
	opcode = TERM_CONFIGURE_OPCODE;
	s$control (&ttyport, &opcode, &config, &status);
	if (status) {
	    debug(F101,"ttsspd s$control term configure status","",status);
	    return (-1);
        }
    }
    else {
	debug(F101,"ttsspd ignoring request, xlocal","",xlocal);
    }

    ttspeed = cps * 10;

    chkalrm();
    return(1);
}

/* T T G S P D  -  Get speed of currently selected tty line  */

/*
   This isn't always 100% accurate.  Some types of devices lie a lot about
   their speed (VTERMs are the worst about this).  If in local mode, it
   should call congspd() to find out either what VOS thinks the speed is,
   which may or may not be right, or what the user said the speed is, say
   if you are using a PAD or front-end that lies about the speed.
*/
long
ttgspd() {				/* Get current tty speed */
    int s; long ss;
    short ttyport;
    short opcode;
    short status;
    term_configure config;

    chkalrm();

#ifdef NETCONN
    if (netconn) return(-1);		/* -1 if network connection */
#endif /* NETCONN */

    if (ttyfd < 0) {
        return (-1);
    }

    if (ttyfd > -1 && xlocal != 0) {
	ttyport = ttyfd;
	opcode = TERM_GET_CONFIG_OPCODE;
	s$control (&ttyport, &opcode, &config, &status);
	if (status)  {
	    debug(F111,"ttgspd s$control TERM_GET_CONFIG status",
		ttnmful,status);
	    return -1;
	}

	if (config.baud < 0 || config.baud >= NUMBAUDS)
	    ttspeed = 0;
	else
	    ttspeed = vosbaud[config.baud];
    }

    chkalrm();

    debug(F101,"ttgspd speed","",ttspeed);
    return(ttspeed);
}

long
congspd() {				/* Get current console speed */
    int s; long ss;
    short conport;
    short opcode;
    short status;
    term_configure config;

    chkalrm();

    if (comspd > 0) {
	debug(F101,"congspd returning saved speed","",comspd);
	return (comspd);
    }

    conport = TERMINAL_PORT_ID;		/* always ask about TERMINAL */
    opcode = TERM_GET_CONFIG_OPCODE;
    s$control (&conport, &opcode, &config, &status);
    if (status) {
	debug(F111,"congspd s$control TERM_GET_CONFIG status",ttnmful,status);
	return -1;
    }

    if (config.baud < 0 || config.baud >= NUMBAUDS)
	comspd = 0;
    else
	comspd = vosbaud[config.baud];

    chkalrm();

    debug(F101,"congspd speed","",comspd);
    return(comspd);
}

/*
  New buffered input scheme.
*/
#define TTXBUFL 1024			/* Internal buffer size */

CHAR 	ttxbuf[TTXBUFL+1];		/* The buffer */
int 	ttxbp = 0, ttxbn = 0;		/* Buffer pointer and count */

#ifdef NETCONN
_PROTOTYP(int netbufr, (int timo));
#endif
/*
  T X B U F R

  Read bytes from communication device into internal buffer ttxbuf[].
  To be called only when input buffer is empty, i.e. when ttxbn == 0.

  Other comm-device reading routines, like ttinc, ttinl, ttxin, should check
  the internal buffer first, and call this routine for a refill if necessary.

  When data is read successfully, the first character is returned and
  the global buffer count, ttxbn, is set to the number of characters remaining
  in ttxbuf after it, and the global buffer offset, ttxbp, is set to 1.

  ttxbp points to the first character which has not yet been read.  ttxbn is
  the number of buffered characters that have not yet been read.

  When data is not read successfully, -1 is returned indicating a timeout,
  or -2 indicating disconnection.
*/
int
txbufr(timo) int timo; {		/* TT Buffer Read */
    short ttyport;
    short status;
    short status2;
    short buflen;
    short readlen;
    long  timeout;

#ifdef COMMENT
    debug(F101,"txbufr entry, timo","",timo);
#endif
    if (ttxbn > 0) {			/* Should not be called */
	debug(F101,"txbufr called with ttxbn","",ttxbn); /* if ttxbn > 0! */
	ttxbn--;
	return(ttxbuf[ttxbp++] & 0xff);
    }
    ttxbp = ttxbn = 0;			/* Reset buffer pointer and count */

    if (timo < 0)			/* Be safe */
      timo = 0;

#ifdef NETCONN
    if(netconn)	 { /* pass it over to the network version */
	buflen = netbufr(timo);
        if (buflen > 0) { /* netbufr set up ttxb* variables with new data */
	    ttxbn--; /* count the one we're sending back */
	    return(ttxbuf[ttxbp++] & 0xff);
	}
	else if (buflen < 0) /* an error */
	    return (buflen); /* pass it back to caller */
        else /* didn't get anything */
	    return (-1); /* no data available */
    }
#endif

    ttyport = ttyfd;

    if (timo > 0) {
	timeout = timo;		/* not in seconds anymore! no conversion */
	s$set_io_time_limit (&ttyport, &timeout, &status);
	if (status)
	    return (-2); /* disconnect */
    }

    buflen = TTXBUFL;			/* Maximum characters to read */
    s$read_raw (&ttyport, &buflen, &readlen, ttxbuf, &status);

    if (timo > 0) {
	timeout = -1; /* clear I/O time limit */
	s$set_io_time_limit (&ttyport, &timeout, &status2);
	if (status2)
	    return (-2);
    }

    /* Check for disconnection */
    if (status == e$line_hangup || status == e$vc_disconnected) {
	debug(F100,"txbufr 1 hangup status","",status);
	return(-2);
    }

    /* check for some codes returned as errors, but with good data */
    if (readlen > 0 && 
	(status == e$caller_must_wait || status == e$short_record))
	status = 0;

    if (status == e$timeout || status == e$caller_must_wait) {
#ifdef COMMENT
	debug(F101, "txbufr returning timeout", "", 0);
#endif
	return(-1);
    }

    if (status != 0) {
	debug(F101,"txbufr ignoring error, status","",status);
	status = 0;
    }

#ifdef COMMENT
    debug(F101,"txbufr readlen","",readlen);
#endif

/*
 * Did anything useful happen?
 */
    if(readlen > 0) {
	ttxbn = readlen;		/* Set buffer count. */
	ttxbn--;			/* Less one for the one we return */
	return(ttxbuf[ttxbp++]);	/* Return it, bump offset */
    }

/*
 * We didn't get data, a timeout, or an error... This is weird.
 */

    return (-2); /* call it an error */
}
/* define a macro to get chars from the buffer */
/* ttxgetc has timo in seconds, txbufr has it in ticks */
#define ttxgetc(timo) (ttxbn ? (ttxbn--, ttxbuf[ttxbp++]) : txbufr(timo<<10))

/*  T T F L U I  --  Flush tty input buffer */

int
ttflui() {
    int n;
    /* do flush of tty input buffer */
/*
  Network flush is done specially, in the network support module.
*/
    chkalrm();
    if (netconn)
	return(netflui());

    debug(F101,"ttflui ttyfd","",ttyfd);
    if (ttyfd < 0)
	 return(-1);

    /* kill what's in the ttxbuf */
    if (ttxbn) {
	ttxbn = 0;
	ttxbp = 0;
    }

    txbufr(10); /* give it a miniscule timeout, .01 second */

    /* kill what's been added to the ttxbuf */
    if (ttxbn) {
	ttxbn = 0;
	ttxbp = 0;
    }

    if ((n = ttchk()) > 0) {
	debug(F101,"ttflui reading","",n);
	while ((n--) && ttinc(0) > 1) ;
    }
    return(0);
}

int
ttfluo() {				/* Flush output buffer */
    chkalrm();
    return(0);				/* (dummy for now) */
}

/* Interrupt Functions */

/* Set up terminal interrupts on console terminal */

#ifndef STRATUS
/* we don't have sigquit.  This was set pretty much the same as SIGINT. */
SIGTYP
esctrp(foo) int foo; {			/* trap console escapes (^\) */
    signal(SIGQUIT,SIG_IGN);            /* ignore until trapped */
    conesc = 1;
    debug(F101,"esctrp caught SIGQUIT","",conesc);
}
#endif

/*  C O N B G T  --  Background Test  */

/*
  Call with flag == 1 to prevent signal test, which can not be expected
  to work during file transfer, when SIGINT probably *is* set to SIG_IGN.  

  Call with flag == 0 to use the signal test, but only if the process-group
  test fails, as it does on some UNIX systems, where getpgrp() is buggy,
  requires an argument when the man page says it doesn't, or vice versa.
 
  If flag == 0 and the process-group test fails, then we determine background
  status simply (but not necessarily reliably) from isatty().

  conbgt() sets the global backgrd = 1 if we appear to be in the background,
  and to 0 if we seem to be in the foreground.  conbgt() is highly prone to
  misbehavior.

  On VOS, we check the process info block.  If interactive mode bit is
  not set, then we are in the background.  (Aren't OLTP OS's fun?) If we
  are in the forground, we still don't want to use a prompt if input has
  been redirected to a command macro or something, so we check vostty as
  well.  The "Signal test" doesn't work anywhere close to the same on
  VOS, so we don't use it.

*/
VOID
conbgt(flag) int flag; {
    int x = -1,				/* process group or SIGINT test */
        y = 0;				/* vostty() test */
    PROCESS_INFO pinfo;
    long pid;
    short status;

/*
  Check for background operation, even if not running on real tty, so that
  background flag can be set correctly.  If background status is detected,
  then Kermit will not issue its interactive prompt or most messages.
  If your prompt goes away, you can blame (and fix?) this function.
*/

/* Use process-type test if possible. */

    chkalrm();
    s$get_process_id (&pid);
    pinfo.version = PROCESS_INFO_VERSION;
    s$get_process_info (&pid, &pinfo, &status);
    if ((pinfo.info.flags & INTERACTIVE) == 0)
	backgrd = 1;
    else
    {
	if (vostty(TERMINAL_PORT_ID)) /* do we have a tty terminal port ? */
	    backgrd = 0;
	else
	    backgrd = 1;
    }

    debug(F101,"conbgt backgrd","",backgrd);
}

/*  C O N I N T  --  Console Interrupt setter  */

/*
  First arg is pointer to function to handle SIGTERM & SIGINT (like Ctrl-C).
  Second arg is pointer to function to handle SIGTSTP (suspend).
*/

VOID					/* Set terminal interrupt traps. */
#ifdef CK_ANSIC
conint(SIGTYP (*f)(int), SIGTYP (*s)(int))
#else
conint(f,s) SIGTYP (*f)(), (*s)();
#endif /* CK_ANSIC */
/* conint */ {

    chkalrm();
    conbgt(0);				/* Do background test. */

/* Set the desired handlers for hangup and software termination. */

    signal(SIGTERM,f);                  /* Software termination */

/* Now handle keyboard stop, quit, and interrupt signals. */
/* Check if invoked in background -- if so signals set to be ignored. */
/* However, if running under a job control shell, don't ignore them. */
/* We won't be getting any, as we aren't in the terminal's process group. */

    debug(F101,"conint backgrd","",backgrd);

    if (backgrd) {		/* In background, ignore signals */
	debug(F101,"conint background ignoring signals","",0);

        signal(SIGINT,SIG_IGN);         /* Keyboard interrupt */
    } else {				/* Else in foreground or suspended */
	debug(F101,"conint foreground catching signals, jc","",0);
        signal(SIGINT,f);               /* Catch terminal interrupt */
        if (conesc) conesc = 0;         /* Clear out pending escapes */
    }
    chkalrm();
}

/*  C O N N O I  --  Reset console terminal interrupts */

SIGTYP					/* Dummy function to ignore signals */
#ifdef CK_ANSIC
sig_ign(int foo)
#else
sig_ign(foo) int foo;
#endif /* CK_ANSIC */
/* sig_IGN */ {				/* Just like the real one, but has  */
}					/* different address. */

VOID
connoi() {                              /* Console-no-interrupts */

    debug(F100,"connoi","",0);

    /* Note the locally defined replacement for SIG_IGN that is used here */
    /* for the SIGINT setting.  This is done so that the Sys V background */
    /* test -- (signal(SIGINT,SIG_IGN) == SIG_IGN) -- can work.  If we use */
    /* the real SIG_IGN here, then conint will always decide that this */ 
    /* program is running in the background! */

    signal(SIGINT,sig_ign);		/* <--- note! */

    signal(SIGHUP,SIG_DFL);
    signal(SIGTERM,SIG_IGN);
    chkalrm();
}

initrawq(tty) int tty; {
    chkalrm();
    return(0);
}

static VOID
catch(foo) int foo; {
    longjmp(jjbuf, -1);
}

/*  G E N B R K  --  Simulate a modem break.  */

VOID
genbrk(fn,msec) int fn, msec; {

    short port = fn;
    short opcode = SEND_BREAK_OPCODE;
    short status;
    short dummy = 0;

    s$control (&port, &opcode, &dummy, &status);
    chkalrm();

    debug(F101,"genbrk status","",status);
    return;
}


/*  T T C H K  --  Tell how many characters are waiting in tty input buffer  */

/*  Some callers of this want to know whether there is something to read
 *  either in the system buffers or in our private buffers (and mostly don't
 *  care how many characters, just whether it's more than zero or not), while
 *  some others would be better off with the number of characters in our
 *  private buffers only.
 *
 *  Some systems can say how many characters there are in the system buffers.
 *  Others can not. For those that can't, the number in the private buffers
 *  will have to do (or should we put the tty into O_NDELAY-mode and try to
 *  read one character?). If the system can tell whether there is zero or
 *  more than zero characters, we can return 1 in the latter case even if the
 *  private buffer is empty. (That is good for sliding windows.)
 */
int
ttchk() {
    int n = 0;

#ifdef NETCONN
    if (netconn)
	return (nettchk());
#endif /* NETCONN */

    if (ttxbn > 0)
	n += ttxbn;

    chkalrm();

    debug(F101,"ttchk returns","",n);
    return(n);
}

/*  T T X I N  --  Get n characters from tty input buffer  */

/*  Returns number of characters actually gotten, or -1 on failure  */

/*  Intended for use only when it is known that n characters are actually */
/*  Available in the input buffer.  */

int
ttxin(n,buf) int n; CHAR *buf; {
    register int x, c;
  
    debug(F101,"ttxin n","",n);
    if (n < 1) return(0);

#ifdef COMMENT
#ifdef STRATUSX25
    if (netconn && (ttnet == NET_VX25))	/* X.25 connection */
      return(x25xin(n,buf));
#endif /* STRATUSX25 */
#endif /* COMMENT */

    ttpmsk = (ttprty) ? 0177 : 0377;	/* Parity stripping mask. */

    if (ttxbn < n) {
        if (!netconn) {			/* All network connections do this. */
	    debug(F101,"ttxin called w/ too few chars","",ttxbn);
	}
	n = ttxbn;
    }

    memmove (buf,&ttxbuf[ttxbp],n);
    ttxbp += n;
    ttxbn -= n;
    buf[n] = '\0';

    for (c = 0; c < n; c++)
	buf[c] &= ttpmsk;

    return(n);
}

/*  T T O L  --  Write string s, length n, to communication device.  */
/*
  Returns:
   >= 0 on success, number of characters actually written.
   -1 on failure.
*/
#define TTOLMAXT	10
int
ttol(s,n) int n; CHAR *s; {
    short ttyport = (short) ttyfd;
    short status;
    short sys_len;
    int   x;
    int   tries;
    int   len;

    if (ttyfd < 0) return(-1);          /* Not open? */
    debug(F101,"ttol n","",n);
    debug(F110,"ttol s",s,0);

    tries = TTOLMAXT;			/* Allow up to this many tries */
    len = n;				/* Remember original length */
    while (n > 0 && tries-- > 0) {	/* Be persistent */
	debug(F101,"ttol try","",TTOLMAXT - tries);

        if (netconn) {
	    x = nettol (s, n);
        }
        else {
	    sys_len = n;
	    s$write_raw (&ttyport, &sys_len, s, &status);
	    if (status == 0 || status == e$caller_must_wait) {
		x = sys_len;
		status = 0;
	    }
	    else
		x = -1;
	}

	chkalrm();			/* check for alarms */

	if (x == n) {			/* Worked? */
	    debug(F101,"ttol ok","",x);	/* OK */
	    return(len);		/* Done */
	} else if (x < 0) {		/* No, got error? */
	    debug(F101,"ttol failed","",errno);

	    if (netconn && ttnet == NET_TCPB) {
		ttclos(0);		/* Close the connection. */
	    }
	    return(-1);
	} else {			/* No error, so partial success */
	    debug(F101,"ttol partial","",x);
	    s += x;			/* Point to part not written yet */
	    n -= x;			/* Adjust length */
	    if (x > 0) msleep(100);	/* Wait 100 msec */
	}				/* Go back and try again */
    }
    return(n < 1 ? len : -1);		/* Return the results */
}

/*  T T O C  --  Output a character to the communication line  */

/*
 This function should only be used for interactive, character-mode operations,
 like terminal connection, script execution, dialer i/o, where the overhead
 of the signals and alarms does not create a bottleneck.
*/
int
#ifdef CK_ANSIC
ttoc(char c)
#else
ttoc(c) char c;
#endif /* CK_ANSIC */
/* ttoc */ {
    int xx;
    short ttyport = ttyfd;
    short len = 1;
    short status;

    c &= 0xff;
    /* debug(F101,"ttoc","",(CHAR) c); */
    chkalrm();
    if (ttyfd < 0) return(-1);          /* Check for not open. */

    if (netconn)
	return (nettoc(c));

    s$write_raw (&ttyport, &len, &c, &status);
    if (status != 0)
	return -1;

    return(0);				/* Return good code. */
}

/*  T T I N L  --  Read a record (up to break character) from comm line.  */
/*
  Reads up to "max" characters from the communication line, terminating on:

    (a) the packet length field if the "turn" argument is zero, or
    (b) on the packet-end character (eol) if the "turn" argument is nonzero
    (c) two Ctrl-C's in a row

  and returns the number of characters read upon success, or if "max" was
  exceeded or the timeout interval expired before (a) or (b), returns -1.

  The characters that were input are copied into "dest" with their parity bits
  stripped if parity was selected.  Returns the number of characters read.
  Characters after the eol are available upon the next call to this function.

  The idea is to minimize the number of system calls per packet, and also to
  minimize timeouts.  This function is the inner loop of the program and must
  be as efficient as possible.  The current strategy is to use myread().

  WARNING: this function calls parchk(), which is defined in another module.
  Normally, ckutio.c does not depend on code from any other module, but there
  is an exception in this case because all the other ck?tio.c modules also
  need to call parchk(), so it's better to have it defined in a common place.

  Since this function has grown to have its fingers so deeply into the 
  protocol, it is slated for removal: rpack() will take care of everything.
*/
#ifdef CTRLC
#undef CTRLC
#endif /* CTRLC */
#define CTRLC '\03'
/*
  We have four different declarations here because:
  (a) to allow Kermit to be built without the automatic parity sensing feature
  (b) one of each type for ANSI C, one for non-ANSI.
*/
int
#ifdef PARSENSE
#ifdef CK_ANSIC
ttinl(CHAR *dest, int max,int timo, CHAR eol, CHAR start, int turn)
#else
ttinl(dest,max,timo,eol,start,turn) int max,timo,turn; CHAR *dest, eol, start;
#endif /* CK_ANSIC */
#else /* not PARSENSE */
#ifdef CK_ANSIC
ttinl(CHAR *dest, int max,int timo, CHAR eol)
#else
ttinl(dest,max,timo,eol) int max,timo; CHAR *dest, eol;
#endif /* CK_ANSIC */
#endif /* PARSENSE */
/* ttinl */ {

#ifdef PARSENSE
    int pktlen = -1;
    int lplen = 0;
    int havelen = 0;
    int flag = 0;
#endif /* PARSENSE */
    register int i, m, n;		/* local variables */
    time_t tout, now;
    int ccn = 0;

    chkalrm();
    if (ttyfd < 0) return(-1);          /* Not open. */

    debug(F101,"ttinl max","",max);
    debug(F101,"ttinl timo","",timo);

    *dest = '\0';                       /* Clear destination buffer */
    if (timo < 0) timo = 0;		/* Safety */

    now = time(NULL);
    tout = now + timo;

#ifdef PARSENSE
    flag = 0;			/* Start of packet flag */
#endif /* PARSENSE */

    ttpmsk = m = (ttprty) ? 0177 : 0377; /* Set parity stripping mask. */

/* Now read into destination, stripping parity and looking for the */
/* the packet terminator, and also for transfer cancellation chars */

    i = 0;				/* Destination index */
#ifdef COMMENT
    debug(F101,"ttinl eol","",eol);
#endif

    while (i < max-1) {
        now = time(NULL);
	if(timo && (tout < now)) {   /* strictly less than */
	    debug(F101,"ttinl timed out","",tout);
	    return (-1); /* timed out */
	}

	n = ttinc(timo ? (tout-now) : 0);

        if (n < 0) {
	    debug(F101,"ttinl ttinc error, char","",n);
	    return (n); /* error */
        }
#ifdef COMMENT
	debug(F101,"ttinl char","", (n & ttpmsk));
#endif

#ifdef PARSENSE
/*
Figure out what the length is supposed to be in case the packet
has no terminator (as with Honeywell GCOS-8 Kermit).
*/
#ifndef xunchar
#define xunchar(ch) (((ch) - 32 ) & 0xFF )	/* Character to number */
#endif /* xunchar */
	if ((flag == 0) && ((n & 0x7f) == start)) flag = 1;
	if (flag) dest[i++] = n & ttpmsk;
/*
If we have not been instructed to wait for a turnaround character, we
can go by the packet length field.  If turn != 0, we must wait for the
end of line (eol) character before returning.
*/
	if (i == 2) {
	    pktlen = xunchar(dest[1]);
	    havelen = (pktlen > 1);
	    debug(F101,"ttinl length","",pktlen);
	} else if (i == 5 && pktlen == 0) {
	    lplen = xunchar(dest[4]);
	} else if (i == 6 && pktlen == 0) {
	    pktlen = lplen * 95 + xunchar(dest[5]) + 5;
	    havelen = 1;
	    debug(F101,"ttinl length","",pktlen);
	}
#else
	dest[i++] = n & ttpmsk;
#endif /* PARSENSE */
/*
  Use parity mask, rather than always stripping parity, to check for
  cancellation.  Otherwise, runs like \x03\x83\x03 in packet could cancel
  the transfer when parity is NONE.
*/
	/* Check cancellation */
	if (!xlocal && xfrcan && ((n & ttpmsk) == xfrchr)) {
	    if (++ccn >= xfrnum) {	/* If xfrnum in a row, bail out. */
		if (timo) {		/* Clear timer. */
		    debug(F100,"ttinl got ^C^C...","",0);
		    ttimoff();
		}
		fprintf(stderr,"^C...\r\n"); /* Echo Ctrl-C */
		return(-2);
	    }
	} else ccn = 0;		/* Not ^C, so reset ^C counter, */

#ifdef PARSENSE
	if (flag == 0) {
	    debug(F101,"ttinl skipping","",n);
	    continue;
	}
#endif /* PARSENSE */

/* Check for end of packet */

	if (((n & 0x7f) == eol)
#ifdef PARSENSE
	    || (!turn && havelen && (i > pktlen+1))
#endif /* PARSENSE */
	    ) {
#ifndef PARSENSE
	    debug(F101,"ttinl got eol","",eol);
	    dest[i] = '\0';	/* Yes, terminate the string, */
	    debug(F101,"ttinl i","",i);
#else
	    if ((n & 0x7f) != eol) {
		debug(F101,"ttinl EOP length","",pktlen);
		debug(F101,"ttinl i","",i);
	    } else debug(F101,"ttinl got eol","",eol);
	    dest[i] = '\0';		/* Terminate the string, */
	    /* Parity checked yet? */
	    if (ttpflg++ == 0 && ttprty == 0) {
		if ((ttprty = parchk(dest,start,i)) > 0) { /* No, check. */
		    int j;
		    debug(F101,"ttinl senses parity","",ttprty);
		    debug(F110,"ttinl packet before",dest,0);
		    ttpmsk = 0x7f;
		    for (j = 0; j < i; j++)
		      dest[j] &= 0x7f;	/* Strip parity from packet */
		    debug(F110,"ttinl packet after ",dest,0);
		} else ttprty = 0;	/* restore if parchk error */
	    }
#endif /* PARSENSE */
	    if (timo) {			/* Turn off timer. */
		ttimoff();
	    }
	    debug(F111,"ttinl got", dest,i);
	    return(i);
	}
    }				/* end of while() */
    debug(F101,"ttinl exited while loop, i","",i);
    ttimoff();
    chkalrm();
    return(-1);
}

/*  T T I N C --  Read a character from the communication line  */
/*
 On success, returns the character that was read, >= 0.
 On failure, returns -1 or other negative error code.
*/
int
ttinc(timo) int timo; {
    int ch = 0;

    chkalrm();
    if (ttyfd < 0) return(-1);          /* Not open. */

    /* this can be used for ALL character input, including network io, */
    /* because ttxgetc will call txbufr, which is network aware.       */

    ch = ttxgetc(timo);

    return((ch >= 0) ? ch & ttpmsk : ch); /* return negative codes intact */
}

/*  S N D B R K  --  Send a BREAK signal of the given duration  */

static int
#ifdef CK_ANSIC
sndbrk(int msec) {			/* Argument is milliseconds */
#else
sndbrk(msec) int msec; {
#endif /* CK_ANSIC */
#ifndef POSIX
    int x, n;
#endif /* POSIX */

    debug(F101,"ttsndb ttyfd","",ttyfd);
    if (ttyfd < 0) return(-1);          /* Not open. */

    if (netconn) 			/* Send network BREAK */
      return(netbreak());

    if (msec < 1 || msec > 5000) return(-1); /* Bad argument */

    genbrk(ttyfd,250);			/* Simulate a BREAK */
    return(0);
}

/*  T T S N D B  --  Send a BREAK signal  */

int
ttsndb() {
    return(sndbrk(275));
}

/*  T T S N D L B  --  Send a Long BREAK signal  */

int
ttsndlb() {
    return(sndbrk(1500));
}

/*  M S L E E P  --  Millisecond version of sleep().  */

/*
  Call with number of milliseconds (thousandths of seconds) to sleep.
  Intended only for small intervals.  For big ones, just use sleep().
  Highly system-dependent.
  Returns 0 always, even if it didn't work.
*/

int
msleep(m) int m; {
    short status;

    /* argument to s$sleep is really in 1024/s but, this is close enough */
    m += m / 40;	/* this gives 1025 per second */
    s$sleep (&m, &status);
    chkalrm();

    return(0);
}

/*  R T I M E R --  Reset elapsed time counter  */

VOID
rtimer() {
    tcount = time( (time_t *) 0 );
    debug(F101,"rtimer","",(long) tcount);
    chkalrm();
}


/*  G T I M E R --  Get current value of elapsed time counter in seconds  */

int
gtimer() {
    int x;
    x = (int) (time( (time_t *) 0 ) - tcount);
    debug(F101,"gtimer","",x);
    chkalrm();
    return( (x < 0) ? 0 : x );
}

#ifdef GFTIMER
/* I know this is different than the structure in the manual.  But
   since we know what it's really got in it, we can handle it this
   way.  We've just moved the radix point a little.  
*/
struct jiffies {
    unsigned long high;
    unsigned long low;
};

static double dzero;

VOID
rftimer() {
    struct jiffies tzero;
    s$jiffy_date_time(&tzero);

    dzero = (tzero.high * (double)65536.0) + (tzero.low / (double)65536.0);
}

CKFLOAT
gftimer() {
    struct jiffies tnow;
    double dnow;
    double s;
#ifdef DEBUG
    char fpbuf[64];
#endif /* DEBUG */
    s$jiffy_date_time(&tnow);

    dnow = (tnow.high * (double)65536.0) + (tnow.low / (double)65536.0);
    s = dnow - dzero;

    if (s == 0.0) s = 0.000001;
#ifdef DEBUG
    if (deblog) {
	sprintf(fpbuf,"%f",s);
	debug(F110,"gftimer",fpbuf,0);
    }
#endif /* DEBUG */
    return((CKFLOAT)s);
}
#endif /* GFTIMER */


/*  Z T I M E  --  Return date/time string  */

VOID
ztime(s) char **s; {

    struct tm *tp;
    time_t xclock;
    time(&xclock);
    tp = localtime(&xclock);
    *s = asctime(tp);
    debug(F111,"ztime",*s,(long) xclock);
    chkalrm();
}

/*  C O N G M  --  Get console terminal modes.  */

/*
  Saves initial console mode, and establishes variables for switching
  between current (presumably normal) mode and other modes.
  Should be called when program starts, but only after establishing
  whether program is in the foreground or background.
  Returns 1 if it got the modes OK, 0 if it did nothing, -1 on error.
*/
int
congm() {
    TERMINAL_INFO	tinfo;
    short		opcode;
    short		conport = TERMINAL_PORT_ID;
    short		status;

    chkalrm();
    if (backgrd || !vostty(conport)) {	/* If in background. */
	cgmf = -1;			/* Don't bother, modes are garbage. */
	return(-1);
    }

    if (cgmf > 0) return(0);		/* Already did this. */
    debug(F100,"congm getting modes","",0); /* Need to do it. */

    opcode = GET_INFO_OPCODE;
    tinfo.version = TERMINAL_INFO_VERSION_2;
    s$control (&conport, &opcode, &tinfo, &status);
    if (status) {
	debug(F111,"congm s$control GET_INFO status",ttnmful,status);
	return -1;
    }

    ccold = tinfo.modes;
    cccbrk = tinfo.modes;
    if (0 == plines)
	plines = tinfo.pause_lines;

    cccbrk &= ~(OS_FUNCTION_KEY_INPUT | OS_BREAK_TABLE_RECORD
	| OS_GENERIC_INPUT | OS_FORMS_INPUT);
    cccbrk |= OS_RAW_INPUT | OS_BULK_RAW_INPUT;
    ccraw = cccbrk;

    chkalrm();
    cgmf = 1;				/* Flag that we got them. */
    return(1);
}

/*  C O N C B --  Put console in cbreak mode.  */

/*  Returns 0 if ok, -1 if not  */

int
#ifdef CK_ANSIC
concb(char esc)
#else
concb(esc) char esc;
#endif /* CK_ANSIC */
/* concb */ {
    short opcode = SET_MODES_OPCODE;
    short port = TERMINAL_PORT_ID;
    short status;

    chkalrm();
    if (cgmf < 1) return(0);		/* Console modes not available yet */

    debug(F101,"concb backgrd","",backgrd);
    if (backgrd) return(0);		/* Do nothing if in background. */

    if (!vostty(port)) return(0);       /* Only for real ttys */
    if (no_wait) cancio();		/* back to wait mode */

    s$control (&port, &opcode, &ccraw, &status);
    if (status) {
	debug(F101,"concb s$control[SET_MODES] status","",status);
	return (-1);
    }

    escchr = esc;                       /* Make this available to other fns */
    ckxech = 1;                         /* Program can echo characters */
    conraw = 1;

    chkalrm();
    return(0);
}

/*  C O N B I N  --  Put console in binary mode  */

/*  Returns 0 if ok, -1 if not  */

int
#ifdef CK_ANSIC
conbin(char esc)
#else
conbin(esc) char esc;
#endif /* CK_ANSIC */
/* conbin */  {

    short port = TERMINAL_PORT_ID;
    short opcode = SET_MODES_OPCODE;
    short status;

    chkalrm();
    debug(F101,"conbin esc","",esc);
    if (!vostty(port))			/* only for real ttys */
	return(0);
    congm();				/* Get modes if necessary. */

    s$control (&port, &opcode, &ccraw, &status);
    if (status)
	debug(F101,"conbin s$control SET_MODES status","",status);

    escchr = esc;                       /* Make this available to other fns */
    ckxech = 1;                         /* Program can echo characters */
    conraw = 2;
    chkalrm();
    return (0);
}

/*  C O N R E S  --  Restore the console terminal  */

int
conres() {
    short conport = TERMINAL_PORT_ID;
    short opcode = SET_MODES_OPCODE;
    short lines; 
    short status;

    debug(F101,"conres cgmf","",cgmf);
    if (no_wait) /* turn it off */
	cancio();
    chkalrm();
    if (cgmf < 1) return(0);		/* Do nothing if modes unchanged */
    if (!vostty(conport)) return(0);	/* only for real ttys */

    s$control (&conport, &opcode, &ccold, &status);
    if (status)
	debug(F101,"conres modes status","",status);    

    ckxech = 0;                         /* System should echo chars */
    conraw = 0;				/* not in raw mode any more */
    chkalrm();
    return (0);
}

/*  C O N O C  --  Output a character to the console terminal  */

int
#ifdef CK_ANSIC
conoc(char c)
#else
conoc(c) char c;
#endif /* CK_ANSIC */
/* conoc */ {
    int rv;

    if (c == '\n' && conraw == 1) /* don't translate in binary or non-raw */
	if (0 > (rv = conout("\r",1)))
	    return rv;

    rv = conout(&c,1);
    return rv;
}

/*  C O N X O  --  Write x characters to the console terminal  */

int
conxo(x,s) int x; char *s; {
    /* this doesn't handle \n translations */
    return(conout(s,x));
}

/*  C O N O L  --  Write a line to the console terminal  */

int
conol(s) char *s; {
    int len;
    len = (int)strlen(s);
    return(vosprtf("%s",s));
}

/*  C O N O L A  --  Write an array of lines to the console terminal */

int
conola(s) char *s[]; {
    int i;
    for (i=0 ; *s[i] ; i++) if (conol(s[i]) < 0) return(-1);;
    return(0);
}

/*  C O N O L L  --  Output a string followed by CRLF  */

int
conoll(s) char *s; {
    conol(s);
    return(conoc('\n'));
}

/*  C O N O U T  --  Write to console device  */

static int
conout(s,n) char *s; int n; {
    short conport = TERMINAL_PORT_ID;
    short status;
    short slen = n;

    chkalrm();
    if (backgrd)
	return -1;

    if (conraw)
	s$write_raw (&conport, &slen, s, &status);
    else
	s$seq_write_partial (&conport, &slen, s, &status);

    chkalrm();
    if (status) {
        debug(F101,"conout s$write status","",status);
	return -1;
    }

    return slen;
}

/*  C O N C H K  --  Return how many characters available at console  */

int
conchk() {
    short status;
    short status2;
    short buflen;
    short reclen;
    long  ticks;
    short conport;

    if (backgrd || !vostty(TERMINAL_PORT_ID))
	return(0);

    chkalrm();

    if (cincnt)
	return cincnt;	/* just pass back the buffered number */

    conport = TERMINAL_PORT_ID;
    ticks = 25; /* this isn't called very often */
    s$set_io_time_limit (&conport, &ticks, &status);
    if (status) return (-1);

    buflen = sizeof cinbuff;
    s$read_raw (&conport, &buflen, &reclen, cinbuff, &status);
    if ((status == e$timeout) || (status == e$short_record))
	status = 0;

    ticks = -1; /* turn timer off */
    s$set_io_time_limit (&conport, &ticks, &status2);

    if (status || status2) {
	debug(F101,"conchk returning status","",status ? status : status2);
	return (-1);
    }
    cincnt = reclen;	/* how many did we get? */
    cinbp = 0;		/* point to first character */

    return (cincnt);
}

/*  C O N I N C  --  Get a character from the console  */
/*
  Call with timo > 0 to do a timed read, timo == 0 to do an untimed read.
  Upon success, returns the character.  Upon failure, returns -1.
  A timed read that does not complete within the timeout period returns -1.
  A no_wait timeout with nothing to read returns -2.
*/
int
coninc(timo) int timo; {
    short conport = TERMINAL_PORT_ID;
    short status;
    short reclen;
    short buflen = sizeof cinbuff;
    long  ticks;
    short sstat;

    if (cincnt) {
	cincnt--;
	return (cinbuff[cinbp++]);
    }

    chkalrm();

    if (timo > 0 ) {                   /* set time limit */
	ticks = timo * 1024;
	s$set_io_time_limit (&conport, &ticks, &status);
	if (status) return (-1);
    }

    while (1) {			/* Keep trying till we get one. */
	if (conraw)
	    s$read_raw (&conport, &buflen, &reclen, cinbuff, &status);
	else
	    s$seq_read (&conport, &buflen, &reclen, cinbuff, &status);

	switch (status) {
	    case e$caller_must_wait:
	    case e$short_record:
		if (reclen)
		    status = 0;

	    /* NO BREAK HERE */

	    case 0:
		cincnt = reclen;	/* how many did we get? */
		cinbp = 0;		/* reset buffer pointer */
		break;

	    default:
		break;
	}

	if (status)
	    break;

	if (reclen == 0) continue;	/* Shouldn't happen. */
	if (reclen > 0) {		/* If read was successful, */
	    if (timo > 0 ) {
		ticks = -1; /* turn timer off */
		s$set_io_time_limit (&conport, &ticks, &status);
	        chkalrm();
		if (status) return (-1);
	    }

	    chkalrm();
	    if (cincnt) {
		cincnt--;
		return ((cinbuff[cinbp++]) & 0377); /* return the character. */
	    }
	    return(-1); /* shouldn't get here */
	}
    }

    /* Come here if read() returned an error. */
    sstat = status;
    if (timo > 0 ) {
	ticks = -1; /* turn timer off */
	s$set_io_time_limit (&conport, &ticks, &status);
        chkalrm();
	if (status) return (-1);
    }

    if (e$caller_must_wait == status)
	return (-2);

    debug(F101, "coninc(0) s$read status","",sstat); /* Log the error. */

    return(-1);
}

/*  C O N G K S  --  Console Get Keyboard Scancode  */

#ifndef congks
/*
  This function needs to be filled in with the various system-dependent
  system calls used by SUNOS, NeXT OS, Xenix, Aviion, etc, to read a full
  keyboard scan code.  For now, it's a dummy.
*/
int
congks(timo) int timo; {
    return(coninc(timo));
}
#endif /* congks */

/*
  contti()  is  used  by the conect() routine to handle the problem of getting
  input from both the console and the tty device at  the  same  time,  without
  hanging  on  one  waiting  on  the  other.  In  VOS, we set the devices into
  no_wait mode, read the event counts, and  try  to  read.  If  we  don't  get
  anything (actually, if we get e$caller_must_wait), then we call s$wait_event
  to do the waiting.
*/
int
#ifdef CK_ANSIC
contti(int *c, int *src)
#else
contti(c, src) int *c; int *src
#endif
/* contti () */ {
    short ttyport;
    short conport;
    short status;
    short buflen;
    short reclen;
    short which;
    long timeout;
    short count;
    short eventstat;
    char  ch;

    *src = -1;
    *c = 0;
    ttyport = ttyfd;
    conport = TERMINAL_PORT_ID;

    chkalrm();
    if (0 == no_wait) {
        no_wait = 1;
#ifdef NETCONN
        if (netconn) {
	    status = netwait (no_wait, ttyfd, &eventids[1], &eventcnts[1]);
	    if (status) {
		debug(F101,"contti nowait net tty status","",status);
		return (-1);
	    }
	}
	else { /* some sort of "terminal" */
#endif
	    s$set_no_wait_mode (&ttyport, &eventids[1], &status);
	    if (status) {
		debug(F101,"contti nowait tty status","",status);
		return (-1);
	    }
#ifdef NETCONN
	}
#endif
	s$set_no_wait_mode (&conport, &eventids[0], &status);
	if (status) {
	    debug(F101,"contti nowait con status","",status);
	    return (-1);
	}
    }

    if (ttxbn) {	/* return buffered tty character */
	*src = 1;
	*c = ttxgetc(0);
	return 0;
    }

    if (cincnt) { 	/* return buffered console character */
	*src = 0;
	*c = coninc(0);
	return 0;
    }


#ifdef NETCONN
    if (netconn == 0) { /* Don't use read_event with socket events... */
#endif
	s$read_event (&eventids[1], &eventcnts[1], &eventstat, &status);
	if (status) {
	    debug(F101,"contti read event tty status","",status);
	    return (-1);
	}
#ifdef NETCONN
    }
#endif

    do {
        *c = ttxgetc(0);		/* ttxgetc knows about net* io */
	if (*c >= 0) {			/* got one */
	    *src = 1;
	    return 0;
	}

	if (*c == -2) {			/* -1 is e$cmw, -2 hangup */
	    debug(F101,"contti read tty ch","",*c);
	    return (-2);
	}

	s$read_event (&eventids[0], &eventcnts[0], &eventstat, &status);
	if (status) {
	    debug(F101,"contti read event console status","",status);
	    return (-1);
	}

	do {
	    *c = coninc(0);

	    if (*c >= 0) { /* read a character successfully */
		*src = 0;
		return 0;
	    }

	    if (*c == -1) { /* error */
		debug(F101,"contti coninc returns","",*c);
		return (-1);
	    }

	    timeout = -1;
	    which = 1; /* must be initialized */
	    count = 2;
	    s$wait_event (&count, eventids, eventcnts, &timeout,
		&which, &status);
	    if (status) {
		debug(F101,"contti wait event status","",status);
		return (-1);
            chkalrm();
	    }
	}
	while (which == 1);	/* console */
    }
    while (which == 2);		/* tty */

    debug(F101,"contti cannot get here, which","",which);
    return (-1);
}

/* C O N R E S N E	-- restore console to normal attributes, just like
			conres, but with system echo turned off.  Used for 
			escape processing during CONNECT. Actually, it wants
			concb without echo, but the description says conres.
*/
VOID conresne(void) {
    short conport = TERMINAL_PORT_ID;
    short status;
    short opcode = DISPLAY_OFF_OPCODE;
    short dummy = 0;

    concb(escchr);
    s$control (&conport, &opcode, &dummy, &status);
    debug(F101,"conresne s$control DISPLAY_OFF status","",status);
    ckxech = 1;                         /* Program can echo characters */
    chkalrm();
}

/* C A N C I O	-- Finished with CONNECT, cancel no_wait and pending I/O */
VOID cancio (void) {
    short conport;
    short ttyport;
    short status;
    short opcode;
    short dummy = 0;

    chkalrm();

    if (no_wait) {
	ttyport = ttyfd;
	conport = TERMINAL_PORT_ID;
	if (no_wait) {
	    no_wait = 0;
#ifdef NETCONN
	    if (netconn)
		status = netwait (no_wait, ttyfd, NULL, NULL);
	    else
#endif /* NETCONN */
		s$set_wait_mode (&ttyport, &status);

	    if (status)
		debug(F101,"cancio s$set_wait_mode tty status","",status);

	    s$set_wait_mode (&conport, &status);
	    if (status)
		debug(F101,"cancio s$set_wait_mode con status","",status);
	}
    }

#ifdef COMMENT
    /* we only do this for the console */
    opcode = DISCARD_INPUT_OPCODE;
    s$control (&conport, &opcode, &dummy, &status);
    if (status)
	debug(F101,"cancio s$control DISCARD_INPUT status","",status);

    opcode = DISCARD_OUTPUT_OPCODE;
    s$control (&conport, &opcode, &dummy, &status);
    if (status)
	debug(F101,"cancio s$control DISCARD_OUTPUT status","",status);
#endif
}

/*  T T S C A R R  --  Set ttcarr variable, controlling carrier handling.
 *
 *  0 = Off: Always ignore carrier. E.g. you can connect without carrier.
 *  1 = On: Heed carrier, except during dialing. Carrier loss gives disconnect.
 *  2 = Auto: For "modem direct": The same as "Off".
 *            For real modem types: Heed carrier during connect, but ignore
 *                it anytime else.  Compatible with pre-5A C-Kermit versions.
 *
 * Of course, nothing of this applies to remote mode (xlocal = 0).
 *
 */
int
ttscarr(carrier) int carrier; {
    ttcarr = carrier;
    debug(F101, "ttscarr","",ttcarr);
    chkalrm();
    return(ttcarr);
}

/* C A R R C T L  --  Set tty modes for carrier treatment.
 *
 * Sets the appropriate bits in a termio or sgttyb struct for carrier control
 * (actually, there are no bits in sgttyb for that), or performs any other
 * operations needed to control this on the current system.  The function does
 * not do the actual TCSETA or stty, since often we want to set other bits too
 * first.  Don't call this function when xlocal is 0, or the tty is not opened.
 *
 * We don't know how to do anything like carrier control on non-ATTSV systems,
 * except, apparently, ultrix.  See above.  It is also known that this doesn't
 * have much effect on a Xenix system.  For Xenix, one should switch back and
 * forth between the upper and lower case device files.  Maybe later. 
 * Presently, Xenix will stick to the mode it was opened with.
 *
 * carrier: 0 = ignore carrier, 1 = require carrier.
 * The current state is saved in curcarr, and checked to save labour.
 */

int
#ifdef CK_ANSIC
carrctl(long *ttpar, int carrier) {
#else
carrctl(ttpar, carrier) long *ttpar; int carrier; {
#endif
    int temp = 0;
    int modem_status;
    debug(F101, "carrctl","",carrier);
    chkalrm();
    if (carrier == curcarr)
      return(0);
    curcarr = carrier;

    return(0);
}


/*  T T G M D M  --  Get modem signals  */
/*
 Looks for RS-232 modem signals, and returns those that are on in as its
 return value, in a bit mask composed of the BM_xxx values defined in ckcdeb.h.
 Returns: 
 -3 Not implemented
 -2 if the communication device does not have modem control (e.g. telnet)
 -1 on error.
 >= 0 on success, with a bit mask containing the modem signals that are on.
*/

int
ttgmdm() {
    short status;
    DATA_SET_STATUS_INFO dssi;
    CV(66) data_set_name;
    int x, y, z;

    chkalrm();
#ifdef NETCONN
    if (netconn) return(-2);		/* Network, no modem signals. */
#endif

    strcpy (&data_set_name, ttnmful);
    dssi.version = 1;
    dssi.flags = 0;
    s$get_data_set_status (&data_set_name, &dssi, &status);
    debug(F101,"ttgmdm",ttnmful,(long)dssi.flags);
    if (status) {
	debug(F101,"ttgmdm s$get_data_set_status status","",status);
	return (status == e$device_not_local) ? (-2) : (-1);
    }

    y = 0;
    if (dssi.flags & DSSI_DATA_SET_READY)
	y |= BM_DSR;

    if (dssi.flags & DSSI_CARRIER_DETECT)
	y |= BM_DCD;

    if (dssi.flags & DSSI_CLEAR_TO_SEND)
	y |= BM_CTS;

    return y;
}

/*  P S U S P E N D  --  Put this process in the background.  */

/*
  Call with flag nonzero if suspending is allowed, zero if not allowed.
  Returns 0 on apparent success, -1 on failure (flag was zero, or
  kill() returned an error code.  VOS doesn't support this.
*/
int
psuspend(flag) int flag; {

    if (flag == 0) return(-1);
    chkalrm();

    return(-1);		/* Didn't work. Never works. */
}


/* Variables for user and group IDs. */

static UID_T realuid = (UID_T) -1, privuid = (UID_T) -1;
static GID_T realgid = (GID_T) -1, privgid = (GID_T) -1;


/* P R I V _ I N I  --  Initialize privileges package  */

/* Called as early as possible in a set-uid or set-gid program to store the
 * set-to uid and/or gid and step down to the users real uid and gid. The
 * stored id's can be temporarily restored (allowed in System V) during
 * operations that require the privilege.  Most of the time, the program
 * should execute in unpriviliged state, to not impose any security threat.
 *
 * Note: Don't forget that access() always uses the real id:s to determine
 * file access, even with privileges restored.
 *
 * Returns an error mask, with error values or:ed together:
 *   1 if setuid() fails,
 *   2 if setgid() fails, and
 *   4 if the program is set-user-id to "root", which can't be handled.
 *
 * Only the return value 0 indicates real success. In case of failure,
 * those privileges that could be reduced have been, at least, but the
 * program should be aborted none-the-less.
 *
 * Also note that these functions do not expect the uid or gid to change
 * without their knowing. It may work if it is only done temporarily, but
 * you're on your own.
 *
 * VOS does support suid programs (Called "owner access"), but not changing
 * these things around.  It also only applies to file access permissions.
 */
int
priv_ini() {
    int err = 0;

    return(err); /* always say it's okay */
}

/* P R I V _ O N  --  Turn on the setuid and/or setgid */

/* Go to the privileged uid (gid) that the program is set-user-id
 * (set-group-id) to, unless the program is running unprivileged.
 * If setuid() fails, return value will be 1. If getuid() fails it
 * will be 2.  Return immediately after first failure, and the function
 * tries to restore any partial work done.  Returns 0 on success.
 * Group id is changed first, since it is less serious than user id.
 */
int
priv_on() {
    return(0); /* always say it's okay */
}

/* P R I V _ O F F  --  Turn on the real uid and gid */

/* Return to the unprivileged uid (gid) after an temporary visit to
 * privileged status, unless the program is running without set-user-id
 * (set-group-id). Returns 1 for failure in setuid() and 2 for failure
 * in setgid() or:ed together. The functions tries to return both uid
 * and gid to unprivileged state, regardless of errors. Returns 0 on
 * success.
 */
int
priv_off() {
    int err = 0;

    return(err); /* always say it's okay */
}

/* Turn off privilege permanently.  No going back.  This is necessary before
 * a fork() on BSD43 machines that don't save the setUID or setGID, because
 * we swap the real and effective ids, and we don't want to let the forked
 * process swap them again and get the privilege back. It will work on other
 * machines too, such that you can rely on its effect always being the same,
 * for instance, even when you're in priv_on() state when this is called.
 * (Well, that part about "permanent" is on System V only true if you follow
 * this with a call to exec(), but that's what we want it for anyway.)
 * Added by Dean Long -- dlong@midgard.ucsc.edu
 */
int
priv_can() {
    return(priv_off()); 
}

/* P R I V _ O P N  --  For opening protected files or devices. */

int
priv_opn(name, modes) char *name; int modes; {

    /* Return Failure, since we can't do an open with these parameters */
    return(-1);
}

/*  P R I V _ C H K  --  Check privileges.  */

/*  Try to turn them off.  If turning them off did not succeed, cancel them */

int
priv_chk() {
    int x;
    x = priv_off();			/* Turn off privs. */
    return(0); /* always say it's okay */
}

UID_T
real_uid() {
    return(realuid);
}

VOID
ttimoff() {				/* Turn off any timer interrupts */
    short ttyport;
    short status;
    long  timo = -1; /* turn it off */

    if (ttyfd < 0)  return;

    ttyport = ttyfd;
    s$set_io_time_limit (&ttyport, &timo, &status);
    debug(F101,"ttimoff status","",status);
    chkalrm();
    return;
}


int vostty (int iport)
{
  short port = (short) iport;
  short status;
  get_port_info info;
  int i;

  info.version = GET_PORT_INFO_VERSION_1;
  s$get_port_info (&port, &info, &status);
  if (status)
  {
    errno = status;
    return -1;
  }
  i = s$is_file_type_a_terminal (&info.type);

  return i;
}

static char *connam (void)
{
  short port = TERMINAL_PORT_ID;
  short status;
  get_port_info info;
  static char tname[DEVNAMLEN+1];

  info.version = GET_PORT_INFO_VERSION_1;
  s$get_port_info (&port, &info, &status);
  if (status)
  {
    errno = status;
    return "";
  }

  strncpy (tname, &info.path_name, DEVNAMLEN);
  tname[DEVNAMLEN] = '\0';
  return tname;
}

int vosprtf (char *fmt, ... )
{
    char buff[1024]; /* hope this is big enough */
    va_list args;
    int rv;
    int diff;
    char *cp;
    char *lcp;

    va_start (args, fmt);
    if (backgrd)			/* in background, printf is okay */
	rv = vprintf (fmt, args);
    else				/* in foreground, it is not */
    {
	rv = vsprintf (buff, fmt, args);

	if (rv > sizeof buff) /* we're doomed... */
	    return (-1);

	/* loop, adding CR's to all the LF's */

        lcp = buff;
	cp = strchr (lcp, '\n');
	while (cp && *cp)
	{
	    diff = cp - lcp;
	    if (0 > conout (lcp, diff)) return (-1);
	    if (0 > conout ("\r", 1)) return (-1);
	    lcp = cp;
	    cp = strchr (lcp+1, '\n');
	}
	if (0 > conout (lcp, strlen (lcp))) return (-1);
    }

    va_end (args);

    return (rv);
}

/*
 * We fudge getenv here.  VOS doesn't have an "environment," hence, no
 * environment variables.  We find the ones that get asked for, and find a
 * way to get the requested value, and return it to the caller. The c-runtime
 * version always returns the empty string, so that's our default.
 */
char *
#ifdef CK_ANSIC
getenv(const char *str)
#else
getenv(str) char *str;
#endif 
/* getenv */ {
    static char envbuf[256];
    short status;
    short opcode;
    short port;
    TERMINAL_INFO tinfo;
    int len;
    CV(66) dummy;
    CV(66) modname;
    CV(256) envpath;
    char buff[300];
    short io_type;
    short envport = 0;
    short bufflen;
    short reclen;

    debug(F110,"GETENV called",str,0);
    strcpy (envbuf, "");  /* make sure it's empty first */

    chkalrm();

    s$get_home_dir (&envpath);

    strcat (&envpath, ">ckermit.env");
    io_type = INPUT_TYPE;
    s$seq_open (&envpath, &io_type, &envport, &status);
    strcpy (buff, &envpath);
    debug(F111,"       s$seq_open status",buff,status);

    len = strlen (str);

    while ('\0' == envbuf[0] && 0 == status) {
 	bufflen = sizeof buff;
	s$seq_read (&envport, &bufflen, &reclen, buff, &status);
        if (0 == status) {
	    buff[reclen] = '\0';
	    if (reclen > len && 0 == strncmp (str, buff, len)) {
		if ('=' == buff[len])
		    strcpy (envbuf, &buff[len+1]);
	    }
	}
    }

    if (envport > 0) {
	s$close (&envport, &status);
	s$detach_port (&envport, &status);
    }

    if ('\0' == envbuf[0]) { /* haven't found it */
	if(0 == strcmp(str, "TERM")) { /* console terminal type */
	    port = TERMINAL_PORT_ID;
	    opcode = GET_INFO_OPCODE;
	    tinfo.version = TERMINAL_INFO_VERSION_2; /* why is this 1? */
	    s$control (&port, &opcode, &tinfo, &status);
	    if (status)
		debug(F101,"getenv getting term info status","",status);
	    else
		strcpy(envbuf, &tinfo.terminal_type_name);
	}

	else if (0 == strcmp (str, "HOST")) { /* host name */
#ifdef TCPSOCKET
	    /* we have the BSD sockets library from OS TCP/IP, */
	    /* so get the real hostname, if it is set. */
	    len = sizeof (envbuf);
	    if (gethostname(envbuf,len) < 0) 
		strcpy (envbuf, "");

#endif /* TCPSOCKET */
	    /* if that did not work or if we don't have sockets, get the */
	    /* system name from the name of the current module.  Format  */
	    /* is "%systemname#modulename".  We return the whole thing.  */

	    if (0 == strlen (envbuf)) {
		/* if 1st arg is empty string, returns current module name */
		strcpy (&dummy, "");
		s$expand_module_name (&dummy, &modname, &status);
		if (status)
		    debug(F101,"getenv exp_mod_name status","",status);
		else
		    strcpy (envbuf, &modname);
	    }
	}
	/* add new ones here */
	else {
	    debug(F100,"GETENV returns NULL","",0);
	    return NULL;
	}
    }
    debug(F110,"GETENV returns",envbuf,0);
    return envbuf;
}

#undef signal
#undef alarm

/* do we really need to know about all the signals? */

static char *signames[] = {
    /*  0 */	"SIG-0",	"SIGABRT",	"SIGFPE",	"SIGILL",
    /*  4 */	"SIGINT",	"SIGSEGV",	"SIGTERM",	"SIGUSR1",
    /*  8 */	"SIGUSR2",	"SIGIO/POLL",	"SIGHUP",	"SIGURG",
    /* 12 */	"SIGALRM"
};

SIGTYP (*
#ifdef CK_ANSIC
vsignal(int type, SIGTYP (*func)(int)))(int){
#else
vsignal(type, func))() int type, SIGTYP (*func)(){
#endif
    char *cp;
    cp = (type <= _SIG_MAX && type >= 1) ? signames[type] : "UNKNOWN SIGNAL";

    debug(F111,"VSIGNAL called",cp,(long) func);
    return (signal(type,func));
}

int 
#ifdef CK_ANSIC
valarm(interval) int interval; {
#else
valarm(int interval) {
#endif
    time_t now;
    int rv;

    debug(F101,"VALARM called","",interval);
    time(&now);

    /* figure out how much was left.  If there is time left, say
     * it's at least one second, even if it comes out to zero; this
     * prevents confusion about what a zero return value means.
     */

    if (deadline) {
	rv = deadline - now;
	if (rv <= 0) /* may also have missed the deadline */
	    rv = 1;
    }
    else
	rv = 0; /* it wasn't set */

    /* set the new deadline */

    if (0 >= interval) /* uh... negative interval (space-like?) */
	deadline = 0;
    else
	deadline = now + interval;

    return rv;
}

int
chkalarm(void) {
    time_t now;

    if (0 == deadline) return 0; /* don't bail out */
    time(&now);
    if (now < deadline) return 0; /* strictly less than */

    /* it's time! */
    debug(F101,"chkalarm timer expired, age","",now-deadline);
    kill (0, SIGALRM); /* We don't have raise(), either. */

    /* most of the alarm handlers longjmp, so we may not get here */
    return -1;
}
