/*	
 * A completely new C Kermit module.
 *
 * Based on code from Frank Da Cruz's excellent book, _Kermit: A File
 * Transfer Protocol_, Digital Press, 1986.
 *
 * As this code is almost entirely from said book, it is certainly covered
 * by that book's copyright.  Basically, this means the code is freely
 * distributable, and can be used in a commercial program provided such
 * use does not increase the program's cost to the purchaser beyond a small
 * handling fee.
 *
 * Stephen Walton, swalton@solar.stanford.edu
 * Dept. of Physics and Astronomy
 * California State University, Northridge
 * Northridge, CA 91330
 *
 *			ORGANIZATION
 *
 *  This file is in three pieces, and could probably be broken into
 * two files.  Leading off are #DEFINE's and declarations of
 * global and external variables.  Following are the unmodified sources
 * from The Book.   Then are the z* file-handling routines written using
 * standard Unix-style (almost ANSI C) file routines.
 *
 * I make no apologies for the organization; my primary goals were (1) to
 * use the unmodified book source except for errors found by Lint, and
 * (2) to allow this file to be plugged into an otherwise unmodified
 * terminal program for the Commodore Amiga computer called VT100.  The
 * comments starting with the string "/*lint" are for the Lint program
 * of Gimpel Software, available for the Commodore Amiga and MS-DOS
 * machines.  I highly recommend it.
 *
 * A few words about style herein.  Both "book Kermit" and the official
 * C Kermit release make extensive use of global variables to set various
 * options and keep track of what is going on.  I don't like this, but
 * since example source code must be part of the Kermit specification,
 * I chose to keep that organization.  There are several places where I
 * I have tried to modularize things better.  First of all, everything
 * which is not needed outside of this file is declared "static".  Second,
 * I have kept the book Kermit code pretty much intact, except for some lint
 * related things and one extra convention:  tmsg() appends characters to
 * an existing status line, but tmsgl() is required to force that output to
 * be seen.  Hence, I've changed the last in each series of tmsg() calls to
 * tmsgl().
 */


/*
 * Revision History--Versions 0 through 1 never left me.
 *
 * Version 0.5--Created and linted.
 * Version 0.6--Added RESUME to handling of "unknown packets".
 *	      --Added proto() function to wrap around wart() for handling
 *		of startup and cleanup functions.
 *	      --Changed handling of timeouts in input():  instead of an
 *		error("Timeout") call, it puts the "Timeout" message in
 *		the data field and pretends it read an 'E' packet.
 * Version 0.7--Fixed a problem with rpsiz and MAXRP.  I had set rpsiz
 *		to MAXRP in rpar() and was calling ttinl() with a max
 *		argument of MAXRP, which resulted in truncated packets;
 *		to wit, MAXRP+1 characters could come into ttinl counting
 *		the eol.  Created defines DRPSIZ/DSPSIZ for default values
 *		for rpsiz and spsiz, and increased MAXRP.
 *	      --Changed rcvpkt, sndpkt, and data so that they are pointers
 *		instead of static arrays, and allocate and de-allocate
 *		them in proto().
 * Version 0.8--Added client support.  To make a Get
 *		command, point the char *cmarg at the remote file
 *		specification and set the start state to 'r'
 *		(extern char start in the calling file).
 * Version 0.9--Added long packet support.  This required adding an extern
 *		int urpsiz which is set to the desired receive packet size
 *		by the user interface.
 *	      --Since the above required using the capas, I also added the
 *		code from C Kermit 4E(070) for negotiating window size
 *		in rpar() and spar(). Of course, we don't do windows yet.
 *	      --Fixed a bug which is also part of C Kermit 4E(070).
 *		If we tell the sender that we can receive a packet
 *		of size MAXRP, the packet can contain as many as
 *		MAXRP+5 characters, counting the EOL and extended
 *		headers.  So rpack() must be able to handle somewhat
 *		more characters than the actual maximum packet length.
 *		I defined MAXRP here to be 1024, but only allow
 *		rpsiz in proto() to be MAXRP-20.
 * Version 0.95--Added code to gracefully abort transfer.  This works
 *		via the cx and cz external variables and the state 'a'.
 * Version 1.0--Unleashed upon the world.
 * Version 1.1--Marco Papa (papa on BIX, papa@pollux.usc.edu on Internet)
 *		added the code between the #ifdef XPRKERMIT...#endif
 *		pairs, which turns this code into a routine which can
 *		be used as an Amiga external file transfer protocol.
 *		Upon getting it back from him, I fixed two lingering
 *		bugs:  there was an "if (ebqflg & b8)" in encode()
 *		which should be &&, and the first argument of encode()
 *		needed to be declared as an int, not a char.
 *	      --Allow user to set bctr (block check to request) himself.
 *	      --If urpsiz is illegal, we set rpsiz to a legal value;  I also
 *		set urpsiz to this as well, so the user knows what's up.
 * Version 1.2--Added a typedef of CHAR to unsigned char to allow this code
 *		to work with the latest version of Wart (V 1A(006) dated
 *		12 January 1989.
 * Version 1.3--21 December 1989
 *		(1)  Added a tflush() call to the end of decode().  This
 *		gives the user an opportunity to flush out the messages
 *		from X packets, if the I/O is buffered.
 *
 * Version 1.4-- 5 January 1989
 * 		(1) Added the counters for characters on a per-file and
 *		per-transaction basis.  Later, we can use the per-file
 *		ones and a timer to figure out how long we have to go
 *		on the current file.
 *		(2) Started replacing tchar() calls with calls to the
 *		screen() function, again a la C Kermit.  #define's for
 *		the manifest constants for screen() are in kermitproto.h,
 * 		copied from C Kermit.
 *		
 */

#ifdef XPRKERMIT
extern long calla(), callaa(), callad(), calladd(), calladda(), calldaa();
extern long (*xupdate)(), (*xswrite)(), (*xfopen)(), (*xfclose)(), (*xfread)(),
     (*xfwrite)(), (*xsread)(),  (*xchkabort)(), (*xffirst)(), (*xfnext)(),
     (*xsflush)(), (*xfinfo)(), (*xgets)();
extern long brkflag;
#include "types.h"
#include "xproto.h"
#endif

/*
 * Run this through the Wart preprocessor which comes with C Kermit.
 */

#include "kermitproto.h"

/*
 * Here are the variables which need to be set to startup values, and which
 * also can be freely changed between protocol transfers.  At first I thought
 * to declare them all "extern" in order to force definition elsewhere.
 * On reflection, it makes sense to both declare them here and set them to
 * their default startup values.  Thus they can be ignored outside of this
 * module if you so desire.
 *
 * Note that the names are very systematic:  Names beginning with "r" have
 * to do with values I use for received packets;  those beginning with "s"
 * are values I use for sending packets.  Also note we set some, others are
 * set for us.  I have made the ones we get in spar() static (local),
 * and the ones we send in rpar() global.
 *
 * First the ints.
 */

