/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* |_o_o|\\ Copyright (c) 1986 The Software Distillery.  All Rights Reserved */
/* |. o.| || This program may not be distributed without the permission of   */
/* | .  | || the authors.                                                    */
/* | o  | ||    Dave Baker     Ed Burnette  Stan Chow    Jay Denebeim        */
/* |  . |//     Gordon Keener  Jack Rouse   John Toebes  Doug Walker         */
/* ======          BBS:(919)-471-6436      VOICE:(919)-469-4210              */
/*                                                                           */
/* Contributed to Columbia University for inclusion in C-Kermit.             */
/* Permission is granted to any individual or institution to use, copy, or   */
/* redistribute this software so long as it is not sold for profit, provided */
/* this copyright notice is retained.                                        */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */

char *ckxv = "Amiga tty I/O $Id: ckitio.c,v 1.7 92/03/16 13:50:58 swalton Exp Locker: swalton $";

/*  C K I T I O  --  Serial and Console I/O support for the Amiga */

/*
 * Author: Jack Rouse, The Software Distillery
 * Based on the CKUTIO.C module for Unix
 *
 * Modified for Manx Aztec C and Version 1.2 and forward of Amiga's OS by
 * Stephen Walton of California State University, Northridge,
 * srw@csun.edu.  Further mods documented in ckiker.upd.
 *

 $Log:	ckitio.c,v $
 * Revision 1.7  92/03/16  13:50:58  swalton
 * Support added for CTR/RTS flow control, using the new FLO_ manifest
 * constants in version 5A.
 *
 * Revision 1.6  92/01/15  17:12:35  swalton
 * Added Long BREAK support with new ttsndlb() routine.
 *
 * Added support for multiple devices;  the SET LINE command now takes a
 * line of the form "device/unit".
 *
 *  Revision 1.5  91/07/18  16:04:57  swalton
 *  ttinl() now null terminates a received packet correctly.
 *
 *  Revision 1.4  91/05/29  09:08:57  swalton
 *  1.  Changed function definitions to prototype style.  Required adding
 *      a few forward declarations.
 *  2.  Removed includes of stdio.h, stdlib.h, and string.h, as they are
 *      now pulled in by ckcdeb.h, provided we compile with -DCK_ANSILIBS.
 *
 *  Revision 1.3  90/11/19  21:46:54  swalton
 *  Modifications for compiling with SAS/C Version 5.10, courtesy of
 *  Larry Rosenman (ler@erami.lonestar.org, ler on BIX)
 *
 *  Revision 1.2  90/11/07  14:42:07  swalton
 *  Version 1.2--released to world as first beta test version simultaneously
 *  with release of edit 5A(160).
 *
 *  Revision 1.1  90/07/12  22:30:11  swalton
 *  Rather extensive changes were made to ckitio.c, mainly to add new functions
 * required for the proper operation of C Kermit 5A(149).  They are not listed
 * in detail here;  refer to the parts of the C Kermit interface document
 * (file ckasys.doc in the Kermit archive) for the portions labeled *NEW*.
 * These will point you at the code revisions.
 *
 * Revision 1.0  90/04/30  11:54:27  swalton
 * Initial revision
 *
 */

#include "ckcdeb.h"
#include "ckcker.h"
#include "ckcnet.h"
#include "exec/types.h"
#include "exec/exec.h"
#include "devices/serial.h"
#include "devices/timer.h"
#include "libraries/dos.h"
#include "libraries/dosextens.h"
#define fh_Interact fh_Port
#define fh_Process fh_Type
#include "intuition/intuition.h"
#include "intuition/intuitionbase.h"
#define BREAKSIGS (SIGBREAKF_CTRL_C|SIGBREAKF_CTRL_D)
#ifdef AZTEC_C
#include "fcntl.h"
#include "signal.h"
#include <functions.h>
char *ckxsys = " Commodore Amiga (Aztec_C)";	/* system name */
#else
#ifdef __SASC
#include <fcntl.h>
#include <signal.h>
#include <ios1.h>		/* defines ufbs structure */
#include <proto/exec.h>
#include <proto/dos.h>
#include <proto/intuition.h>
char *ckxsys = " Commodore Amiga (SAS/C)";	/* system name */
#endif
#endif

/* external definitions */
char *dftty = SERIALNAME;		/* serial device name */
int dfloc = 1;				/* serial line is external */
int dfprty = 0;				/* default parity is none */
int ttprty = 0;				/* parity in use */
int dfflow = FLO_XONX;			/* default flow control is on */
int backgrd = 0;			/* default to foreground */
int ckxech = 0;				/* echo in case redirected stdin */
int tvtflg = 0;				/* Flag for ttvt() called. */
int ttcarr = 0;				/* Carrier detection mode */
int ttnproto = NP_NONE;			/* Protocol for network device */

struct Process *CurProc;		/* current process */
struct CommandLineInterface *CurCLI;	/* current CLI info */
struct IntuitionBase *IntuitionBase;	/* ptr to Intuition lib */
short v37;				/* Are we version 37? */

/* static definitions */
static struct MsgPort *serport;		/* message port for serial comm */
static struct MsgPort *conport;		/* console packet port */
static struct timerequest *TimerIOB;	/* timer request */
static struct IOExtSer *ReadIOB;	/* serial input request */
static struct IOExtSer *WriteIOB;	/* serial output request */
static struct DosPacket *conpkt;	/* console I/O packet */
static WORD serialopen;			/* true iff serial device open */
static WORD timeropen;			/* true iff timer device open */
static WORD pendwrite;			/* true iff WriteIOB in use */
static WORD pendread;			/* true iff ReadIOB in use */
static WORD pendconsole;		/* true when console read pending */
static int queuedser;			/* serial pushback char or -1 */
static UBYTE serbufc;			/* char buffer for read ahead I/O */
#define NTTOQ 64			/* connect output queue size */
static char ttoq[NTTOQ];		/* connect output queue */
static int nttoq;			/* number of chars in ttoq */
static int pttoq;			/* next char to output in ttoq */
static int queuedcon;			/* contti pushback char or -1 */
static LONG intsigs;			/* signals for aborting serial I/O */
static int (*inthdlr)(int);		/* function to signal break to */
static BPTR rawcon;			/* file handle for RAW: window */
static BPTR saverr;                     /* saved stderr file handle */
static APTR savewindow;			/* saved process WindowPtr */
static APTR pushwindow;			/* pushed process WindowPtr */
static struct DateStamp prevtime;	/* saved time value */


