/** xprkermit.c
*
*   These are the protocol transfer routines for a simple Kermit upload/dnload
*
*   Version 0.9--by Marco Papa.
*
*   Version 1.0--updated by Stephen Walton.
*	Added several more user selections to XPRotocolSetup():
*	bctr (block check type to request), limit (retry-limit), and
*	rtimo (timeout for me).  "Real" C Kermit makes many of the
*	things declared as local to kermitproto.w user-settable,
*	such as the send packet size.
*	Also fixed several problems in kermitproto.w.
*
*   Version 1.5--more features
*	Extensive changes.
*	(1) Created a SetupVars variable to hold setting-up information
*	in the XPR_IO structure.  This will allow re-entrancy when and if
*	it comes.  Right now, it mainly gives a way to set up defaults with
*	one assignment.
*	(2) Added the generic Kermit server functions FINISH, BYE, and CD.
*	(3) Added the ability to set options and execute the server functions
*	from a setup string (sent as IO->xpr_filename to XPRotocolSetup()).
*
*	This code is copyright 1990 by Stephen Walton and Marco Papa.  It may
*	be freely distributed in its original or in modified form, provided
*	this copyright notice is kept intact.  It may not be sold for profit,
*	but may be included as part of a commercial program provided that
*	said inclusion does not increase the cost of that program beyond a
*	modest handling fee.
**/
#include <exec/exec.h>
#include <functions.h>
#include <stdio.h>

/*
 * xproto.h is the include file given in Appendix B.
 */

#include "xproto.h"
#include "xprkermit.h"
#include "kermitproto.h"

/*
 * The following two strings must exist.
 */
char            XPRname[] = "xprkermit.library";
char            XPRid[] = "xprkermit 1.5 (9 December 1989)\r\n";
UWORD           XPRrevision = 5;

long            atol();
/*
 * The callxx...() routines are described later. They provide the assembler
 * interface from the XPR library to the call-back routines.
 */
long
calla(), callaa(), callad(), calladd(), calladda(), calldaa(),
callda(), calld();

/* IMPORTANT: this makes it non-reentrant !!!! */
long            (*xupdate) (), (*xswrite) (), (*xfopen) (), (*xfclose) (), (*xfread) (),
                (*xsread) (), (*xchkabort) (), (*xfnext) (), (*xffirst) (), (*xsflush) (),
                (*xfwrite) (), (*xgets) (), (*xfinfo) ();

/*
 * External and forward declarations.
 */

char           *malloc(), *strcpy(), *index();
void            ioerr(), XPRLong();
SetupVars      *setup();
extern char     start;

/*
 * The flags for the kermitproto.w module.
 */
char           *p_pattern;	/* wildcard pattern */
int             parity;		/* parity on? 0 for no parity--need for
				 * proper 8th-bit quote */
int             text;		/* Text or binary mode? Flag 1 for text file,
				 * 0 for binary file	 */
int             convert;	/* Convert file names to lower case? 0 for
				 * literal files (no), 1 for translate (yes) */
int             urpsiz;		/* Kermit maximum packet size. Maximum
				 * receive packet size user wants.	 */
char           *cmarg;		/* Character string containing Kermit server
				 * cmd */

extern int      bctr;		/* Block check type to request. */
extern int      limit;		/* Retry limit.  May increase on very noisy
				 * lines. */
extern int      rtimo;		/* Timeout to request. */

int             getfile;	/* Host server. 0 = Receive (no) ; 1 = Get
				 * (yes) */
long            brkflag;

static SetupVars Defaults = {
    {""},			/* No default file name. */
    0,				/* Parity defaults to off. */
    1,				/* Text file defaults to on. */
    1,				/* Convert file names by default. */
    94,				/* Default maximum packet length. */
    1,				/* Default block check type. */
    5,				/* Retry limit */
    10,				/* Timeout (seconds) */
    0,				/* Host is server?  No by default. */
};

/**
*
*   Send a file
*
**/
long
XProtocolSend(IO)
    struct XPR_IO  *IO;
{
    struct XPR_UPDATE xpru;
    SetupVars      *sv;