int	cx = 0,
	cz = 0,		/* Flags for aborting transfers.  cx (control-X)*
			 * is set to 1 if an abort of the current file	*
			 * is desired, cz (control-Z) if an abort of	*
			 * an entire batch transfer is desired.		*/
	rpsiz = MAXRP,	/* Maximum packet size				*/
			/* Like most of the receive packet parameters,	*
			 * this one is actually set by the sender.  But	*
			 * since the sender has the option to not send	*
			 * these, we must initialize to "reasonable"	*
			 * defaults.					*/
	bctr = 1,	/* Block check type to request.			*/
	limit = 5,	/* Retry limit on receive			*/
	warn = 0,	/* 1 for warn before overwriting files		*/
	rpadn = 0,	/* Number of pad characters I require.		*/
	rtimo = 10;	/* How long I want you to wait before you	*
			 * you time me out.				*/

char	rmark = '\1',	/* Start of packet marker for receive		*/
	reol = '\r',	/* End of packet marker for receive		*/
	start = 0,	/* Start state					*/
	sctlq = '#',	/* Control character quote character for send	*/
	rpadc = '\0';	/* Pad character I want you to use		*/

/*
 * Variables which MUST be set by the external interface.
 */
extern int
	parity,		/* 0 for no parity--need for proper 8th-bit quote */
	text,		/* Flag 1 for text file, 0 for binary file	*/
	urpsiz;		/* Maximum receive packet size user wants.	*/

extern char
	*cmarg;		/* Character string containing Kermit server cmd */

/*
 * Variables having to do with the status of the file transfer.
 */
static long tfc,	/* File chararacters sent/received, total. */
	    ffc,	/*   "       "           "        , current file. */
	    tlci,	/* Comm line characters in, total. */
	    flci,	/*   "    "      "       ", current file. */
	    tlco,	/*   "    "      "       ", total. */
	    flco;	/*   "    "      "       ", current file. */
/*
 * In a fit of cleverness, here are some macro defines for variables we *
 * aren't currently using. Only now we tell Lint to ignore constant	*
 * Booleans!
 */

/*lint -e506 */

#define local 1		/* Local mode flag--that is, I'm on your end	*/
#define server 0	/* We are never server				*/
#define delay 0		/* Time to delay before sending first packet	*/
#define xpkt 0		/* Send X packet instead of F?			*/

/*
 * This block of defines is strictly internal flags of various kinds.	*
 * I hope to Grid that I've got them all.  Someday this will be cleaner *
 */
static int
	spsiz = DSPSIZ,	/* Maximum send packet size			*/
	wsize = MAXWS,	/* Maximum window size				*/
	sndpkl,		/* Size of packet currently being attempted	*/
	filcnt,		/* Number of files transferred so far		*/
	bctu = 1,	/* Block check type to use			*/
	rqf,		/* Flag for 8th bit quote negotiations		*/
	ebq = '&',	/* 8th-bit prefix				*/
	ebqflg = 0,	/* 8th-bit quoting flag				*/
	xflag,		/* Output to screen for generic server commands	*/
	rq = 0,		/* Received 8bq bid				*/
	sq = 'Y',	/* Sent 8bq bid					*/
	rpt = 0,	/* Repeat count					*/
	rptq = '~',	/* Repeat prefix				*/
	rptflg = 0,	/* Repeat processing flag			*/
	capas = 10,	/* Final position of inbound capas mask		*/
	atcapr = 0,	/* Attribute capability requested		*/
	atcapu = 0,	/* Attribute capability used			*/
	swcapr = 0,	/* Sliding windows capability requested		*/
	swcapu = 0,	/* Sliding windows capability used		*/
	lpcapr = 0,	/* Long packets capability requested		*/
	lpcapu = 0,	/* Long packets capability used			*/
	rsn,		/* Received sequence number			*/
	seq = 0,	/* Current sequence number			*/
	maxsiz,		/* Maximum data size for packet			*/
	rln,		/* Length of received data field		*/
	size,		/* Current size of output packet data		*/
	osize,		/* Previous output packet data size		*/
	first = 0,	/* Some kind of lookahead flag			*/
	stimo = 5,	/* Timeout interval for me to use		*/
	spadn = 0;	/* Number of pad characters for me to use	*/

#define atcapb 8	/* Attribute capability bit			*/
#define swcapb 4	/* Sliding windows capability bit		*/
#define lpcapb 2	/* Long packets capability bit			*/
#define closif zclosi	/* I use closif() to close the input file in	*
			 * case it needs to be more complex later, but	*
			 * for now it just calls the z routine.		*/

static char
	smark = '\1',	/* Start of packet marker for send		*/
	spadc = '\0',	/* Pad character to use on sending		*/
	seol = '\r',	/* End of packet marker for sending		*/
	rctlq = '#',	/* Control character quote char for receiving	*/
	filnam[50],	/* Current file name				*/
	ssc,		/* Start server command				*/
	*sndpkt,	/* Send packet.					*/
	*rcvpkt,	/* Receieve packet.				*/
	*data,		/* Data to send/receive before encode/decode	*/
	*rdatap,	/* Pointer to null-terminated data field	*/
	*isp = NULL,	/* Pointer to characters in memory		*/
	*osp = NULL;	/* Output string pointer			*/

/*
 * Forward declarations.  Soon to be prototypes if the ANSI standard
 * committee keeps its promises.
 */
int input(), spack(), ack();
char *rpar();

/*
 * External routines provided.
 */

extern int ttinl(), ttol(), gnfile();
#ifdef XPRKERMIT
extern void ttflui(), tchar(), sleep();
#else
extern void ttflui(), tchar(), tmsg(), tmsgl(), sleep();
#endif
#define ERR(s) error(s); RESUME
#define RESUME return

/*lint -save -e525 -e527	We don't care how Wart formats!		*/

%states ssfil ssdat sseot
%states srini srfil srdat
%states sipkt srgen
%%

 /* Start states */

s {					/* - Start State - */
    tinit();				/* Initialize transaction */
    if (sinit('S') < 0) { ERR("sinit"); }	/* Build, send Send-Init. */
    else {				/* If successful, */
	filcnt = 0;			/* initialize file counter */
	BEGIN ssfil;			/* and switch to ssfil state. */
    }
}
v { tinit(); rinit(); BEGIN srini; }	/* - Receive - */

r {					/* Get */
    tinit(); ssc = 0;
    if (sinit('I') < 0) { ERR("sinit"); }
    else BEGIN sipkt;
}

c {					/* Host */
    tinit(); ssc = 'C';
    if (sinit('I') < 0) { ERR("sinit"); }
    else BEGIN sipkt;
}