/* AmigaDOS support (from ckiutl.c) */
struct DosPacket *CreatePacket();
VOID DeletePacket();

#ifdef AZTEC_C
/* translate Unix file handle (0, 1, or 2) to AmigaDOS file handle */
#define DOSFH(n) (_devtab[n].fd)
/* translate Unix file handle (0, 1, or 2) to Aztec file handle */
#define FILENO(n) (n)
extern int Enable_Abort;
#else
/* Lattice runtime externals */
#ifdef __SASC
#define DOSFH(n) (chkufb(n)->ufbfh)
#define FILENO(n) (n)
#endif
#endif

/*
 * Under ANSI C, pointer-pointer assignments are illegal without an
 * explicit cast.  So, we define the following to make such casts short.
 */
#define IOR struct IORequest

/*
 * Forward declarations
 */
void reqres(void);
static void testint(long);

/*
 * make note of a serial error and quit
 */
static
Fail(char *msg)
{
	syscleanup();
	fprintf(stderr, msg);
	fprintf(stderr, "\n");
	exit(2);
}
/*
 * default interrupt handler
 */
static int
defhdlr(int foo)
{
	printf("*** BREAK ***\n");
	doexit(BAD_EXIT, 10);
}

/*
 *  sysinit -- Amiga specific initialization
 */
int
sysinit(void)
{
	struct IOExtSer *iob;

	/* set current process info */
	CurProc = (struct Process *)FindTask((char *)NULL);
	CurCLI = (struct CommandLineInterface *)BADDR(CurProc->pr_CLI);
	backgrd = (CurCLI == NULL || CurCLI->cli_Background);
	savewindow = CurProc->pr_WindowPtr;

	/* default interrupts to exit handler */
	intsigs = BREAKSIGS;
	inthdlr = defhdlr;
#ifdef __SASC
	signal(SIGINT, SIG_IGN);
#else
	Enable_Abort = 0;
#endif

	/* allocate console ports and IO blocks */
	if ((conport = CreatePort((char *)NULL, 0L)) == NULL)
		Fail("no console MsgPort");
	if ((conpkt = CreatePacket()) == NULL)
		Fail("no console packet");

	/* allocate serial ports and IO blocks */
	if ((serport = CreatePort((char *)NULL, 0L)) == NULL)
		Fail("no serial MsgPort");
	iob = (struct IOExtSer *)CreateExtIO(serport,(LONG)sizeof(*iob));
	if ((WriteIOB = iob) == NULL) Fail("no WriteIOB");
	iob = (struct IOExtSer *)CreateExtIO(serport,(LONG)sizeof(*iob));
	if ((ReadIOB = iob) == NULL) Fail("no ReadIOB");

	/* open the timer device */
	TimerIOB = (struct timerequest *)
		   CreateExtIO(serport,(LONG)sizeof(*TimerIOB));
	if (TimerIOB == NULL) Fail("no TimerIOB");
	if (OpenDevice(TIMERNAME, (LONG)UNIT_VBLANK, (IOR *)TimerIOB, 0L) != 0)
		Fail("no timer device");
	timeropen = TRUE;

	/* open the Intuition library */
	if (!IntuitionBase &&
	    (IntuitionBase = (struct IntuitionBase *)
			     OpenLibrary("intuition.library", 0L) ) == NULL )
		Fail("can't open Intuition");

	if (((struct Library *)IntuitionBase)->lib_Version >= 37)
		v37 = TRUE;
	else
		v37 = FALSE;
	/* open the serial device to get configuration */
	iob->io_SerFlags = SERF_SHARED;
	if (OpenDevice(SERIALNAME, 0L, (IOR *)iob, 0L) != 0)
		Fail("can't open serial.device");
	/* set parameters from system defaults */
	if (!(iob->io_SerFlags & SERF_XDISABLED))
		dfflow = FLO_XONX;
	else if (iob->io_SerFlags & SERF_7WIRE)
		dfflow = FLO_RTSC;
	else
		dfflow = FLO_NONE;
	/*
	 * Set default (startup) parity from Preferences settings.
 	 */
	if (iob->io_SerFlags & SERF_PARTY_ON) 	/* Parity is on */
		if (iob->io_ExtFlags & SEXTF_MSPON)	/* Space or mark */
			if (iob->io_ExtFlags & SEXTF_MARK)
				dfprty = 'm';		/* Mark parity */
			else
				dfprty = 's';		/* Space parity */
		else					/* Even or odd */
			if (iob->io_SerFlags & SERF_PARTY_ODD)
				dfprty = 'o';		/* Odd parity */
			else
				dfprty = 'e';		/* Even parity */
	else
		dfprty = 0;				/* No parity. */
	ttprty = dfprty;

	CloseDevice((IOR *)iob);
	serialopen = FALSE;
	return(0);
}

/*
 * syscleanup -- Amiga specific cleanup
 */