    brkflag = 0;

    if ((sv = setup(IO)) == NULL)
	return 0L;		/* Initialize parameters. */
    /*
     * Read the text/binary set using xpr_finfo if present.  Else use the
     * value chosen in XPRotocolSetup.
     */
    if (xfinfo) {
	/*
	 * Use feature that calling xpr_finfo with a zero-length filename
	 * returns setting of internal comm program Text/Binary flag.
	 */
	text = (callad(xfinfo, IO->xpr_filename, 2L) == 1 ? 0 : 1);
    }
    /*
     * Start the transfer. See 3.8 for a discussion on how to implement
     * xupdate.
     */
    tchar('#');

    /*
     * Copy filename, and put pointer in external Kermit variable.
     */
    strcpy(sv->FileName, IO->xpr_filename);
    p_pattern = sv->FileName;
    /*
     * */
    start = 's';
    proto();

    /*
     * If we got here through chkabort() say Aborted.
     */
    xpru.xpru_updatemask = XPRU_MSG;
    if (brkflag)
	xpru.xpru_msg = "Aborted";
    else
	xpru.xpru_msg = "Done";
    (void) calla(xupdate, &xpru);
    if (brkflag)
	return (0L);
    else
	return (1L);
}


/**
*
*   Receive a file.
*
**/
long
XProtocolReceive(IO)
    struct XPR_IO  *IO;
{
    struct XPR_UPDATE xpru;
    long            status;
    SetupVars      *sv;


    brkflag = 0;

    if ((sv = setup(IO)) == NULL)
	return 0L;			/* Initialize parameters. */
    /*
     * Read the text/binary set using xpr_finfo if present.  Else use the
     * value chosen in XPRotocolSetup.
     */
    if (xfinfo) {
	/*
	 * Use feature th at calling xpr_finfo with a zero-length filename
	 * returns setting of internal comm program Text/Binary flag.
	 */
	text = (callad(xfinfo, "", 2L) == 1 ? 0 : 1);
    }
    /*
     * Start the transfer. See 3.8 for a discussion on how to implement
     * xupdate.
     */
    tchar('#');

    /*
     * */
    if (getfile) {
	/*
	 * Copy filename, and put pointer in external Kermit variable.
	 */
	cmarg = sv->FileName;
	start = 'r';
	status = callaa(xgets, "Host Filename", cmarg);
	if (!status)
	    return (0L);
    } else
	start = 'v';
    proto();

    /*
     * If we got here through chkabort() say Aborted.
     */
    xpru.xpru_updatemask = XPRU_MSG;
    if (brkflag)
	xpru.xpru_msg = "Aborted";
    else
	xpru.xpru_msg = "Done";
    (void) calla(xupdate, &xpru);
    if (brkflag)
	return (0L);
    else
	return (1L);
}

/*
 * Perform a generic Kermit server command.
 */
static
DoGeneric(IO, s)
    struct XPR_IO  *IO;
    char           *s;
{
    SetupVars *sv;

    if ((sv = setup(IO)) == NULL)
	return 0;			/* Set up transfer characteristics. */
    brkflag = 0;
    start = 'g';
    cmarg = s;
    proto();
    if (brkflag)
	return (0);
    else
	return (1);
}

/*
 * Execute a Kermit FINISH command.
 */
static
KermitFinish(IO)
    struct XPR_IO  *IO;
{
    return (DoGeneric(IO, "F"));
}

/*
 * Execute a Kermit BYE command.
 */
static
KermitBye(IO)
    struct XPR_IO  *IO;
{
    return (DoGeneric(IO, "L"));
}

/*
 * Change directory on remote server.  We need to return an error if the CD
 * fails on the remote end.
 */
static
KermitCd(IO, dir)
    struct XPR_IO  *IO;
    char           *dir;
{
    char            CdCommand[100];
    int             retval;

    CdCommand[0] = 'C';
    CdCommand[1] = tochar(strlen(dir));
    strcpy(CdCommand + 2, dir);
    retval = DoGeneric(IO, CdCommand);
    return retval;
}