g {					/* Generic */
    tinit(); ssc = 'G';
    if (sinit('I') < 0) { ERR("sinit"); }
    else BEGIN sipkt;
}

a {					/* Immediate protocol abort */
    spack('E', seq, 21, "User aborted protocol");
    closif();  closof(1);		/* Close files, deleting output */
    RESUME;
}

/* Dynamic states, sending file(s) */

<ssfil>Y {				/* - Send File State - */
    if (filcnt++ == 0) spar(rdatap);	/* Set parameters if 1st time */
    cx = 0;				/* Reset file interruption flag */
    bctu = bctr;			/* Switch to negotiated block check */
    resetc();				/* Reset global counters. */
#ifdef XPRKERMIT
    ST_Display_CRC(bctu);
#endif
    /* Is there a file to send in an uncancelled batch? */
    if (!cz && gnfile(filnam, sizeof(filnam)) > 0) {
	if (sfile() < 0) { ERR("sfile"); }	/* Yes, open it, send F packet */
	else BEGIN ssdat;		/* and if no error, switch state. */
    } else {				/* No (more) files to send */
	if (seot() < 0) { ERR("seot"); }	/* so send B packet */
	else BEGIN sseot;		/* and switch to sseot state. */
    }
}
<ssdat>Y {				/* - Send Data State - */
    int x;
    if (rln == 1 && *rdatap == 'X')	/* Did ACK contain X as data? */
	cx = 1;				/* Yes, set control-x flag. */
    else if (rln == 1 && *rdatap == 'Z') /* Did ACK contain Z as data? */
	cz = 1;				/* Yes set control-z flag. */
    /* Check here for cancellation of transfer and data left to send. */
    if (cx || cz || (x = sdata()) == 0) {
	if (seof((cx | cz) ? "D" : "") < 0) {	/* If not, send Z packet. */
	    ERR("seof");
	}
	else BEGIN ssfil;		/* and go back to ssfil state. */
    } else if (x < 0) { ERR("sdata"); }	/* Handle file i/o errors. */
}
<sseot>Y { RESUME; }			/* - Send B, done. */

/* Dynamic states, receiving file(s) */

<srini,srgen>S {
    resetc();
    spar(rdatap);
    (void) ack1(rpar());
    bctu = bctr;
    BEGIN srfil;
}

<srfil>B { (void) ack(); RESUME; }

<srfil>F { if (rcvfil() < 0) { ERR("rcvfil"); } else { (void) ack(); BEGIN srdat; } }

<srdat>D {
   if (cx)
	ack1("X");
   else if (cz)
	ack1("Z");
   else {
	if (decode() < 0) { ERR("decode"); } else (void) ack();
   }
}

<srdat>Z {
    /* Discard output file if the sender tells us so. */
    if (closof(cx || cz || (rln == 1 && *rdatap == 'D')) < 0) {
	ERR("error closing file");
    } else {
	(void) ack(); BEGIN srfil;
    }
}

/* Dynamic states, server mode */

<sipkt>Y {			/* Got ACK for I packet */
    spar(rdatap);		/* Set parameters from it */
    start = 'E';		/* Force entry into next state */
}

<sipkt>E {			/* Got E for I packet */
    if (ssc) {
	if (scmd(ssc,cmarg) < 0) { ERR("scmd"); }
	else BEGIN srgen;
    } else {
	if (scmd('R',cmarg) < 0) { ERR("scmd"); }
	else BEGIN srini;
    }
}

<srgen>Y { xflag = 1; decode(); RESUME; }

<srgen,srfil>X { xflag = 1; ack(); BEGIN srdat; }

/* Error state */

E { error(rdatap);
    (void) closif();
    (void) closof(1);		/* close files, discarding output */
    RESUME; }
    
. { error("Unexpected packet type"); RESUME; }
				/* Handle unwanted packet types. */
%%

/*lint -restore */

static
int
input() {				/* Return packets	*/
   int type, try;

    if (start != 0) {			/* Start state in effect? */
	type = start;			/* Yes, call it a packet type, */
	start = 0;			/* nullify the start state, */
	return(type);			/* and return the type. */
    }
    type = rpack();			/* No start state, read a packet. */
    for (try = 0; rsn != seq || strchr("TQN",type); try++) {
	if (try > limit) {		/* If too mahy tries, */
	    strcpy(data, "Timed out");	/* give up */
	    rdatap = data;		/* Make up pretend 'E' packet */
	    return('E');		/* and return it */
	}
	if (type == 'N' && rsn == ((seq + 1) & 63)) {
					/* NAK for next packet */
	    return('Y');		/* is ACK for current. */
	} else {			/* Otherwise, */
	    (void) resend();		/* resend previous packet. */
	}
	type = rpack();			/* Try to read response. */
    }
    ttflui();				/* Got a good one, clear buffer. */
    return(type);			/* Return its type. */
}

static
nxtpkt() {
    seq = (seq + 1) & 63;		/* Next packet number, mod 64 */
}

/*  R E S E T C  --  Reset per-transaction character counters */
resetc() {
    tfc = tlci = tlco = 0;	/* Total file chars, line chars in & out */
}

static
tinit() {				/* Transaction initialization */
    seq = 0;				/* Start off with packet 0 */
    ebqflg = 0;				/* 8-bit quoting off */
    sq = 'Y';				/* Normal 8-bit quote bid */
    rqf = -1;				/* Flag other's bid not yet seen */
    rptflg = 0;				/* No repeat counts by default */
    bctu = 1;				/* Block check to use back to 1 */
    xflag = 0;				/* Output normally to file */
    osp = NULL;				/* ... */
    *filnam = *sndpkt = *rcvpkt = '\0';  /* Clear string buffers */
}

static
error(s) char *s; {			/* Fatal error */
    if (local) {			/* If in local mode */
	screen(SCR_EM,0,0L,s);		/* Type message on console */
    } else {				/* Otherwise, */
	(void) spack('E',seq,strlen(s),s); /* Send in error packet. */
    }
    return;
}

static
ack() {
    int x;				/* Empty acknowledgement */
    x = spack('Y',seq,0,"");		/* Send the packet */
    nxtpkt();				/* Increment the packet number */
    return(x);
}

static
ack1(s) char *s; {
    int x;				/* Acknowledgement with data */
    x = spack('Y',seq,strlen(s),s);	/* Send the packet */
    nxtpkt();				/* Increment packet number */
    return(x);
}

static
nak() {					/* Negative acknowledgement */
    return(spack('N',seq,0,""));	/* Never has data! */
}

/* Functions used by file sender. */

/* sinit()--start the transaction by filling in the initialization string
 * and sending it in an S packet.
 */