syscleanup(void)
{
	/* close everything */
	if (serialopen) CloseDevice((IOR *)ReadIOB);
	if (timeropen) CloseDevice((IOR *)TimerIOB);
	if (TimerIOB) DeleteExtIO((IOR *)TimerIOB);
	if (WriteIOB) DeleteExtIO((IOR *)WriteIOB);
	if (ReadIOB) DeleteExtIO((IOR *)ReadIOB);
	if (serport) DeletePort(serport);
	if (conpkt) DeletePacket(conpkt);
	if (conport) DeletePort(conport);
	reqres();
	if (IntuitionBase)
	{
		CloseLibrary((struct Library *)IntuitionBase);
		IntuitionBase = NULL;
	}

	/* reset standard I/O */
	if (rawcon > 0)
	{
		/* restore Lattice AmigaDOS file handles */
		DOSFH(0) = Input();
		DOSFH(1) = Output();
		DOSFH(2) = saverr;
		Close(rawcon);
	}
	return 1;
}

/*
 * reqoff -- turn requestors off
 *    When AmigaDOS encounters an error that user intervention can fix
 *    (like inserting the correct disk), it normally puts up a requestor.
 *    The following code disables requestors, causing an error to be
 *    returned instead.
 */
void
reqoff(void)
{
	pushwindow = CurProc->pr_WindowPtr;
	CurProc->pr_WindowPtr = (APTR)-1;
}
/*
 * reqpop -- restore requesters to action at last reqoff
 */
void
reqpop(void)
{
	CurProc->pr_WindowPtr = pushwindow;
}

/*
 * reqres -- restore requestors to startup action
 */
void
reqres(void)
{
	CurProc->pr_WindowPtr = savewindow;
}

/*
 * KillIO -- terminate an I/O request
 */
static int
KillIO(struct IORequest *iob)
{
	AbortIO(iob);
	return((int)WaitIO(iob));
}
/*
 * DoIOQuick -- DoIO with quick IO
 * This should not be used where waiting is expected since
 *    it cannot be interrupted.
 */
static int
DoIOQuick(struct IORequest *iob)
{
	/* do I/O with quick option, wait around if necessary */
	iob->io_Flags = IOF_QUICK;
	BeginIO(iob);
	if (!(iob->io_Flags & IOF_QUICK))
		WaitIO(iob);

	/* return the error, if any */
	return((int)iob->io_Error);
}

/*
 * ttopen -- open the serial device
 *    If already open, returns 0 immediately.
 *    Otherwise, the ttname is compare to SERIALNAME and used to
 *    open the serial device, and, if the value of *lcl is < 0, it is
 *    reset to 1 indicating local mode.  Returns -1 on error.
 *    timo is the length of time to wait before flunking open;  we don't
 *    need this feature on the Amiga.
 */
int
ttopen(char * ttname, int *lcl, int modem, int timo)
{
	struct IOExtSer *iob = ReadIOB;
	char *p;
	ULONG unit;
static  char cttname[50];		/* Current open ttname */

	if (modem < 0) return -1;	/* We don't do networks yet. */
	if (serialopen)			/* Already have serial device open */
            if (strcmp(ttname, cttname) == 0)
                return(0);		/* Same device - ignore  */
            else ttclos(0);		/* Different device - close */

	/* verify the serial name */
#if 0
	if (strcmp(ttname, SERIALNAME) != 0) return(-1);
#endif

	/* set open modes.  We no longer open in shared mode. */
	iob->io_SerFlags = (modem > 0 ? SERF_7WIRE : 0);

	/* parse device name as device/unit */
	if ((p = strchr(ttname, '/')) == NULL)
	    unit = 0;
	else {
	    if (*(p + strlen(p) - 1) == 's')    /* Open in shared mode */
            {
                iob->io_SerFlags |= SERF_SHARED;
                *(p + strlen(p) - 1) = '\0';
            }
	    unit = (ULONG) atoi(p + 1);
	    *p = '\0';
	}
	/* open the serial device */
	if (OpenDevice(ttname, unit, (IOR *)iob, 0L) != 0)
		return(-1);
	serialopen = TRUE;
	tvtflg = 0;
	pendread = pendwrite = pendconsole = FALSE;
	queuedser = -1;

	/* fill in the fields of the other IO blocks */
	*WriteIOB = *iob;

	/* set local mode */
	if (*lcl == -1)	*lcl = 1; /* always local */
	if (p) *p = '/';		/* restore slash */
        if (iob->io_SerFlags & SERF_SHARED)
            *(p + strlen(p)) = 's';     /* restore suffix if present */
	strcpy(cttname, ttname);
	return(0);
}

/*
 * StartTimer -- start a timeout
 */
static VOID
StartTimer(LONG secs, LONG micro)
{
	TimerIOB->tr_node.io_Command = TR_ADDREQUEST;
	TimerIOB->tr_time.tv_secs  = secs;
	TimerIOB->tr_time.tv_micro = micro;
	SendIO((IOR *)TimerIOB);
}

/*
 * SerialWait -- wait for serial I/O to terminate
 *    return I/O error
 */
static int
SerialWait(struct IOExtSer *iob, int timeout)
{
	LONG sigs;
	struct timerequest *timer = TimerIOB;
	LONG waitsigs;

	/* set up timeout if necessary */
	if (timeout > 0) StartTimer((LONG)timeout, 0L);

	/* wait for completion, timeout, or interrupt */
	sigs = 0;
	waitsigs = (1L << serport->mp_SigBit) | intsigs;
	for (;;)
	{
		if (sigs & intsigs)
		{	/* interrupted */
			if (timeout > 0) KillIO((IOR *)timer);
			KillIO((IOR *)iob);
			testint(sigs);
			return(-1);
		}
		if (CheckIO((IOR *)iob))
		{
			if (timeout > 0) KillIO((IOR *)timer);
			return((int)WaitIO((IOR *)iob));
		}
		if (timeout > 0 && CheckIO((IOR *)timer))
		{
			KillIO((IOR *)iob);
			WaitIO((IOR *)timer);
			/* restart if XOFF'ed */
			iob->IOSer.io_Command = CMD_START;
			DoIOQuick((IOR *)iob);
			return(-1);
		}
		sigs = Wait(waitsigs);
	}
}