#if AZTEC_C
/*
 * Case-independent string comparison for Aztec C. Modification of the
 * strcmp() routine from Henry Spencer's portable string library.  Depends on
 * a toupper() function (as opposed to macro).
 */

int				/* <0 for <, 0 for ==, >0 for > */
stricmp(s1, s2)
    char           *s1;
    char           *s2;
{
    register char  *scan1;
    register char  *scan2;

    scan1 = s1;
    scan2 = s2;
    while (*scan1 != '\0' && toupper(*scan1) == toupper(*scan2)) {
	scan1++;
	scan2++;
    }

    /*
     * The following case analysis is necessary so that characters which look
     * negative collate low against normal characters but high against the
     * end-of-string NUL.
     */
    if (*scan1 == '\0' && *scan2 == '\0')
	return (0);
    else if (*scan1 == '\0')
	return (-1);
    else if (*scan2 == '\0')
	return (1);
    else
	return (toupper(*scan1) - toupper(*scan2));
}

#endif

/**
*
*   Setup
*
* First, a general-purpose comparison for either of the two possible returns
* indicating a Yes push on a Boolean gadget.
**/

#define XprBoolTrue(s) ((stricmp(s, "yes") == 0) || (stricmp(s, "on") == 0))

/*
 * Then, a small set of code to initialize a string based on a value.
 */

#define XprSet(value, string) (value ? \
   (void) strcpy(string, YesString) : \
   (void) strcpy(string, NoString)) \

static char     YesString[] = "yes";
static char     NoString[] = "no";

/*
 * If Setup() succeeds, flag same.  Also, we don't need a file requester
 * on receive.
 */
#define SUCCESS (XPRS_SUCCESS | XPRS_NORECREQ)

long
XProtocolSetup(IO)
    struct XPR_IO  *IO;
{
    long            (*xupdate) (), (*xgets) (), (*xoptions) (), (*xfinfo) ();
    struct XPR_UPDATE xpru;
#define NOPTS 8
    struct xpr_option opt[NOPTS], *popt[NOPTS];
#define MAXSTRING 6
    char            ValueStrings[NOPTS][MAXSTRING];
    long            status;
    int             i, j;
    char            buf[256];
    SetupVars      *sv, tempvar;

    if ((xupdate = IO->xpr_update) == NULL)
	return (XPRS_FAILURE);
    if ((xgets = IO->xpr_gets) == NULL)
	return (XPRS_FAILURE);