static
sinit(c) char c; {
    char *s;
    s = rpar();
    if (local == 0 && c == 'S' && server == 0) {
#ifdef XPRKERMIT
	ST_Display_String(STMsg,"Escape back to local system, RECEIVE command");
#else
	tmsgl("Escape back to local system, give RECEIVE command...");
#endif
	sleep(delay);
    }
    return(spack(c,seq,strlen(s),s));
}

/*
 * scmd() -- send a preformatted Kermit server command string.
 */
static
scmd(t, s) char t, *s; {	/* Send a packet of the given type */
    encstr(s);			/* Encode the command string */
    spack(t,seq,size,data);
}

/* rinit() -- do whatever is needed to initialize receive.  Now a no-op.
 */
static
rinit()
{
}

/* sfile() -- open the file and send the File-Header packet.  Assumes that
 * the global string pointer filnam references the file name.
 */

static
sfile() {
    int x;
    char pktnam[50];
    if (zopeni(filnam) < 0)		/* Try to open file. */
	return -1;
    zltor(filnam,pktnam);		/* OK, convert name. */
    x = encstr(pktnam);			/* Encode the result */
    if (local) {			/* If in local mode, */
#ifdef XPRKERMIT
	ST_Display_String(STFile, pktnam);
	ST_Display_String(STUplSize, filnam);
#else
	tmsg("Sending ");		/* let user know we're */
	tmsg(filnam);			/* sending this file */
	tmsg(" as ");			/* under */
	tmsgl(pktnam);			/* this name */
#endif
    }
    first = 1;				/* Flag beginning of file */
    ffc = flci = flco = 0;		/* Zero per-file char count */
    maxsiz = spsiz - (bctr + 3);	/* Maximum data length */
    nxtpkt();				/* Increment packet number */
    return(spack((xpkt ? 'X' : 'F'),seq,x,data)); /* Send packet */
}

/* sdata() -- get next packet's worth of data */

static
sdata() {
    int x;
    if ((x = getpkt(maxsiz)) == 0)	/* If no data left to send, */
	return(0);			/* return EOF indication. */
    nxtpkt();
    return(spack('D',seq,x,data));	/* Send the data packet */
}

/* seof -- close the input file and send a Z packet. */

static
seof(s) char *s; {
    if (closif() < 0)			/* Try to close the file. */
	return -1;			/* On error, return failure. */
    else {				/* Otherwise, */
#ifdef XPRKERMIT
	if (local) ST_Display_String(STMsg,"OK");
#else
	if (local) tmsgl("OK");		/* if local, reassure user. */
#endif
	nxtpkt();
	return(spack('Z',seq,strlen(s),s));	/* Send Z packet */
    }
}

/* seot -- send B packet. */

static
seot() {
    nxtpkt();
#ifdef XPRKERMIT
    if (local) ST_Display_String(STMsg,"Done");
#else
    if (local) tmsgl("Done");
#endif
    return(spack('B',seq,0,""));
}

static
rcvfil() {				/* Receive a file */
    char myname[50];

    ffc = flci = flco = 0;		/* Initialize per-file char count */
    decstr(filnam);			/* Decode name */
    zrtol(filnam,myname,warn);		/* Convert to local form. */
    if (zopeno(myname) < 0)
	return -1;
    else {				/* File open OK, give message. */
	if (local && !xflag) {
#ifdef XPRKERMIT
	    ST_Display_String(STFile,myname);
	    ST_Display_CRC(bctu);
#else
	    tmsg("Receiving "); tmsg(filnam); tmsg(" as "); tmsgl(myname);
#endif
	}
	return 0;
    }
}

static
closof(nokeep) int nokeep; {		/* Close output file, but */
    if (xflag) return 0;		/* not if it's the screen. */
    if (zcloso(nokeep) < 0)
	return -1;
    return 0;
}

static
spack(type,n,len,d) char type, *d; int n, len; {
    int i = 0, j, k;
    char *sohp;				/* Mark start of packet data. */

    for (i = 0; i < spadn; i++)
	sndpkt[i] = spadc;		/* Do requested padding */
    sohp = sndpkt + i;
    sndpkt[i++] = smark;		/* Packet mark */
    k = i++;				/* Remember this place */
    sndpkt[i++] = tochar(n);		/* Sequence number */
    sndpkt[i++] = type;			/* Packet type */
    j = len + bctu;			/* True length */
    if (j > 95) {			/* Long packet? */
	sndpkt[k] = tochar(0);		/* Set LEN to 0 */
	sndpkt[i++] = tochar(j / 95);	/* High part of length */
	sndpkt[i++] = tochar(j % 95);	/* Low part of length */
	sndpkt[i] = '\0';		/* Header checksum */
	sndpkt[i++] = tochar(chk1(sndpkt+k));
    } else
	sndpkt[k] = tochar(j+2);	/* True length. */

    for (j = len; j > 0; j--) {		/* Data */
	sndpkt[i++] = *d++;
    }
    sndpkt[i] = '\0';			/* Null terminate. */
    switch (bctu) {
	case 1:				/* Type 1 - 6 bit checksum */
	    sndpkt[i++] = tochar(chk1(sndpkt+k));
	    break;
	case 2:				/* Type 2 - 12 bit checksum */
	    j = chksum(sndpkt+k);
	    sndpkt[i++] = tochar((j >> 6) & 077);
	    sndpkt[i++] = tochar(j & 077);
	    break;
	case 3:				/* Type 3 - 16 bit CRC-CCITT */
	    j = chk3(sndpkt + k);
	    sndpkt[i++] = tochar((j >> 12) & 017);
	    sndpkt[i++] = tochar((j >> 6) & 077);
	    sndpkt[i++] = tochar(j & 077);
	    break;
	}
    sndpkt[i++] = seol;			/* End of line */
    sndpkt[i] = '\0';			/* Null string-terminat	or. */
    sndpkl = i;				/* Remember length. */
    i = ttol(sndpkt,sndpkl);		/* Send the packet. */
    tlco += sndpkl;			/* Count characters output. */
    flco += sndpkl;
    screen(SCR_PT, type, (long) n, sohp);
    return(i);
}

static
resend() {
    int x;
    if (*sndpkt)
	x = ttol(sndpkt,sndpkl);	/* Send previous packet */
    else
	x = nak();			/* or NAK if none */
    if (local && !xflag) tchar('%');	/* Let the user know. */
    return(x);
}

chk1(packet) char *packet; {		/* Compute Kermit's */
    int s, t;				/* 1-character block check. */
    s = chksum(packet);			/* Get the arithmetic sum. */
    t = (((s & 192) >> 6) + s) & 63;	/* Fold it into 6 bits. */
    return(t);
}

static
chksum(p) char *p; {			/* Compute the checksum. */
    unsigned m;
    long s;

    m = (parity) ? 0177 : 0377;		/* Mask for parity bit.	*/
    s = 0;
    for (; *p != '\0'; p++)		/* For each character, */
	s += *p & m;			/* accumulate the sum, */
    return(s & 07777);			/* and then return it. */
}