/*
 * TerminateRead -- wait for queued read to finish
 */
static int
TerminateRead(void)
{
	if (!pendread) return(0);
	if (WaitIO((IOR *)ReadIOB) == 0) queuedser = serbufc;
	pendread = FALSE;
	return((int)ReadIOB->IOSer.io_Error);
}

/*
 * TerminateWrite -- ensure WriteIOB is ready for reuse
 */
static int
TerminateWrite(int timeout)
{
	testint(0L);
	if (!pendwrite) return(0);
	pendwrite = FALSE;
	return(SerialWait(WriteIOB, timeout));
}

/*
 * SerialReset -- terminate pending serial and console I/O
 */
static void
SerialReset(void)
{
	if (pendread)
	{
		AbortIO((IOR *)ReadIOB); /* should work even if read finished */
		TerminateRead();
	}

	if (pendconsole)
	{	/* this does not happen normally */
		WaitPort(conport);
		GetMsg(conport);
		pendconsole = FALSE;
	}

	if (pendwrite)
		TerminateWrite(1);
}

/*
 * ttres -- reset serial device
 */
ttres()
{
	if (!serialopen) return(-1);

	/* reset everything */
	SerialReset();
	ReadIOB->IOSer.io_Command = CMD_RESET;
	tvtflg = 0;
	return(DoIOQuick((IOR *)ReadIOB) ? -1 : 0);
}

/*
 * ttclos -- close the serial device
 */
int
ttclos(int foo)
{
	if (!serialopen) return(0);
	if (ttres() < 0) return(-1);
	CloseDevice((IOR *)ReadIOB);
	serialopen = FALSE;
	tvtflg = 0;
	return(0);
}

/*
 * tthang -- hang up phone line
 *    Drops DTR by closing serial.device
 */
int
tthang(void)
{	return((serialopen) ? ttclos(0) : -1); }

/*
 * ttpkt -- set serial device up for packet transmission
 *    sets serial parameters
 */
int
ttpkt(long speed, int flow, int parity)
{
	extern UBYTE eol;
	struct IOExtSer *iob = ReadIOB;

	if (!serialopen || pendread) return(-1);

	/* terminate any pending writes */
	TerminateWrite(1);

	/* fill in parameters */
	iob->io_CtlChar = 0x11130000;
	if (speed >= 0 && ttsspd((int) (speed / 10)) >= 0) iob->io_Baud = speed;
	/*
	 * Notice the dopar(eol) here to set the EOL character with the
	 * appropriate parity.  See also ttinl().
	 */
	setmem(&iob->io_TermArray, sizeof(struct IOTArray), dopar(eol));
	iob->io_ReadLen = iob->io_WriteLen = 8;
	iob->io_StopBits = 1;
	if (flow == FLO_XONX)
		iob->io_SerFlags &= ~(SERF_XDISABLED | SERF_7WIRE);
	else if (flow == FLO_NONE) {
		iob->io_SerFlags |= SERF_XDISABLED;
		iob->io_SerFlags &= ~SERF_7WIRE;
	} else if (flow == FLO_RTSC)
		iob->io_SerFlags |= (SERF_XDISABLED | SERF_7WIRE);
	else {
		puts("Only XON/XOFF and RTS/CTS are available");
		iob->io_SerFlags |= SERF_XDISABLED;
		iob->io_SerFlags &= ~SERF_7WIRE;
	}
	/* if no XON/XOFF flow and high baud rate, RAD_BOOGIE is appropriate */
	if (flow != FLO_XONX && iob->io_Baud > 19200)
		iob->io_SerFlags |= SERF_RAD_BOOGIE;
	else
		iob->io_SerFlags &= ~SERF_RAD_BOOGIE;

	/*
	 * Parity setting.  For packet send/receive, we turn off the
	 * Amiga's internal parity generation and checking, as this code
	 * does it itself (which makes it bigger and slower...).  We
	 * save the current parity for ttinl().
	 */

	ttprty = parity;
	iob->io_SerFlags &= ~(SERF_EOFMODE|SERF_PARTY_ON|SERF_PARTY_ODD);
	iob->io_ExtFlags = 0;		/* MUST BE ZERO unless Mark or Space. */

	/* set the parameters */
	iob->IOSer.io_Command = SDCMD_SETPARAMS;
	if (DoIOQuick((IOR *)iob) != 0) return(-1);
	tvtflg = 0;
	return(ttflui());
}

/*
 * ttvt -- set up serial device for connect mode.  This is almost the same
 * as ttpkt() on the Amiga, except we save the settings and a flag and return
 * without doing anything if we've already been called with the same
 * values.
 */
int
ttvt(long speed, int flow) {
	static long ospeed = -1;
	static int oflow = -9;

	if (tvtflg != 0 && ospeed == speed && oflow == flow)
		return 0;
	if (ttpkt(speed, flow, 0) < 0)
		return -1;
	ospeed = speed;			/* Save speed */
	oflow = flow;			/* and flow control set */
	tvtflg = 1;			/* and flag we've been called */
	return 0;
}

/*  T T S S P D  --  Checks and sets transmission rate.  */

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

int
ttsspd(int cps) {
    long s;

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

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

    /* First check that the given speed is valid. */

    switch (cps) {
      case 0:   s = 0;    break;
      case 5:   s = 50;   break;
      case 7:   s = 75;   break;
      case 11:  s = 110;  break;
      case 15:  s = 150;  break;
      case 20:  s = 200;  break;
      case 30:  s = 300;  break;
      case 60:  s = 600;  break;
      case 120: s = 1200; break;
      case 180: s = 1800; break;
      case 240: s = 2400; break;
      case 480: s = 4800; break;
      case 888: return -1;		/* 888 means 75/1200 split speed */
      case 960: s = 9600; break;
      case 1920: s = 19200; break;
      case 3840: s = 38400; break;
      default: s = -1; break;
    }
    return(s > 0 ? cps : -1);
}

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