    /*
     * Allocate memory for file name buffer and options if first call. Copy
     * defaults into it and then merge in user changes.
     */
    if ((sv = (SetupVars *) IO->xpr_data) == NULL) {
	if ((sv = (SetupVars *) malloc((unsigned) sizeof(SetupVars))) == NULL)
	    return XPRS_FAILURE;
	*sv = Defaults;
	IO->xpr_data = (long *) sv;
    }
    tempvar = *sv;
    /*
     * In order to use xpr_options, we must have all of the three conditions
     * which follow true.  Otherwise, either IO->xpr_filename is non-NULL, in
     * which case we assume it contains a setup string, or there is no
     * xpr_options, in which case we use xpr_gets to retrieve a setup string.
     */
    if (IO->xpr_filename == NULL &&
	IO->xpr_extension >= 1 &&
	(xoptions = IO->xpr_options) != NULL) {

	/*
	 * I use a counter, i, here, so that I can stick in more options
	 * without needing to change a lot of numbers.  I can also skip
	 * options dynamically, as in Text below.
	 */
#if 1
	i = 0;
	/* Announce us. */
	opt[i].xpro_description = "Kermit Commands 1.5";
	opt[i].xpro_type = XPRO_HEADER;
	opt[i].xpro_value = NULL;
	opt[i].xpro_length = 0;
	i++;
	/*
	 * First, do the Kermit server command items.
	 */
	opt[i].xpro_description = "Kermit FINISH";
	opt[i].xpro_type = XPRO_COMMAND;
	opt[i].xpro_value = NULL;
	opt[i].xpro_length = 0;
	i++;
	opt[i].xpro_description = "Kermit BYE";
	opt[i].xpro_type = XPRO_COMMAND;
	opt[i].xpro_value = NULL;
	opt[i].xpro_length = 0;
	i++;
	opt[i].xpro_description = "Kermit CD";
	opt[i].xpro_type = XPRO_COMMPAR;
	buf[0] = '\0';
	opt[i].xpro_value = buf;		/* Use buf to hold dir name. */
	opt[i].xpro_length = sizeof(buf) - 1;
	i++;
	opt[i].xpro_description = "Kermit Options";
	opt[i].xpro_type = XPRO_COMMAND;
	opt[i].xpro_value = NULL;
	opt[i].xpro_length = 0;
	i++;
	/* show requester after loading pointers */
	for (j = 0; j < i; j++)
	    popt[j] = &opt[j];
	status = callda(xoptions, (long) i, popt);
	/* check returned value */
	if (status == -1L)
	    return XPRS_FAILURE;
	/* Check returned value to see what we are to do. */
	i = 1;
	if (status & (1L << i))
	    return (KermitFinish(IO) ? SUCCESS : XPRS_FAILURE);
	i++;
	if (status & (1L << i))
	    return (KermitBye(IO) ? SUCCESS : XPRS_FAILURE);
	i++;
	if (status & (1L << i))
	    return (KermitCd(IO, opt[i].xpro_value) ? SUCCESS : XPRS_FAILURE);
#endif
	/* If we get to this point, we are to set options. */
	i = 0;			/* Start over. */
	opt[i].xpro_description = "Kermit Options 1.5";
	opt[i].xpro_type = XPRO_HEADER;
	opt[i].xpro_value = NULL;
	opt[i].xpro_length = 0;
	i++;
	/* Convert filename */
	opt[i].xpro_description = "Convert Filename";
	opt[i].xpro_type = XPRO_BOOLEAN;
	XprSet(tempvar.ConvertFlag, ValueStrings[i]);
	opt[i].xpro_value = ValueStrings[i];
	opt[i].xpro_length = MAXSTRING;
	i++;
	/* host is server */
	opt[i].xpro_description = "Host Server";
	opt[i].xpro_type = XPRO_BOOLEAN;
	XprSet(tempvar.GetFlag, ValueStrings[i]);
	opt[i].xpro_value = ValueStrings[i];
	opt[i].xpro_length = MAXSTRING;
	i++;
	/*
	 * file type -- only show this if xpr_finfo not present, or returns
	 * error when we try to get the file type.
	 */
	if ((xfinfo = IO->xpr_finfo) == NULL ||
	    (tempvar.TextFlag = (callad(xfinfo, "", 2L))) == 0) {
	    tempvar.TextFlag = -1;	/* Flag xfinfo failed. */
	    opt[i].xpro_description = "Text File";
	    opt[i].xpro_type = XPRO_BOOLEAN;
	    XprSet(tempvar.TextFlag, ValueStrings[i]);
	    opt[i].xpro_value = ValueStrings[i];
	    opt[i].xpro_length = MAXSTRING;
	    i++;
	} else
	    /*
	     * Switch to 0 for binary, 1 for text; xpr_finfo returns 1 for
	     * binary, 2 for text.
	     */
	    tempvar.TextFlag -= 1;
	/* Packet size */
	opt[i].xpro_description = "Packet Size";
	opt[i].xpro_type = XPRO_LONG;
	(void) sprintf(ValueStrings[i], "%-d", tempvar.MaxPacket);
	opt[i].xpro_value = ValueStrings[i];
	opt[i].xpro_length = MAXSTRING;
	i++;
	/* Block Check type */
	opt[i].xpro_description = "Block Check (1, 2, 3)";
	opt[i].xpro_type = XPRO_LONG;
	(void) sprintf(ValueStrings[i], "%-d", tempvar.BlockCheckType);
	opt[i].xpro_value = ValueStrings[i];
	opt[i].xpro_length = MAXSTRING;
	i++;
	/* Retry Limit */
	opt[i].xpro_description = "Maximum Retries";
	opt[i].xpro_type = XPRO_LONG;
	(void) sprintf(ValueStrings[i], "%-d", tempvar.RetryLimit);
	opt[i].xpro_value = ValueStrings[i];
	opt[i].xpro_length = MAXSTRING;
	i++;
	/* Timeout */
	opt[i].xpro_description = "Timeout (seconds)";
	opt[i].xpro_type = XPRO_LONG;
	(void) sprintf(ValueStrings[i], "%-d", tempvar.Timeout);
	opt[i].xpro_value = ValueStrings[i];
	opt[i].xpro_length = MAXSTRING;
	i++;
	/* show requester after loading pointers */
	for (j = 0; j < i; j++)
	    popt[j] = &opt[j];
	/* show requester */
	status = callda(xoptions, (long) i, popt);
	/* check returned value */
	if (status == -1L)
	    return XPRS_FAILURE;
	i = 1;			/* Skip header		 */
	if (status & (1L << i))
	    tempvar.ConvertFlag = XprBoolTrue(opt[i].xpro_value) ? 1 : 0;
	i++;
	if (status & (1L << i))
	    tempvar.GetFlag = XprBoolTrue(opt[i].xpro_value) ? 1 : 0;
	i++;
	if (xfinfo == NULL || tempvar.TextFlag == -1) {
	    if (status & (1L << i))
		tempvar.TextFlag = XprBoolTrue(opt[i].xpro_value) ? 1 : 0;
	    i++;
	}
	if (status & (1L << i))
	    tempvar.MaxPacket = atoi(opt[i].xpro_value);
	i++;
	if (status & (1L << i))
	    tempvar.BlockCheckType = atoi(opt[i].xpro_value);
	i++;
	if (status & (1L << i))
	    tempvar.RetryLimit = atoi(opt[i].xpro_value);
	i++;
	if (status & (1L << i))
	    tempvar.Timeout = atoi(opt[i].xpro_value);
    } else {
	if (IO->xpr_filename != NULL)
	    strcpy(buf, IO->xpr_filename);	/* Save setup string */
	else {			/* Prompt for command/options string */
	    sprintf(buf, "OC%c,G%c,T%c,P%d,B%d,R%d,O%d", tempvar.ConvertFlag ? 'Y' : 'N',
		    tempvar.GetFlag ? 'Y' : 'N', tempvar.TextFlag ? 'Y' : 'N', tempvar.MaxPacket,
	       tempvar.BlockCheckType, tempvar.RetryLimit, tempvar.Timeout);
	    if (callaa(xgets, "Kermit Options", buf) == 0)
		return XPRS_FAILURE;	/* Failed to set up? */
	}
	switch (buf[0]) {
	case 'F':
	    return (KermitFinish(IO) ? SUCCESS : XPRS_FAILURE);
	case 'B':
	    return (KermitBye(IO) ? SUCCESS : XPRS_FAILURE);
	case 'C':
	    return (KermitCd(IO, buf + 1) ? SUCCESS : XPRS_FAILURE);
	case 'O':
	    if (SetupFromString(IO, buf, &tempvar) == 0)
		return XPRS_FAILURE;
	    break;
	case '\0':
	    break;
	default:
	    ioerr(IO, "Unrecognized XPR Kermit setup string");
	    break;
	}
    }
    *sv = tempvar;		/* Copy setups into safe place. */
    /*
     * Return success and inform caller that we don't need a requester for
     * receive.
     */
    return SUCCESS;
}