/*
 * rpack reads a packet and returns the packet type, or else Q if the
 * packet was invalid, or T if a timeout occured.   Upon successful return,
 * sets the global variables:
 *	rsn	- the received sequence number
 *	rln	- length of the received data field
 *	rdatap	- a pointer to the null-terminated contents of the data field
 */
static
rpack() {
    int i, j, x, type, rlnpos;
    char pbc[4];			/* Packet block check. */
    char *sohp;				/* Start of packet data. */

    rsn = rln = -1;			/* In case of failure. */

    *rcvpkt = '\0';			/* Initialize receive buffer. */
    j = ttinl(rcvpkt,MAXRP,reol,stimo); /* Try to get a "line". */
    if (j < 0) return('T');		/* Timed out. */

    tlci += j;				/* Count received characters. */
    flci += j;				/* On per-file basis. */
    for (i = 0; rcvpkt[i] != rmark && (i < j); i++)	/* Find mark. */
	;
    if (i == j) return('Q');		/* If no mark, bad packet. */
    sohp = rcvpkt+i;

    rlnpos = ++i;			/* Got it, remember position. */
    if ((j = unchar(rcvpkt[i++])) == 0) { /* Long packet? */
    	j = rlnpos + 5;			/* Yes, check header */
    	if (j > MAXRP) return('Q');	/* Be defensive. */
    	x = rcvpkt[j];			/* Remember header checksum */
    	rcvpkt[j] = '\0';
    	if (unchar(x) != chk1(rcvpkt+rlnpos))	/* Check header */
	    return('Q');
	rcvpkt[j] = x;			/* Restore packet */
	rln = unchar(rcvpkt[j-2]) * 95 + unchar(rcvpkt[j-1]) - bctu;
	j = 3;
    } else {
	rln = j - bctu - 2;		/* Regular packet */
	j = 0;				/* No extended header */
    }
    rsn = unchar(rcvpkt[i++]);		/* Sequence number. */
    type = rcvpkt[i++];			/* Packet type */
    i += j;				/* Skip extended header, if any */
    rdatap = rcvpkt + i;		/* The data itself. */
    j = rln + i;			/* Position of block check. */
    if (j > MAXRP)
	return('Q');			/* Be defensive! */
    for (x = 0; x < bctu; x++)		/* Copy the block check. */
	pbc[x] = rcvpkt[j+x];
    rcvpkt[j] = '\0';
    switch (bctu) {			/* Which block check type? */
	case 1:
	    if (unchar(*pbc) != chk1(rcvpkt+rlnpos)) return('Q');
	    break;
	case 2:
	    x = unchar(*pbc) << 6 | unchar(pbc[1]);
	    if (x != chksum(rcvpkt+rlnpos)) return('Q');
	case 3:
	    x = unchar(*pbc) << 12 | unchar(pbc[1]) << 6 | unchar(pbc[2]);
	    if (x != chk3(rcvpkt+rlnpos)) return('Q');
	    break;
	default:
	    error("Impossible block check type.");
    }
    screen(SCR_PT, type, (long) rsn, sohp);
    return type;			/* Otherwise, return packet type */
}

/*
 * CHK3
 * Calculate the 16-bit CRC of a null-terminated string using a
 * byte-oriented tableless algorithm devised by Andy Lowry (Columbia
 * University).  The magic number 010201 is derived from the CRC-CCITT
 * polynomial x^16+x^12+x^5+1.
 */
static
chk3(s) char *s; {
    unsigned int c, q;
    long crc = 0;

    while ((c = *s++) != '\0') {
	if (parity) c &= 0177;
	q = (crc ^ c) & 017;		/* Low order nybble */
	crc = (crc >> 4) ^ (q * 010201);
	q = (crc ^ (c >> 4)) & 017;	/* High order nybble */
	crc = (crc >> 4) ^ (q * 010201);
    }
    return(crc);
}

/*
 * getpkt--Fill a packet to the maximum.  Result goes in local data array
 * whose current length is indicated in global size.
 */
static
getpkt(maxlen) int maxlen; {
    int i, next;
    static int c;
    static char remain[6] = {'\0', '\0', '\0', '\0', '\0', '\0'};
    void encode();
    
    if (first == 1) {			/* If first time thru... */
	first = 0;			/* remember not to do this next time, */
	remain[0] = '\0';		/* discard any old leftovers, */
	c = gnchar();			/* get first character of file */
	if (c < 0) {			/* watching out for null file */
	    first = -1;
	    return(size = 0);
	}
    } else if (first == -1) {		/* EOF from last time? */
	return(size = 0);
    }
	

/* Copy any leftovers that didn't fit in the last packet. */

    for (size = 0; (data[size] = remain[size]) != '\0'; size++)
	;
    *remain = '\0';

/* Get, encode, and deposit the next character. */

    rpt = 0;				/* Initialize repeat counter. */
    
    while (first > -1) {		/* Until end of file or string... */
	next = gnchar();		/* Look ahead one character */
	if (next < 0) first = -1;	/* If none, we're at EOF. */
	osize = size;			/* Remember current size. */
	encode(c, next);		/* Encode the character. */
	c = next;			/* Old next char is now current. */
	if (size == maxlen) return(size); /* If just at end, done. */
	if (size > maxlen) {		/* Past end, must save some. */
	    for (i = 0; (remain[i] = data[osize+i]) != '\0'; i++)
		;
	    size = osize;
	    data[size] = '\0';
	    return(size);		/* Return size. */
	}
    }
    return(size);			/* EOF, return size. */
}

static
gnchar() {
   int c;

    if (isp) {				/* From string in memory */
	return((c = *isp++) > 0 ? c : -1);
    } else {
	if ((c = zgetc(text)) > 0) {	/* or from a file. */
	    ffc++; tfc++;
	}
	return c;
    }
}

/*
 * Encodes the character a into the global data array,
 * and the global size is updated.
 * Global sctlq is the control prefix for sending data.
 *
 * The first argument is spec'd as a char in the Kermit book but must
 * actually be an int;  otherwise, it is sign extended and the comparison
 * if (a == next) comes up TRUE if the last character in the file has the
 * value 255 and the EOF flag (which is what next's value will be in this
 * case) is -1.  This results in that last 255 not being sent.
 */