/*
  Read speed from serial.device, or, if not open, return the value in
  the current ReadIOB.
*/
long
ttgspd(void) {				/* Get current tty speed */
	struct IOExtSer *myread = ReadIOB;

	if (!serialopen)
		if (myread != NULL) return((long)myread->io_Baud);
		else return -1;
	testint(0L);
	if (pendread && !CheckIO((IOR *)myread)) return(0);
	if (TerminateRead() != 0) return(-1);
	myread->IOSer.io_Command = SDCMD_QUERY;
	return((DoIOQuick((IOR *)myread) == 0)
			? (long)myread->io_Baud
			: -1);
}

/*
 * ttflui -- flush serial device input buffer
 */
int
ttflui(void)
{
	if (!serialopen || pendread) return(-1);
	queuedser = -1;
	ReadIOB->IOSer.io_Command = CMD_CLEAR;
	return(DoIOQuick((IOR *)ReadIOB) ? -1 : 0);
}

/*
 * ttfluo -- flush serial output buffer
 */
int
ttfluo(void)
{
	if (!serialopen || pendwrite) return -1;
	WriteIOB->IOSer.io_Command = CMD_CLEAR;
	return(DoIOQuick((IOR *)WriteIOB) ? -1 : 0);
}


static struct IntuiText BodyText = {
	0xffu, 0xffu, 0, 4, 4, NULL, (UBYTE *) "Interrupt Requested", NULL
};

static struct IntuiText ContinueText = {
	0xffu, 0xffu, 0, 4, 4, NULL, (UBYTE *) "Continue", NULL
};

static struct IntuiText AbortText = {
	0xffu, 0xffu, 0, 4, 4, NULL, (UBYTE *) "Exit C-Kermit", NULL
};

/*
 * test for and catch interrupt
 */
void
testint(LONG sigs)
{
	int (*catch)(int);

	/* test for and reset caught interrupt signals */
	if (((sigs | SetSignal(0L, (LONG)BREAKSIGS)) & intsigs) && inthdlr)
	{
		if (AutoRequest((struct Window *)NULL,
				&BodyText,
				&ContinueText,
				&AbortText,
				0L, 0L, 260L, 55L) )
			return;
		catch = inthdlr;
		inthdlr = NULL;
		intsigs = 0;
		(void) (*catch)(0);
	}
}

/*
 * conint -- set console interrupt handler and suspend handler.
 */
void
conint(int (*newhdlr)(int), int (*stophdlr)(int))
{
	testint(0L);			/* handle any pending interrupts */
	inthdlr = newhdlr;		/* set the new handler */
	intsigs = BREAKSIGS;		/* note signal caught */
}

/*
 * connoi -- disable interrupt trapping
 */
void
connoi(void)
{
	inthdlr = NULL;			/* disable interrupts */
	intsigs = 0;			/* note signal ignored */
	testint(0L);			/* ignore pending interrupts */
}

/*
 * ttchk -- return number of chars immediately available from serial device
 */
int
ttchk(void)
{
	struct IOExtSer *myread = ReadIOB;

	if (!serialopen) return(-1);
	testint(0L);
	if (pendread && !CheckIO((IOR *)myread)) return(0);
	if (TerminateRead() != 0) return(-1);
	myread->IOSer.io_Command = SDCMD_QUERY;
	return((DoIOQuick((IOR *)myread) == 0)
			? ((queuedser >= 0 ? 1 : 0) + (int)myread->IOSer.io_Actual)
			: -1);
}

/*
 * ttxin -- get n characters from serial device.  This routine should
 * only be called when we know that there are at least n characters
 * ready to be read.
 */
int
ttxin(int n, CHAR *buf)
{	return(ttinl(buf, n, 0, 0)); }

#ifdef PARSENSE

extern CHAR partab[];

/*  P A R C H K  --  Check if Kermit packet has parity  */

/*
  Call with s = pointer to packet, start = packet start character, n = length.
  Returns 0 if packet has no parity, -1 on error, or if packet has parity:
    'e' for even, 'o' for odd, 'm' for mark.  Space parity cannot be sensed.
*/
parchk(s,start,n) CHAR *s, start; int n; {
    CHAR s0, s1, s2, s3, sn;

    debug(F101,"parchk n","",n);
    debug(F101,"parchk start","",start);
    debug(F110,"parchk s",s,0);

    s0 = s[0] & 0x7f;			/* Mark field (usually Ctrl-A) */

    if (s0 != start || n < 5) return(-1); /* Not a valid packet */

/* Look at packet control fields, which never have 8th bit set */
/* First check for no parity, most common case. */

    if (((s[0] | s[1] | s[2] | s[3] | s[n-2]) & 0x80) == 0)
      return(0);			/* No parity */

/* Check for mark parity */

    if (((s[0] & s[1] & s[2] & s[3] & s[n-2]) & 0x80) == 0x80)
      return('m');			/* Mark parity */

/* Packet has some kind of parity */
/* Make 7-bit copies of control fields */

    s1 = s[1] & 0x7f;			/* LEN */
    s2 = s[2] & 0x7f;			/* SEQ */
    s3 = s[3] & 0x7f;			/* TYPE */
    sn = s[n-2] & 0x7f;			/* CHECK */

/* Check for even parity */

    if ((s[0] == partab[s0]) &&
        (s[1] == partab[s1]) &&
        (s[2] == partab[s2]) &&
	(s[3] == partab[s3]) &&
	(s[n-2] == partab[sn]))
      return('e');

/* Check for odd parity */

    if ((s[0] != partab[s0]) &&
        (s[1] != partab[s1]) &&
        (s[2] != partab[s2]) &&
	(s[3] != partab[s3]) &&
	(s[n-2] != partab[sn]))
      return('o');

/* Otherwise it's probably line noise.  Let checksum calculation catch it. */

    return(-1);
}
#endif /* PARSENSE */