static char Delimiters[] = " \t\r\n,";

SetupFromString(IO, s, sv)
    struct XPR_IO  *IO;
    char           *s;
    SetupVars      *sv;
{
    char           *p, *strtok();
    char            errbuf[50];

    if (*s != 'O')
	return 0;		/* Options string must start with O. */
    s++;			/* Skip leading O. */
    /*
     * Hunt for options with strtok.  We allow whitespace and commas to
     * separate options.
     */
    for (p = strtok(s, Delimiters); p != NULL; p = strtok(NULL, Delimiters)) {
	switch (*p++) {				/* Auto-increment to option */
	case 'C':				/* Case conversion. */	
	    if (*p == 'Y' || *p == 'N')
		sv->ConvertFlag = (*p == 'Y');
	    else
		ioerr(IO, "Illegal C option format (must be Y or N)");
	    break;
	case 'G':				/* Get files (Host server) */
	    if (*p == 'Y' || *p == 'N')
		sv->GetFlag = (*p == 'Y');
	    else
		ioerr(IO, "Illegal G option format (must be Y or N)");
	    break;
	case 'T':				/* Text file */
	    if (*p == 'Y' || *p == 'N')
		sv->TextFlag = (*p == 'Y');
	    else
		ioerr(IO, "Illegal T option format (must be Y or N)");
	    break;
	case 'P':				/* Maximum packet length */
	    sv->MaxPacket = atoi(p);
	    break;
	case 'B':				/* Block check type. */
	    sv->BlockCheckType = atoi(p);
	    break;
	case 'R':				/* Retry limit */
	    sv->RetryLimit = atoi(p);
	    break;
	case 'O':				/* Timeout */
	    sv->Timeout = atoi(p);
	    break;
	default:
	    sprintf(errbuf, "Illegal XPR Kermit option: %c", p[-1]);
	    ioerr(IO, errbuf);
	    break;
	}
    }
    return 1;
}