static void
encode(a, next) int a; int next; {
    int a7, b8;

    if (rptflg) {			/* Doing run-length encoding? */
	if (a == next) {		/* Yes, got a run? */
	    if (++rpt < 94) {		/* Yes count. */
		return;
	    } else if (rpt == 94) {	/* If at maximum */
		data[size++] = rptq;	/* Emit prefix, */
		data[size++] = tochar(rpt); /* and count, */
		rpt = 0;		/* and reset counter. */
	    }
	} else if (rpt == 1) {		/* Run broken, only two? */
	    rpt = 0;			/* Yes, do the character wice */
	    encode(a,-1);		/* by calling self recursively. */
	    if (size <= maxsiz) osize = size; /* Watch for boundary */
	    rpt = 0;			/* Call self second time. */
	    encode(a,-1);
	    return;
	} else if (rpt > 1) {		/* Run broken, more than two? */
	    data[size++] = rptq;	/* Yes, emit prefix and count */
	    data[size++] = tochar(++rpt);
	    rpt = 0;			/* and reset counter. */
	}
    }
    a7 = a & 127;			/* Isolate low 7 bits */
    b8 = a & 128;			/* And "parity" bit */

    if (ebqflg && b8) {			/* If doing 8th-bit prefixing */
	data[size++] = ebq;		/* and 8th bit on, insert prefix */
	a = a7;				/* and clear the 8th bit. */
    }
    if (a7 < 32 || a7 == 127) {		/* If control character */
	data[size++] = sctlq;		/* insert control quote */
	a = ctl(a);			/* and make printable. */
    } else if (a7 == sctlq)		/* If data is control prefix, */
	data[size++] = sctlq;		/* prefix it. */
    else if (ebqflg && a7 == ebq)	/* If doing 8th-bit prefixing, */
	data[size++] = sctlq;		/* ditto for 8th-bit prefix. */
    else if (rptflg && a7 == rptq)	/* If doing run-length encoding, */
	data[size++] = sctlq;		/* ditto for repeat prefix. */

    data[size++] = a;			/* Finally, emit the character. */
    data[size] = '\0';			/* Terminate string. */
}

/*
 * Decodes the data pointed to by the global pointer rdatap.
 */
static
decode() {
    int a, a7, b8;

    while ((a = *rdatap++) != '\0') {
	rpt = 1;			/* Initialize repeat count. */
	if (rptflg) {			/* Repeat processing? */
	    if (a == rptq) {		/* Yes, have repat prefix? */
		rpt = unchar(*rdatap++); /* Yes, get count. */
		a = *rdatap++;		/* and following character. */
	    }
	}
	b8 = 0;				/* Assume 8th bit not on. */
	if (ebqflg) {			/* Doing 8th-bit prefixing? */
	    if (a == ebq) {		/* Yes, have 8th-bit prefix? */
		b8 = 128;		/* Yes, remember bit 8 on */
		a = *rdatap++;		/* and get following character. */
	    }
	}
	if (a == rctlq) {		/* Is it control prefix? */
	    a = *rdatap++;		/* Yes, get next character */
	    a7 = a & 127;		/* and its low 7 bits. */
	    if (a7 > 62 && a7 < 96)	/* Encoded control character? */
	        a = ctl(a);		/* Yes, controllify */
	}
	a |= b8;			/* OR in the 8th bit. */
	for (; rpt > 0; rpt--) {
	    if (pnchar(a) < 0) return -1; /* Output the character. */
	    ffc++; tfc++;		  /* Update character counts. */
	}
    }
    tflush();				/* Flush out message (if xflag) */
    return(0);
}

static
pnchar(c) int c; {			/* Put next character. */
    if (xflag) {			/* To screen if desired. */
	tchar(c);
	return(1);
    } else if (osp) {			/* Or to string in memory... */
	*osp++ = c;
	return(1);
    } else return(zputc(c,text));	/* Otherwise to file. */
}

static
encstr(s) char *s; {			/* Fill a packet from the string */
    first = 1;				/* Start lookahead. */
    isp = s;				/* Set input string pointer */
    (void) getpkt(spsiz);		/* Fill a packet */
    isp = NULL;				/* Reset input string pointer */
    return(size);			/* Return data field length */
}

static
decstr(s) char *s; {			/* Decode packet data into a string */
    osp = s;				/* Set output string pointer */
    (void) decode();			/* Decode the string */
    *osp = '\0';			/* Terminate the string */
    osp = NULL;				/* Reset output string pointer */
}

static
spar(s) char *s; {			/* Set parameters */
    int x;

    s--;				/* Line up with field numbers. */
    
    /* Limit on size of outbound packets */
    x = (rln >= 1) ? unchar(s[1]) : 80;
    spsiz = (x < 10) ? 80 : x;

    /* Timeout on inbound packets */
    x = (rln >= 2) ? unchar(s[2]) : 5;
    stimo = (x < 0) ? 5 : x;

    
    /* Outbound padding */
    spadn = 0; spadc = '\0';
    if (rln >= 3) {
	spadn = unchar(s[3]);
	if (rln >= 4)
	    spadc = ctl(s[4]);
    }
    
    /* Outbound packet terminator */
    seol = (rln >= 5) ? unchar(s[5]) : '\r';
    if (seol < 2 || seol > 31) seol = '\r';
    
    /* Control prefix */
    x = (rln >= 6) ? s[6] : '#';
    rctlq = ((x > 32 && x < 63) || (x > 95 && x < 127)) ? x : '#';
    
    /* 8th-bit quoting */
    rq = (rln >= 7) ? s[7] : 0;
    if (rq == 'Y') rqf = 1;
    else if ((rq > 32 && rq < 63) || (rq > 95 && rq < 127)) rqf = 2;
    else rqf = 0;
    
    switch (rqf) {
	case 0: ebqflg = 0; break;
	case 1: if (parity) { ebqflg = 1; ebq = '&'; } break;
	case 2: if (ebqflg = (ebq == sq || sq == 'Y')) ebq = rq;
    }

    /* Block check */
    x = 1;
    if (rln >= 8) {
	x = s[8] - '0';
	if (x < 1 || x > 3) x = 1;
    }
    bctr = x;

    /* Repeat prefix */
    if (rln >= 9) {
	rptq = s[9];
	rptflg = ((rptq > 32 && rptq < 63) || (rptq > 95 && rptq < 127));
    } else rptflg = 0;

    /* Capabilities */
    atcapu = lpcapu = swcapu = 0;	/* No capabilities by default	*/
    if (rln >= 10) {
	x = unchar(s[10]);
	atcapu = (x & atcapb) && atcapr;	/* Attribute packets */
	lpcapu = (x & lpcapb) && lpcapr;	/* Long packets */
	swcapu = (x & swcapb) && swcapr;	/* Sliding windows */
	for (capas = 10; (unchar(s[capas]) & 1) && (rln >= capas); capas++)
	    ;					/* Skip to capas + 1 */
    }

    /* Long packets */
    if (lpcapu) {			/* Flag set above */
	if (rln > capas+2) {
	    x = unchar(s[capas+2]) * 95 + unchar(s[capas+3]);
	    spsiz = x > MAXSP ? MAXSP : x;
	}
	/* else a fatal error, but how do we terminate? */
    }

    /* Sliding windows */
    if (swcapu) {
	if (rln > capas+1) {
	    x = unchar(s[capas+1]);
	    wsize = x > MAXWS ? MAXWS : x;
	} else
	    wsize = 1;
    }
}