/*
 * ttinc -- read character from serial line
 */
int
ttinc(int timeout)
{
	UBYTE ch;

	return((ttinl((CHAR *)&ch, 1, timeout, 0) > 0) ? (int)ch : -1);
}

/*
 * ttol -- write n chars to serial device.  For small writes, we have
 * a small local buffer which allows them to run asynchronously.  For
 * large writes, we do them synchronously.  This seems to be the best
 * compromise between speed and code simplicity and size.
 *
 * Stephen Walton, 23 October 1989
 */
int
ttol(CHAR *buf, int n)
{
	struct IOExtSer *mywrite = WriteIOB;
	static char outbuf[256];	/* safe place for output characters */

	if (!serialopen) return(-1);
	if (TerminateWrite(1) != 0) return(-1);
	pendwrite = TRUE;
	mywrite->IOSer.io_Command = CMD_WRITE;
	if (n <= 256) {
		movmem(buf, outbuf, n);
		mywrite->IOSer.io_Data    = (APTR)outbuf;
		mywrite->IOSer.io_Length  = n;
		SendIO((IOR *)mywrite);
	} else {
		mywrite->IOSer.io_Data	= (APTR) buf;
		mywrite->IOSer.io_Length	= n;
		DoIOQuick((IOR *)mywrite);
	}
	return(n);
}

/*
 * ttoc -- output single character to serial device
 */
int
ttoc(char c)
{	return(ttol((CHAR *) &c, 1)); }

/*
 * ttinl -- read from serial device, possibly with timeout and eol character
 *    reads up to n characters, returning the number of characters read
 *    if eol > 0, reading the eol character will terminate read
 *    if timeout > 0, terminates read if timeout elapses
 *    returns -1 on error, such as timeout or interrupt
 *
 *    Note that this is the single routine which does all character reading
 *    in Amiga C Kermit, and has some added "features" compared to, say,
 *    the Unix version.  If timeout is 0, this routine waits forever.
 *    If eol is zero, it is not used.
 *
 *    New for 5A(157) is the start parameter, which is the start-of-packet
 *    character.  Following the Unix example, we just read until eol,
 *    but return a bad packet if the first character we got doesn't agree
 *    with start.
 */
int
ttinl(CHAR *buf, int n, int timeout, CHAR eol)
{
        unsigned  mask;
	struct IOExtSer *myread = ReadIOB;
	int count;
	int nread, i;

	testint(0L);
 	if (!serialopen || pendread || n <= 0) return(-1);

	mask = (ttprty ? 0177 : 0377);	/* parity stripping mask */

	/* handle pushback */
	if (queuedser >= 0)
	{
		*buf = queuedser & mask;	/* Strip queued character. */
		queuedser = -1;
		if (*buf == eol || n == 1) return(1);
		++buf;
		--n;
		count = 1;
	}
	else
		count = 0;

	/* set up line terminator */
	if (eol > 0)
	{
		/*
		 * For reasons which are obscure to me, this batch of
		 * code generally fails.  Normally, this doesn't matter,
		 * because io_TermArray is set in ttpkt() above, and so
		 * this code is only executed if eol changes as a result
		 * of the initial packet negotiation.  I found the bug
		 * by inadvertently not using dopar(eol) in the setting
		 * of io_TermArray in ttpkt(), which did cause this code
		 * to be called if parity was MARK or EVEN (since in that
		 * case dopar(eol) != eol).
		 */

		if (dopar(eol) != *(UBYTE *)&myread->io_TermArray)
		{
			setmem(&myread->io_TermArray,
			       sizeof(struct IOTArray), dopar(eol));
			myread->IOSer.io_Command = SDCMD_SETPARAMS;
			if (DoIOQuick((IOR *)myread) != 0) {
				debug(F111, "SETPARAMS fails in ttinl()",
				      "io_Error", (int) myread->IOSer.io_Error);
				myread->io_TermArray.TermArray0 =
					myread->io_TermArray.TermArray1 = 0xffffffffu;
				return -1;
			}
		}
		myread->io_SerFlags |= SERF_EOFMODE;
	}
	else
		myread->io_SerFlags &= ~SERF_EOFMODE;

	/* set up the read */
	myread->IOSer.io_Command = CMD_READ;
	myread->IOSer.io_Data    = (APTR)buf;
	myread->IOSer.io_Length  = n;

	/* perform read quickly if possible */
	myread->IOSer.io_Flags = IOF_QUICK;
	BeginIO((IOR *)myread);
	if (myread->IOSer.io_Flags & IOF_QUICK)
		myread->IOSer.io_Flags = 0;
	else
		/* wait for read to complete if no QUICK. */
		if (SerialWait(myread, timeout) != 0)
			return -1;

	if (myread->IOSer.io_Error != 0)
		return -1;
#if COMMENT
	if (start != 0 && (buf[0] & mask) != start) /* Bad packet */
		return -1;
#endif
	/* Strip parity bits if need be. */
	nread = (int) myread->IOSer.io_Actual;
	if (ttprty)
		for (i = 0; i < nread; i++)
			buf[i] &= mask;
	if (nread > 1)
		buf[nread] = '\0';		/* Null terminate */
	return(count + nread);
}

/*
 * Sleeper -- perform an interruptible timeout
 */
static int
Sleeper(LONG secs, LONG micro)
{
	LONG sigs;
	LONG waitsigs;
	struct timerequest *timer = TimerIOB;

	if (!timeropen) return(-1);
	StartTimer(secs, micro);
	sigs = 0;
	waitsigs = (1L << serport->mp_SigBit) | intsigs;
	for (;;)
	{
		if (CheckIO((IOR *)timer))
		{
			WaitIO((IOR *)timer);
			return(0);
		}
		if (sigs & intsigs)
		{
			KillIO((IOR *)timer);
			testint(sigs);
			return(-1);
		}
		sigs = Wait(waitsigs);
	}
}