/**
*
*   Cleanup
*
**/
long
XProtocolCleanup(IO)
    struct XPR_IO  *IO;
{
    if (IO->xpr_data)
	free(IO->xpr_data);
    IO->xpr_data = NULL;

    return (1L);
}

int
XPRParity(IO)
    struct XPR_IO  *IO;
{
    long            (*xsetserial) ();
    struct XPR_UPDATE xpru;
    long            status;

    /* check out parity */
    if (xsetserial = IO->xpr_setserial) {
	status = calld(xsetserial, -1L);
	if (status & 0x00000001L)
	    return 1;
	else
	    return 0;
    } else
	return 0;		/* Assume no parity if can't tell. */
}

void
XPRLong(IO, i)
    struct XPR_IO  *IO;
    long            i;
{
    long            (*xupdate) ();
    struct XPR_UPDATE xpru;
    char            locbuf[80];

    if ((xupdate = IO->xpr_update) == NULL)
	return;
    /* debug: show long value */
    xpru.xpru_updatemask = XPRU_MSG;
    sprintf(locbuf, "%lx", i);
    xpru.xpru_msg = &locbuf[0];
    calla(xupdate, &xpru);
}

/*
 * Copy setup variables into the local copies.  If there are none, then
 * simply copy the defaults.
 */
static SetupVars *
setup(IO)
    struct XPR_IO  *IO;
{
    register SetupVars *Current;

    /*
     * These are the call-backs we need. If any of them isn't provided, quit.
     * Could do some error reporting if at least xupdate is there.
     */
    if ((xupdate = IO->xpr_update) == NULL)
	return (0L);
    if ((xswrite = IO->xpr_swrite) == NULL)
	return (0L);
    if ((xfopen = IO->xpr_fopen) == NULL)
	return (0L);
    if ((xfclose = IO->xpr_fclose) == NULL)
	return (0L);
    if ((xfread = IO->xpr_fread) == NULL)
	return (0L);
    if ((xsread = IO->xpr_sread) == NULL)
	return (0L);
    if ((xchkabort = IO->xpr_chkabort) == NULL)
	return (0L);
    if ((xfnext = IO->xpr_fnext) == NULL)
	return (0L);
    if ((xffirst = IO->xpr_ffirst) == NULL)
	return (0L);
    if ((xsflush = IO->xpr_sflush) == NULL)
	return (0L);
    if ((xfwrite = IO->xpr_fwrite) == NULL)
	return (0L);
    if ((xgets = IO->xpr_gets) == NULL)
	return (0L);
    xfinfo = IO->xpr_finfo;