/* Fill the array with my send-init parameters */

static char *
rpar() {
    data[1] = tochar(DRPSIZ);		/* Biggest packet I can receive */
    data[2] = tochar(rtimo);		/* When I want to be timed out */
    data[3] = tochar(rpadn);		/* How much padding I need */
    data[4] = ctl(rpadc);		/* Padding character I want */
    data[5] = tochar(reol);		/* End-of-Line character I want */
    data[6] = sctlq;			/* Control-Quote character I send */
    switch(rqf) {			/* 8th-bit prefix */
	case -1:
	case  1: if (parity) ebq = sq = '&'; break;
	case  0:
	case  2: break;
    }
    data[7] = sq;
    data[8] = bctr + '0';		/* Block Check Type */
    if (rptflg) data[9] = rptq; else data[9] = '~';
    data[10] = tochar(atcapr?atcapb:0 | lpcapr?lpcapb:0 | swcapr?swcapb:0);
    capas = 10;
    data[capas+1] = tochar(swcapr ? wsize : 0);	/* Window size */
    data[capas+2] = tochar(rpsiz / 95);	/* Long packet size */
    data[capas+3] = tochar(rpsiz % 95);	/* ... */
    data[capas+4] = '\0';
    return(data+1);			/* Return a pointer to the string */
}

/*
 * proto()--Kermit protocol entry point.  Set your start state and call
 * this, NOT wart().  Modified to set long packets capability on the
 * basis of the packet size set in the external user interface.
 */
void
proto()
{
    void *malloc();
#ifdef XPRKERMIT
    struct XPR_UPDATE xpru;

    xpru.xpru_protocol = "XPR-Kermit";
    xpru.xpru_updatemask = XPRU_PROTOCOL;
    calla(xupdate, &xpru);
    zclear();
#endif
    sndpkt = (char *) malloc((unsigned) (MAXSP+100));
    rcvpkt = (char *) malloc((unsigned) (MAXRP+200));
    data = (char *) malloc((unsigned) (MAXSP+4));
    if (urpsiz > 94) {				/* Long packets? */
	rpsiz = (urpsiz > MAXRP - 20 ? MAXRP - 20 : urpsiz);
	lpcapr = 1;				/* Request long packets */
    } else {					/* No long packets */
	lpcapr = 0;
	if (urpsiz < 10)			/* Too small?	*/
	    rpsiz = 80;
	else
	    rpsiz = DRPSIZ;
    }
    urpsiz = rpsiz;
    cx = cz = 0;				/* Haven't aborted yet */
    if (bctr < 1)
	bctr = 1;			/* Legal block check? */
    if (bctr > 3)
	bctr = 3;
    if (sndpkt == NULL || rcvpkt == NULL || data == NULL)
#ifdef XPRKERMIT
	ST_Display_String(STMsg,"Can't allocate memory");
#else
	tmsgl("Can't allocate memory for Kermit!!");
#endif
    else
	wart();
    if (sndpkt) free(sndpkt);
    if (rcvpkt) free(rcvpkt);
    if (data) free(data);
}
    
/*
 * That ends the system-independent Kermit modules.  What follows
 * are the system-dependent ones.
 */

/*
 * Now for the file routines.  I chose to use the z...() routines
 * written in terms of stdio.
 */
#ifndef XPRKERMIT
#include <stdio.h>
#else
typedef struct {
	int dummy;
} FILE;
#define EOF -1
#endif

static FILE *ifp = NULL, *ofp = NULL;

#ifdef XPRKERMIT
static 
zclear()
{
    ifp = NULL; ofp = NULL;
}
#endif

static
zopeni(name) char *name; {
#ifdef XPRKERMIT
    ifp = (FILE *) callaa(xfopen, name, "r");
#else
    ifp = fopen(name, "r");
#endif
    if (ifp == NULL)
	return -1;
    else
	return 0;
}

static
zopeno(name) char *name; {
#ifdef XPRKERMIT
    ofp = (FILE *) callaa(xfopen, name, "w");
#else
    ofp = fopen(name, "w");
#endif
    if (ofp == NULL)
	return -1;
    else
	return 0;
}

static
zclosi() {
    int x;

    if (ifp == NULL)
	return 0;
#ifdef XPRKERMIT
    x = calla(xfclose, ifp);
#else
    x = fclose(ifp);
#endif
    ifp = NULL;
    if (x < 0)
	return -1;
    else
	return 0;
}

static
zcloso(discard) int discard; {
    int x;

    if (ofp == NULL)
	return 0;
#ifdef XPRKERMIT
    x = calla(xfclose, ofp);
#else
    x = fclose(ofp);
#endif
    ofp = NULL;
    if (x < 0)
	return -1;
#if 0
    else if (discard)
	if (unlink(filnam) < 0)
		return -1;
#endif
    return 0;
}

#include <ctype.h>

extern int convert;		/* 0 for literal files, 1 for translate */

/* name from remote to local format */
static
zrtol(s1,s2,warn) char *s1, *s2; int warn; {
    strcpy(s2,s1);		/* for now */
    if (convert)
	while (*s2 != '\0') {
	    if (isupper(*s2))
		*s2 = tolower(*s2);
	    s2++;
	}
}

/* name from local to remote format */

static
zltor(s1,s2) char *s1, *s2; {
#ifdef XPRKERMIT
    char *EndPath();
#endif
    char dotseen = 0;

    if (!convert)
	strcpy(s2, s1);
    else {
#ifdef XPRKERMIT
	s1 = EndPath(s1);	/* strip dir/disk */
#endif
	while (*s1 != '\0') {
	    if (islower(*s1))
		*s2 = toupper(*s1);
	    else if (isalnum(*s1))
		*s2 = *s1;
	    else if (*s1 == '.')
		if (!dotseen) {
		    dotseen = 1;
		    *s2 = *s1;
		} else
		    *s2 = 'X';
	    /* else a character we're not prepared to handle. */
	    s1++; s2++;
    	}
	*s2 = '\0';
    }
}

/*
 * System-dependent function to return next character from file.
 * If the text flag argument is nonzero, first convert to canonic form. 
 */