/*
 * sleep -- wait n seconds
 */
int
sleep(int n)
{	return(Sleeper((LONG)n, 0L)); }

/*
 * msleep -- wait n milliseconds
 */
int
msleep(int m)
{	return(Sleeper((LONG)(m / 1000), (m % 1000) * 1000L)); }


/*
 * rtimer -- reset elapsed time
 */
void
rtimer(void)
{	DateStamp(&prevtime); }

/*
 * gtimer -- get currently elapsed time in seconds
 */
int
gtimer(void)
{
	int x;
	struct DateStamp curtime;

	DateStamp(&curtime);
	x = ((curtime.ds_Days   - prevtime.ds_Days  ) * 1440 +
	     (curtime.ds_Minute - prevtime.ds_Minute) ) * 60 +
	     (curtime.ds_Tick   - prevtime.ds_Tick  ) / 50;
	return((x < 0) ? 0 : x );
}

/*
 * ztime -- format current date and time into string
 */
void
ztime(char **s)
{
   /*
    * The following date code taken from a USENET article by
    *    Tomas Rokicki(rokicki@Navajo.ARPA)
    */
   static char *months[] = { NULL,
      "January","February","March","April","May","June",
      "July","August","September","October","November","December"};
   static char buf[32];

   long n ;
   int m, d, y ;
   struct DateStamp datetime;

   DateStamp(&datetime);

   n = datetime.ds_Days - 2251 ;
   y = (4 * n + 3) / 1461 ;
   n -= 1461 * y / 4 ;
   y += 1984 ;
   m = (5 * n + 2) / 153 ;
   d = n - (153 * m + 2) / 5 + 1 ;
   m += 3 ;
   if (m > 12) {
      y++ ;
      m -= 12 ;
   }
   sprintf(buf, "%02d:%02d:%02d %s %d, %d",
           datetime.ds_Minute / 60, datetime.ds_Minute % 60,
	   datetime.ds_Tick / 50, months[m], d, y) ;
   *s = buf;
}

/*
 * congm -- save console modes
 */
int
congm(void)
{
	if (!saverr) saverr = DOSFH(2);
	return(0);
}

/*
 * CreateWindow -- create window and jam it into standard I/O
 */
int
CreateWindow(int esc)
{
	if (rawcon > 0) return(0);
	congm();

	if ((rawcon = Open("RAW:0/0/1024/1024/Kermit", (LONG)MODE_NEWFILE)) == 0)
		return(-1);
	DOSFH(0) = DOSFH(1) = DOSFH(2) = rawcon;

	/* if we create a window, don't abort on errors or echo */
	backgrd = FALSE;
	ckxech = 1;
	return(0);
}

/*
 * concb -- put console in single character wakeup mode
 */
int
concb(char esc)
{
	if (rawcon) return(0);
	if (CurCLI && CurProc->pr_CIS != CurCLI->cli_StandardInput)
		return(0);
	return(CreateWindow(esc));
}

/*
 * conbin -- put console in raw mode
 */
int
conbin(char esc)
{
	if (rawcon) return(0);
	if (CurCLI && CurProc->pr_CIS != CurCLI->cli_StandardInput)
		return(isatty(0) ? 0 : -1);
	return(CreateWindow(esc));
}

/*
 * conres -- restore console
 *    we actually restore in syscleanup()
 */
conres()
{	return(0); }

/*
 * conoc -- output character to console
 */
conoc(char c)
{
	putchar(c);
	fflush(stdout);
	testint(0L);
}

/*
 * conxo -- output x chars to console
 */
int
conxo(int n, char *buf)
{
	int retval;

	fflush(stdout);
	retval = write(FILENO(1), buf, n);
	testint(0L);
	return retval;
}

/*
 * conol -- output line to console
 */
int
conol(char *l)
{
	int retval;
	retval = fputs(l, stdout);
	fflush(stdout);
	testint(0L);
	return retval;
}

/*
 * conola -- output line array to console
 */
int
conola(char **l)
{
	for (; **l; ++l)
		if (conol(*l) < 0)
			return(-1);
	return 0;
}

/*
 * conoll -- output line with CRLF
 */
int
conoll(char *l)
{
	if (conol(l) < 0)
		return -1;
	if (conxo(2, "\r\n") < 0)
		return -1;
	return 0;
}

/*
 * conchk -- returns nonzero if characters available from console
 */
int
conchk(void)
{
	fflush(stdout);
	testint(0L);
	return(WaitForChar(DOSFH(0), 0L) != 0);
}

/*
 * coninc -- get input character from console
 */
int
coninc(int timeout)
{
	UBYTE ch;

	fflush(stdout);
	testint(0L);
	if (timeout > 0 && !WaitForChar(DOSFH(0), timeout * 1000000L))
		return(-1);
	if (read(FILENO(0), &ch, 1) < 1) return(-1);
	testint(0L);
	return((int)ch);
}

/*
 * T T S C A R R -- Copy desired character mode to global ttcarr for future
 * and later use.
 */
ttscarr(carrier) int carrier; {
    ttcarr = carrier;
    debug(F101, "ttscarr","",ttcarr);
    return(ttcarr);
}

static int
sendbreak(long time) {
	if (!serialopen) return(-1);
	/* flush queued output */
	TerminateWrite(1);
	nttoq = 0;
	pendwrite = TRUE;
	WriteIOB->IOSer.io_Command = SDCMD_SETPARAMS;
	WriteIOB->io_BrkTime = time;
	(void) DoIOQuick((IOR *)WriteIOB);
	pendwrite = TRUE;
	WriteIOB->IOSer.io_Command = SDCMD_BREAK;
	WriteIOB->io_SerFlags &= ~SERF_QUEUEDBRK;
	SendIO((IOR *)WriteIOB);
	return(0);
}