    if (IO->xpr_data == NULL) {
	if ((IO->xpr_data = (long *) malloc((unsigned) sizeof(SetupVars)))
            == NULL) {
	    ioerr(IO, "Out of memory!");
	    return NULL;
	}
	Current = (SetupVars *) IO->xpr_data;
	*Current = Defaults;
    } else
	Current = (SetupVars *) IO->xpr_data;
    parity = Current->ParityFlag = XPRParity(IO);
    text = Current->TextFlag;
    convert = Current->ConvertFlag;
    urpsiz = Current->MaxPacket;
    bctr = Current->BlockCheckType;
    limit = Current->RetryLimit;
    rtimo = Current->Timeout;
    getfile = Current->GetFlag;
    return Current;
}

/*
 * Have the comm program display an error message for us, using a temporary
 * XPR_UPDATE structure; used to display errors before Vars gets allocated
 */
void
ioerr(IO, msg)
    struct XPR_IO  *IO;
    char           *msg;
{
    struct XPR_UPDATE xpru;
    long            (*xupdate) ();

    if (xupdate = IO->xpr_update) {
	xpru.xpru_updatemask = XPRU_ERRORMSG;
	xpru.xpru_errormsg = msg;
	(void) calla(xupdate, &xpru);
    }
}

#if AZTEC_C
/*
 * Simple, re-entrant versions of malloc() and free to replace the ones in
 * the Aztec C libraries.  The only reason to use these instead of AllocMem()
 * is that these remember the size of the stuff allocated.
 */

char *malloc(n)
unsigned n;
{
    long *p;

    if ((p = AllocMem((long) n + sizeof(long), MEMF_PUBLIC | MEMF_CLEAR)) == NULL)
	return NULL;
    p[0] = n;
    return ((char *) &p[1]);
}

free(p)
char *p;
{
    long *s = (long *) p;

    FreeMem(&s[-1], s[-1]);
    return 1;
}
#endif
/**
*
*   The following functions setup the proper registers for the call-back 
*   functions.
*
**/
#ifndef _lint

#asm
        public _callad
_callad:
        movea.l 8(sp),a0                ; Second argument goes in a0
        move.l  12(sp),d0               ; Third  argument goes in d0
/*
*   Now this is a trick to avoid using another register.
*   Charlie taught me this...
*/
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _calladda
_calladda:
        movea.l 8(sp),a0                ; Second argument goes in a0
        move.l  12(sp),d0               ; Third  argument goes in d0
        move.l  16(sp),d1               ; Fourth argument goes in d1
        movea.l 20(sp),a1               ; Fifth  argument goes in a1
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _calldaa
_calldaa:
        move.l  8(sp),d0                ; Second  argument goes in d0
        movea.l 12(sp),a0               ; Third argument goes in a0
        movea.l 16(sp),a1               ; Fourth argument goes in a1
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _calla
_calla:
        movea.l 8(sp),a0                ; Second argument goes in a0
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _calld
_calld:
        move.l  8(sp),d0                ; Second argument goes in d0
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _callaa
_callaa:
        movea.l 8(sp),a0                ; Second argument goes in a0
        movea.l 12(sp),a1               ; Third  argument goes in a1
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _callda
_callda:
        move.l  8(sp),d0                ; Second argument goes in d0
        movea.l 12(sp),a0               ; Third  argument goes in a0
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

        public  _calladd
_calladd:
        movea.l  8(sp),a0               ; Second argument goes in a0
        move.l  12(sp),d0               ; Third  argument goes in d0
        move.l  16(sp),d1               ; Fourth argument goes in d1
        move.l  4(sp),-(sp)             ; First  argument is function
        rts

#endasm
/*
*   Could have added any other functions needed for other call-backs.
*   Could have written a fancier single one... Could've...
*/
#endif