static
zgetc(text) {				/* Get next char from file. */
#define MAXREC 100			/* Size of record buffer. */

    static char recbuf[MAXREC + 1];	/* Big enough for MAXREC newlines */
    static char *rbp;			/* Buffer pointer */
    static int i = 0;			/* Buffer char counter */
    int c;

    if (i == 0) {			/* If the buffer is empty, */
					/* read next line from file. */
	for (i = 0;
	     i < MAXREC - 1 && (c = getc(ifp)) != EOF && c != '\n';
	     i++)
	    recbuf[i] = c;
	if (c == '\n') {		/* Got newline		*/
	    if (text) {			/* If in text mode	*/
		recbuf[i++] = '\r';	/* Substitute CRLF	*/
	    }
	    recbuf[i++] = c;
	}
	recbuf[i] = '\0';		/* Done, terminate buffer */
	if (i == 0) return -1;		/* If empty, indicate EOF */
	rbp = recbuf;			/* Remember position for next time */
    }
    i--;				/* Adjust the counter. */
    return(*rbp++ & 0377);		/* Return hext character */
}

static
zputc(c, text) int c, text; {		/* Put character in file. */
    unsigned int x;

    c &= 255;				/* Undo any sign extension */
    if (text && c == '\r')		/* If in text mode, */
    	return 0;			/* eliminate carriage returns. */
    else {				/* Otherwise, */
	x = putc(c, ofp) & 255;		/* output the character. */
	if (c == 255) return 0;		/* Special handling for all 1's */
	return ((x != c) ? -1 : 0);	/* Normal return code. */
    }
}

#ifdef XPRKERMIT
int getc(ifp)
FILE *ifp;
{
	unsigned char buf;
	long status;

	status = calladda(xfread, &buf, 1L, 1L, ifp);
	if (status == 0) return (EOF);
	else return((int) buf);
}

putc(c, ofp)
int c;
FILE *ofp;
{
	int	status;
	char cl;

	cl = c;			/* useful ??? */
	status = calladda(xfwrite, &cl, 1L, 1L, ofp);
	if (status == 0) return (EOF);
	else return(c);
}

int ttol(s, n)
char *s;
int n;
{
	long status;

	status = callad(xswrite, s, (long) n);
	if (status == 0) return(n);
	else return(-1);
}

int ttinl(s, max, eol, timeout)
char *s;
int max, timeout, eol;
{
	int x = 0;
	char c;

	*s = '\0';
	/*
	 * Willy Langeveld assures me that, since serial.device does its
	 * own buffering, callling xsread once for each character isn't
	 * going to take too long.
	 */
	for (x = 0; x < max; x++) {
		if (calladd(xsread, &c, 1L, (long) (timeout * 1000000L)) < 0) {
			return -1;
		} else if ((s[x] = parity ? c & 127 : c) == eol) {
			break;
		} else if (xchkabort()) {
			cz = cx = brkflag = 1;
			break;
		}
	}
	s[x] = '\0';
	return(x);
#endif
}

void ttflui()
{
	xsflush();
}

void sleep(sec)
int sec;
{
	if (sec) TimeOut((long)sec);
}

#define MAXMESSAGE 50
static char message[MAXMESSAGE+1];
static int nmess = 0;
static long errors = 0, blocks = 0;

/*  screen(f,c,n,s)
      f - argument descriptor
      c - a character or small integer
      n - a long integer
      s - a string.
 Fill in this routine with the appropriate display update for the system.
 This version for XPR Kermit.
*/

screen(f,c,n,s) int f; long n; char c; char *s; {
    struct XPR_UPDATE xpru;

    if (!local || xflag) return;
    switch (f) {
    case SCR_PT:			/* Packet Transferred. */
	if (c == 'Y') return;		/* Don't bother with ACK's. */
	xpru.xpru_packettype = c;
	xpru.xpru_blocksize = strlen(s);
	xpru.xpru_blocks = ++blocks;
	xpru.xpru_bytes = ffc;
	xpru.xpru_updatemask = XPRU_PACKETTYPE | XPRU_BLOCKSIZE |
		XPRU_BLOCKS | XPRU_BYTES;
	break;
    case SCR_EM:			/* Error message. */
	xpru.xpru_errormsg = s;
	xpru.xpru_updatemask = XPRU_ERRORMSG;
	break;
    default:
	return;
    }
    calla(xupdate, &xpru);
}

void tchar(c)
char c;
{
	struct XPR_UPDATE xpru;

	if (c == '%') {		/* retry */
		xpru.xpru_errors = ++errors;
		xpru.xpru_updatemask = XPRU_ERRORS;
	} else if (c == '#') { /* fake it */
		xpru.xpru_errors = errors = 0;
		xpru.xpru_blocks = blocks = 0;
		nmess = 0;			/* Make sure message empty. */
		xpru.xpru_updatemask = XPRU_BLOCKS | XPRU_ERRORS;
	} else {
		message[nmess++] = c;
		if (nmess >= MAXMESSAGE)
			tflush();
		return;
	}
	calla(xupdate, &xpru);
}

static
tflush()
{
	struct XPR_UPDATE xpru;

	if (nmess <= 0)
		return;
	message[nmess] = '\0';
	xpru.xpru_errormsg = message;
	xpru.xpru_updatemask = XPRU_ERRORMSG;
	calla(xupdate, &xpru);
	nmess = 0;
}

ST_Display_String(Item, S)
int Item;
char *S;
{
  struct XPR_UPDATE xpru;

  switch (Item) {
  case STMsg:
  	xpru.xpru_msg = S;
	xpru.xpru_updatemask = XPRU_MSG; break;
  case STFile:
  	xpru.xpru_filename = S;
	xpru.xpru_updatemask = XPRU_FILENAME; break;
  case STUplSize:
  	xpru.xpru_filesize = callad(xfinfo, S, 1L);
	xpru.xpru_updatemask = XPRU_FILESIZE; break;
  default:
	return;
  }
  calla(xupdate, &xpru);
}

ST_Display_CRC(Item)
int Item;
{
  struct XPR_UPDATE xpru;

  switch (Item) {
  case 1:
  	xpru.xpru_blockcheck = "Check-6"; break;
  case 2:
  	xpru.xpru_blockcheck = "Check-12"; break;
  case 3:
  	xpru.xpru_blockcheck = "CRC-16"; break;
  default:
	return;
  }
  xpru.xpru_updatemask = XPRU_BLOCKCHECK;

  calla(xupdate, &xpru);
}

extern char *p_pattern;

#define TRUE	1
#define FALSE	0

int gnfile(fname, len)
char *fname;
int len;	/* it is 50 */
{
	static firsttime = TRUE;
	char buffer[256];
	static long state;

	if (firsttime) {
		state = callaa(xffirst, buffer, p_pattern);
		if (state) firsttime = FALSE;
	} else {
		state = calldaa(xfnext, state, buffer, p_pattern);
		if (state == 0L) firsttime = TRUE;
	}
	if (state && strlen(buffer)<len) {
		strcpy(fname, buffer);
		return((int)state);
	} else	return(0);
}

char *EndPath(p)
char *p;
{
	char *q;

	q = p;
	while (*p != '\0') {
		if ((*p == '/') || (*p == ':'))
			q = p + 1;
		p++;
	}
	return(q);
}