/*
 * ttsndb -- send a BREAK
 *    flushes queued and active output
 */
int
ttsndb(void)
{
	return(sendbreak(275000L));
}

/*
 * ttsndlb -- send a long BREAK (1.5 sec)
 */
int
ttsndlb(void) {
	return(sendbreak(1500000L));
}

/*  T T W M D M  --  Wait for modem signals  */

/*
  Wait up to timo seconds for all of the given modem signals to appear.
  mdmsig is a bit mask, in which:
   BM_CTS (bit 0) means wait for Clear To Send
   BM_DSR (bit 1) means wait for Data Set Ready
   BM_DCD (bit 2) means wait for Carrier Detect
  Returns:
  -3 Not implemented.
  -2 This line does not have modem control.
  -1 Timeout: time limit exceeded before all signals were detected.
   1 Success.
*/
int
ttwmdm(int mdmsig, int timo) {
    return(-3);
}

/*  T T G M D M  --  Get modem signals  */
/*
 Looks for the modem signals CTS, DSR, and CTS, and returns those that are
 on in as its return value, in a bit mask as described for ttwmdm.  Returns:
 -3 Not implemented
 -2 if the line does not have modem control
 -1 on error.
 >= 0 on success, with a bit mask containing the modem signals that are on.
*/

int
ttgmdm(void) {
    return(-3);
}


/*
 * ttocq -- write char to serial device, queueing if necessary
 *    returns -2 on overrun, -1 on serial error
 *    use only in connect mode
 */
int
ttocq(char c)
{
	int i;

	if (!serialopen) return(-1);
	if (pendwrite && CheckIO((IOR *)WriteIOB))
	{
		pendwrite = FALSE;
		if (WaitIO((IOR *)WriteIOB) != 0) return(-1);
	}
	if (pendwrite)
	{
		if (nttoq >= NTTOQ) return(-2);		/* overrun */
		ttoq[(pttoq + nttoq++) % NTTOQ] = c;
	}
	else if (nttoq == 0)
		return(ttoc(c));
	else
	{
		i = ttoc(ttoq[pttoq]);
		ttoq[(pttoq + nttoq) % NTTOQ] = c;
		pttoq = (pttoq + 1) % NTTOQ;
		if (i < 0) return(-1);
	}
	return(1);
}

/*
 * ttonq -- returns number of characters in serial output queue
 */
int
ttonq(void)
{	return(nttoq); }

/*
 * conttb -- prepare for contti() usage
 */
void
conttb(void)
{
	/* flush queued input and output */
	queuedcon = -1;
	pttoq = nttoq = 0;
}

/*
 * contte -- end contti() usage
 *    this can be called after a tthang, it which case ttres will already
 *    have done this cleanup
 */
void
contte(void)
{
	/* clear any pending ^C, ^D interrupts */
	testint(0L);

	/* terminate any pending I/O */
	if (serialopen) SerialReset();
}

/*
 * contti -- wait for console or tty input
 *    returns next console input or -1 when serial input available
 */
int
contti(void)
{
	int i;
	LONG waitsigs;
	struct DosPacket *pkt = conpkt;
	struct IOExtSer *myread = ReadIOB;
	static UBYTE conchar;
	BPTR dosfh = DOSFH(0);
	struct FileHandle *fh = (struct FileHandle *)BADDR(dosfh);

	if (queuedcon >= 0)
	{
		conchar = queuedcon;
		queuedcon = -1;
		return((int)conchar);
	}

	if (!pendconsole)
	{	/* start a console read */
		pkt->dp_Port = conport;
		pkt->dp_Type = ACTION_READ;
		pkt->dp_Arg1 = (LONG)dosfh;
		pkt->dp_Arg2 = (LONG)&conchar;
		pkt->dp_Arg3 = 1;
		PutMsg(fh->fh_Process, pkt->dp_Link);
		pendconsole = TRUE;
	}

	if (queuedser < 0 && !pendread)
	{	/* start a serial read */
		myread->IOSer.io_Command = CMD_READ;
		myread->IOSer.io_Data    = (APTR)&serbufc;
		myread->IOSer.io_Length  = 1;
		SendIO((IOR *)myread);
		pendread = TRUE;
	}

	waitsigs = (1L << serport->mp_SigBit) | (1L << conport->mp_SigBit);
	for (;;)
	{
		if (pendwrite && CheckIO((IOR *)WriteIOB))
		{
			pendwrite = FALSE;
			if (nttoq > 0)
			{
				i = ttoc(ttoq[pttoq]);
				pttoq = (pttoq + 1) % NTTOQ;
				--nttoq;
				if (i < 0) return(-1);
			}
		}

		/* give the console first chance */
		if (GetMsg(conport))
		{
			pendconsole = FALSE;
			if (pkt->dp_Res1 != 1) return(-1);
			/* translate CSI to ESC [ */
			if (conchar == 0x9B)
			{	conchar = 0x1B; queuedcon = '['; }
			return((int)conchar);
		}

		if (queuedser >= 0) return(-2);

		if (CheckIO((IOR *)myread))
			return((TerminateRead() == 0) ? -2 : -1);

		Wait(waitsigs);
	}
}

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

/*
 * Even though this isn't supported on the Amiga, I return success anyway.
 * After all, the user can pop the window to the back and do something
 * else any time he wants.
 */

int
psuspend(int foo) {
    return 0;
}

/* P R I V _ functions -- all dummy on the Amiga. */

int
priv_ini(void) {
    return 0;
}


int
priv_on(void) {
    return 0;
}

int
priv_off(void) {
    return 0;
}

int
priv_can(void) {
    return 0;
}
