 /**********************************************************************
  * Utils.c: Miscellaneous support routines for xprzmodem.library;
  * Version 2.10, 12 February 1991, by Rick Huebner.
  * Released to the Public Domain; do as you like with this code.
  *
  * Version 2.50, 15 November 1991, by William M. Perkins.  Added code
  * to update_rate() function in utils.c to avoid the Guru # 80000005
  * you would have gotten if you had decided to adjust the system clock
  * back during an upload or download.
  *
  * Mysprintf() function to replace sprintf() and proto code to use
  * libinit.o and linent.o library code was supplied by Jim Cooper of SAS.
  *
  * Version 2.61, 3 July 1993 Mysprintf changed to xprsprintf(), written
  * in Assembler, because under SAS/C the old code running not correctly.
  *
  * Version 2.62, 27 July 1993 build in variable Blocksize.
  *
  * Version 2.63, 30 July 1993 build in locale, by Rainer Hess
  *
  * Version 2.64,  3 Aug 1993 global vaiable Blocksize, now in
  *                 struct Vars, by Rainer Hess
  *
  *                4 Aug 1993 changes in update_rate() function because
  * it always GURU 8000 0005 on little files (testet with 2 byte-file)
  * when you send. If on receiver the file exists when receiver ask for
  * overwrite, the machine crash if you click overwrite on receiver.
  * Testet on Term 3.4 -> Ncomm 2.0, by Rainer Hess
  *
  **********************************************************************/

#include "xprzmodem_all.h"

#define CATCOMP_NUMBERS
#include "xprzmodem_catalog.h"

/*
 * Transfer options to use if XProtocolSetup not called
 */

struct SetupVars Default_Config =
{
  NULL,				/* = *matchptr  */
  NULL,				/* = *bufpos    */
  0,				/* = buflen     */
  "C",				/* option_t[2]  */
  "N",				/* option_o[2]  */
  "16",				/* option_b[8]  */
  "0",				/* option_f[8]  */
  "10",				/* option_e[8]  */
  "N",				/* option_s[4]  */
  "N",				/* option_r[4]  */
  "Y",				/* option_a[4]  */
  "N",				/* option_d[4]  */
  "Y",				/* option_k[4]  */
  "",				/* option_p[256] */
  "1024"			/* option_m[8]  */
};

#ifdef DEBUGLOG
UBYTE DebugName[] = "T:XDebug.Log";
void *DebugLog = NULL;
#endif

struct DosLibrary *DOSBase;
struct ExecBase *SysBase;

#ifdef UP_TO_2X
struct Library *UtilityBase;
#endif

#define LIBRARY_ANY 0L

#define LocaleBase li.li_LocaleBase
#define catalog    li.li_Catalog

struct LocaleInfo li;

struct TagItem LocalTags[3] =
{
  OC_BuiltInLanguage, (ULONG) "english",
  OC_Version, 1,		/* Catalog-Nummer */
  TAG_DONE
};

/**********************************************************
 *      int __UserLibInit(struct Library *libbase)
 **********************************************************/
int __saveds __asm
__UserLibInit (register __a6 struct MyLibrary *libbase)
{
  SysBase = *(struct ExecBase **) 4L;
  if (!(DOSBase = (struct DosLibrary *) OpenLibrary ("dos.library", LIBRARY_ANY)))
    return (RETURN_FAIL);

#ifdef UP_TO_2X
  if (!(UtilityBase = OpenLibrary ("utility.library", 37L)))
    return (RETURN_FAIL);
#endif

  if (LocaleBase = OpenLibrary ("locale.library", 38L))
    catalog = OpenCatalogA (NULL, "xprzmodem.catalog", &LocalTags[0]);

  return (RETURN_OK);
}				/* End of __UserLibInit() */

/**********************************************************
 *      void __UserLibCleanup(struct Library *libbase)
 **********************************************************/
void __saveds __asm
__UserLibCleanup (register __a6 struct MyLibrary *libbase)
{
  if (DOSBase)
    CloseLibrary ((struct Library *) DOSBase);

#ifdef UP_TO_2X
  if (UtilityBase)
    CloseLibrary (UtilityBase);
#endif

  if (LocaleBase)
    {
      if (catalog)
	CloseCatalog (catalog);
      CloseLibrary (LocaleBase);
    }
}				/* End of __UserLibCleanup() */

/**********************************************************
 *      long XProtocolSetup(struct XPR_IO *xio)
 *
 * Called by comm program to set transfer options
 **********************************************************/
long __saveds __asm
XProtocolSetup (register __a0 struct XPR_IO *xio)
{
  struct SetupVars *sv, tempsv;
  struct xpr_option *option_ptrs[13];
  struct xpr_option *optr, xo_hdr, xo_t, xo_o, xo_b, xo_f, xo_e, xo_s,
    xo_r, xo_a, xo_d, xo_k, xo_p, xo_m;
  UBYTE buf[256], *p;
  long i, len;

  /* Allocate memory for transfer options string */
  if (!(sv = (void *) xio->xpr_data))
    {
      xio->xpr_data = AllocMem ((ULONG) sizeof (struct SetupVars), MEMF_CLEAR);
      if (!(sv = (void *) xio->xpr_data))
	{
	  ioerr (xio, "Not enough memory!");
	  return XPRS_FAILURE;
	}
      /* Start out with default options; merge user changes into defaults */
      *sv = Default_Config;
    }

  /* If options string passed by comm prog, use it; else prompt user */
  if (xio->xpr_filename)
    strcpy (buf, xio->xpr_filename);
  else
    {
      /* If xpr_options() implemented by comm program, use it */
      if (xio->xpr_extension >= 1 && xio->xpr_options)
	{
	  /*
	     * Let user edit temp copy of options so we can ignore invalid
	     * entries.  Have to init all this crud the hard way 'cause it's
	     * got to be on the stack in order to maintain reentrancy
	   */
	  tempsv = *sv;
	  xo_hdr.xpro_description = GetLocalString( &li, MSG_ZMODEM_OPTIONS );
	  xo_hdr.xpro_type = XPRO_HEADER;
	  xo_hdr.xpro_value = NULL;
	  xo_hdr.xpro_length = 0;
	  option_ptrs[0] = &xo_hdr;
	  xo_t.xpro_description = GetLocalString( &li, MSG_TEXT_MODE );
	  xo_t.xpro_type = XPRO_STRING;
	  xo_t.xpro_value = tempsv.option_t;
	  xo_t.xpro_length = sizeof (tempsv.option_t);
	  option_ptrs[1] = &xo_t;
	  xo_o.xpro_description = GetLocalString( &li, MSG_OVERWRITE_MODE );
	  xo_o.xpro_type = XPRO_STRING;
	  xo_o.xpro_value = tempsv.option_o;
	  xo_o.xpro_length = sizeof (tempsv.option_o);
	  option_ptrs[2] = &xo_o;
	  xo_b.xpro_description = GetLocalString( &li, MSG_IO_BUFFER_SIZE );
	  xo_b.xpro_type = XPRO_LONG;
	  xo_b.xpro_value = tempsv.option_b;
	  xo_b.xpro_length = sizeof (tempsv.option_b);
	  option_ptrs[3] = &xo_b;
	  xo_f.xpro_description = GetLocalString( &li, MSG_FRAME_SIZE );
	  xo_f.xpro_type = XPRO_LONG;
	  xo_f.xpro_value = tempsv.option_f;
	  xo_f.xpro_length = sizeof (tempsv.option_f);
	  option_ptrs[4] = &xo_f;
	  xo_e.xpro_description = GetLocalString( &li, MSG_ERROR_LIMIT );
	  xo_e.xpro_type = XPRO_LONG;
	  xo_e.xpro_value = tempsv.option_e;
	  xo_e.xpro_length = sizeof (tempsv.option_e);
	  option_ptrs[5] = &xo_e;
	  xo_a.xpro_description = GetLocalString( &li, MSG_AUTO_ACTIVATE_RECEIVER );
	  xo_a.xpro_type = XPRO_BOOLEAN;
	  xo_a.xpro_value = tempsv.option_a;
	  xo_a.xpro_length = sizeof (tempsv.option_a);
	  option_ptrs[6] = &xo_a;
	  xo_d.xpro_description = GetLocalString( &li, MSG_DELETE_AFTER_SENDING );
	  xo_d.xpro_type = XPRO_BOOLEAN;
	  xo_d.xpro_value = tempsv.option_d;
	  xo_d.xpro_length = sizeof (tempsv.option_d);
	  option_ptrs[7] = &xo_d;
	  xo_k.xpro_description = GetLocalString( &li, MSG_KEEP_PARTIAL_FILES );
	  xo_k.xpro_type = XPRO_BOOLEAN;
	  xo_k.xpro_value = tempsv.option_k;
	  xo_k.xpro_length = sizeof (tempsv.option_k);
	  option_ptrs[8] = &xo_k;
	  xo_s.xpro_description = GetLocalString( &li, MSG_SEND_FULL_PATH );
	  xo_s.xpro_type = XPRO_BOOLEAN;
	  xo_s.xpro_value = tempsv.option_s;
	  xo_s.xpro_length = sizeof (tempsv.option_s);
	  option_ptrs[9] = &xo_s;
	  xo_r.xpro_description = GetLocalString( &li, MSG_USE_RECEIVED_PATH );
	  xo_r.xpro_type = XPRO_BOOLEAN;
	  xo_r.xpro_value = tempsv.option_r;
	  xo_r.xpro_length = sizeof (tempsv.option_r);
	  option_ptrs[10] = &xo_r;
	  xo_p.xpro_description = GetLocalString( &li, MSG_DEFAULT_RECEIVE_PATH );
	  xo_p.xpro_type = XPRO_STRING;
	  xo_p.xpro_value = tempsv.option_p;
	  xo_p.xpro_length = sizeof (tempsv.option_p);
	  option_ptrs[11] = &xo_p;
	  xo_m.xpro_description = GetLocalString( &li, MSG_MAXIMUM_BLOCK_SIZE );
	  xo_m.xpro_type = XPRO_LONG;
	  xo_m.xpro_value = tempsv.option_m;
	  xo_m.xpro_length = sizeof (tempsv.option_m);
	  option_ptrs[12] = &xo_m;

	  /* Convert Y/N used elsewhere into "yes"/"no" required by spec */
	  for (i = 6; i <= 10; ++i)
	    {
	      optr = option_ptrs[i];
	      strcpy (optr->xpro_value, (*optr->xpro_value == 'Y') ? "yes" : "no");
	    }

	  (*xio->xpr_options) (13L, option_ptrs);

	  /* Convert "yes"/"no" or "on"/"off" into Y/N */
	  for (i = 6; i <= 10; ++i)
	    {
	      optr = option_ptrs[i];
	      strcpy (optr->xpro_value, (!stricmp (optr->xpro_value, "yes")
			 || !stricmp (optr->xpro_value, "on")) ? "Y" : "N");
	    }
	  /* Convert xpr_options() results into parseable options string */
	  xprsprintf (buf, "T%s,O%s,B%s,F%s,E%s,A%s,D%s,K%s,S%s,R%s,P%s,M%s",
	  tempsv.option_t, tempsv.option_o, tempsv.option_b, tempsv.option_f,
	  tempsv.option_e, tempsv.option_a, tempsv.option_d, tempsv.option_k,
		      tempsv.option_s, tempsv.option_r, tempsv.option_p, tempsv.option_m);
	  /* If xpr_options() not provided, try xpr_gets() instead */
	}
      else
	{
	  /* Start buffer with current settings so user can see/edit them. */
	  xprsprintf (buf, "T%s,O%s,B%s,F%s,E%s,A%s,D%s,K%s,S%s,R%s,P%s,M%s",
		      sv->option_t, sv->option_o, sv->option_b, sv->option_f,
		      sv->option_e, sv->option_a, sv->option_d, sv->option_k,
		    sv->option_s, sv->option_r, sv->option_p, sv->option_m);
	  if (xio->xpr_gets)
	    (*xio->xpr_gets) ( GetLocalString( &li, MSG_ZMODEM_OPTIONS ), buf);
	}
    }
  /* Upshift options string for easier parsing */
  strupr (buf);

  /*
     * Merge new T(ext) option into current settings if given
     *  "TY" = Force Text mode on,
     *  "TN" = Force Text mode off,
     *  "T?" = Use other end's text mode suggestion (default to binary)
     *  "TC" = Ask Comm program for file type
   */
  if (p = find_option (buf, 'T'))
    {
      if (*p == 'Y' || *p == 'N' || *p == '?' || *p == 'C')
	*sv->option_t = *p;
      else
	ioerr (xio, "Invalid T flag ignored; should be Y, N, ?, or C");
    }

  /*
     * Merge new O(verwrite) option into current settings if given
     *  "OY" = Yes, delete old file and replace with new one,
     *  "ON" = No, prevent overwrite by appending ".dup" to avoid name collision,
     *  "OR" = Resume transfer at end of existing file,
     *  "OS" = Skip file if it already exists; go on to next
   */
  if (p = find_option (buf, 'O'))
    {
      if (*p == 'R' && !xio->xpr_finfo)
	ioerr (xio, GetLocalString( &li, MSG_NO_XPR_FINFO ));
      else if (*p == 'Y' || *p == 'N' || *p == 'R' || *p == 'S')
	*sv->option_o = *p;
      else
	ioerr (xio, GetLocalString( &li, MSG_INVALID_O_FLAG ));
    }

  /*
     * Merge new B(uffer) setting into current settings if given
     * Size of file I/O buffer in kilobytes
   */
  if (p = find_option (buf, 'B'))
    {
      len = atol (p);
      if (len < 1)
	len = 1;
      xprsprintf (sv->option_b, "%ld", len);
    }

  /*
     * Merge new F(ramelength) setting into other settings if given
     * Number of bytes we're willing to send or receive between ACKs.
     * 0 = unlimited; nonstop streaming data
   */
  if (p = find_option (buf, 'F'))
    {
      len = atol (p);
      if (len < 0)
	len = 0;
      if (len > 0 && len < MINBLOCK)
	len = MINBLOCK;
      xprsprintf (sv->option_f, "%ld", len);
    }

  /*
     * Merge new E(rror limit) setting into other settings if given
     * Number of sequential errors which will cause an abort
   */
  if (p = find_option (buf, 'E'))
    {
      len = atol (p);
      if (len < 1)
	len = 1;
      if (len > 32767)
	len = 32767;
      xprsprintf (sv->option_e, "%ld", len);
    }

  /*
     * Merge new A(uto-activate) setting into other settings if given
     *  "AY" = Automatically call XProtocolReceive() if ZRQINIT string received
     *  "AN" = Don't look for ZRQINIT; user will explicitly activate receive
   */
  if (p = find_option (buf, 'A'))
    {
      if (*p == 'Y' || *p == 'N')
	*sv->option_a = *p;
      else
	ioerr (xio, GetLocalString( &li, MSG_INVALID_A_FLAG ));
    }

  /*
     * Merge new D(elete after sending) setting into other options
     *  "DY" = Delete files after successfully sending them
     *  "DN" = Don't delete files after sending
   */
  if (p = find_option (buf, 'D'))
    {
      if (*p == 'Y' && (xio->xpr_extension < 2 || !xio->xpr_unlink))
	ioerr (xio, GetLocalString( &li, MSG_NO_DY_XPR_UNLINK ));
      else if (*p == 'Y' || *p == 'N')
	*sv->option_d = *p;
      else
	ioerr (xio, GetLocalString( &li, MSG_INVALID_D_FLAG ));
    }

  /*
     * Merge new K(eep partial files) setting into other options
     *  "KY" = Keep partially-received file fragments to allow later resumption
     *  "KN" = Delete partially-received file fragments
   */
  if (p = find_option (buf, 'K'))
    {
      if (*p == 'N' && (xio->xpr_extension < 2 || !xio->xpr_unlink))
	ioerr (xio, GetLocalString( &li, MSG_NO_KN_XPR_UNLINK ));
      else if (*p == 'Y' || *p == 'N')
	*sv->option_k = *p;
      else
	ioerr (xio, GetLocalString( &li, MSG_INVALID_K_FLAG ));
    }

  /*
     * Merge new S(end full path) setting into other options
     *  "SY" = Send full filename including directory path to receiver
     *  "SN" = Send only simple filename portion, not including directory path
   */
  if (p = find_option (buf, 'S'))
    {
      if (*p == 'Y' || *p == 'N')
	*sv->option_s = *p;
      else
	ioerr (xio, GetLocalString( &li, MSG_INVALID_S_FLAG ));
    }

  /*
     * Merge new R(eceive path) setting into other options
     *  "RY" = Use full filename exactly as received; don't use P option path
     *  "RN" = Ignore received directory path if any; use path from P option
   */
  if (p = find_option (buf, 'R'))
    {
      if (*p == 'Y' || *p == 'N')
	*sv->option_r = *p;
      else
	ioerr (xio, GetLocalString( &li, MSG_INVALID_R_FLAG ));
    }

  /*
     * Merge new P(ath) setting into other options
     *  "Pdir" = Receive files into directory "dir" if RN selected
     *  "dir" can by any valid existing directory, with or without trailing "/"
   */
  if (p = find_option (buf, 'P'))
    {
      strcpy (sv->option_p, p);
      p = sv->option_p + strcspn (sv->option_p, " ,\t\r\n");
      *p = '\0';
    }

  /*
     * Maximum packet size. Must be <=8192
   */

  if (p = find_option (buf, 'M'))
    {
      len = atol (p);
      if (len < MINBLOCK)
	len = MINBLOCK;
      if (len > MAXGOODNEEDED)
	len = KSIZE;

      xprsprintf (sv->option_m, "%ld", len);
    }

  return (*sv->option_a == 'Y') ? XPRS_SUCCESS | XPRS_NORECREQ | XPRS_HOSTMON
    : XPRS_SUCCESS | XPRS_NORECREQ;
}				/* End of long XProtocolSetup() */

/**********************************************************
 *      long XProtocolCleanup(struct XPR_IO *xio)
 *
 * Called by comm program to give us a chance to clean
 * up before program ends
 **********************************************************/
long __saveds __asm
XProtocolCleanup (register __a0 struct XPR_IO *xio)
{
  /* Release option memory, if any */
  if (xio->xpr_data)
    {
      FreeMem (xio->xpr_data, (long) sizeof (struct SetupVars));
      xio->xpr_data = NULL;
    }

  return XPRS_SUCCESS;
}				/* End of long XProtocolCleanup() */

/**********************************************************
 *      long XProtocolHostMon(struct XPR_IO *xio,
 *         char *serbuff, long actual, long maxsize)
 *
 * Called by comm program upon our request (XPRS_HOSTMON)
 * to let us monitor the incoming data stream for our
 * receiver auto-activation string (ZRQINIT packet).
 * We only ask for this to be called if option AY is set.
 **********************************************************/
long __saveds __asm
XProtocolHostMon (
		   register __a0 struct XPR_IO *xio,
		   register __a1 char *serbuff,
		   register __d0 long actual,
		   register __d1 long maxsize)
{
  static UBYTE startrcv[] =
  {
    ZPAD, ZDLE, ZHEX, '0', '0'
  };
  struct SetupVars *sv;

  if (!(sv = (void *) xio->xpr_data))
    return actual;		/* XProtocolSetup() never called?! */

  if (!sv->matchptr)
    sv->matchptr = startrcv;

  /*
     * Scan through serbuff to see if we can match all bytes in the start
     * string in sequence.
   */
  for (sv->bufpos = serbuff; sv->bufpos < serbuff + actual; ++sv->bufpos)
    {
      if (*sv->bufpos == *sv->matchptr)
	{			/* if data matches current position in match */
	  ++sv->matchptr;	/* string, increment match position */
	  if (!*sv->matchptr)
	    {			/* if at end of match string, it all matched */
	      sv->buflen = actual - (sv->bufpos - serbuff);
	      XProtocolReceive (xio);
	      sv->matchptr = startrcv;
	      actual = 0;
	      break;
	    }
	}
      else if (sv->matchptr > startrcv)
	{			/* mismatch?  Reset to start of match string */
	  sv->matchptr = startrcv;
	  if (*sv->bufpos == *sv->matchptr)
	    ++sv->matchptr;
	}
    }

  sv->bufpos = NULL;
  return actual;
}

/**********************************************************
 *      long XProtocolUserMon(struct XPR_IO *xio,
 *         char *serbuff, long actual, long maxsize)
 *
 * Called by comm program to let us monitor user's inputs;
 * we never ask for this to be called, but it's better to
 * recover gracefully than guru the machine.
 **********************************************************/
long __saveds __asm
XProtocolUserMon (
		   register __a0 struct XPR_IO *xio,
		   register __a1 char *serbuff,
		   register __d0 long actual,
		   register __d1 long maxsize)
{
  return actual;
}

/**********************************************************
 *      struct Vars *setup(struct XPR_IO *io)
 *
 * Perform setup and initializations common to both Send
 * and Receive routines
 **********************************************************/
struct Vars *
setup (struct XPR_IO *io)
{
  static long bauds[] =
  {
    110, 300, 1200, 2400, 4800, 9600, 19200, 31250,
    38400, 57600, 76800, 115200};
  struct SetupVars *sv;
  struct Vars *v;
  long origbuf, newstatus;

#ifdef DEBUGLOG
  long i, *lng;
#endif

  /* Make sure comm program supports the required call-back functions */
  if (!io->xpr_update)
    return NULL;

  if (!io->xpr_fopen || !io->xpr_fclose || !io->xpr_fread
      || !io->xpr_fwrite || !io->xpr_fseek || !io->xpr_sread
      || !io->xpr_swrite)
    {
      ioerr (io, GetLocalString( &li, MSG_COMM_PROG_MISSING ));
      return NULL;
    }

  /* Hook in default transfer options if XProtocolSetup wasn't called */
  if (!(sv = (void *) io->xpr_data))
    {
      io->xpr_data = AllocMem ((ULONG) sizeof (struct SetupVars), MEMF_CLEAR);
      if (!(sv = (void *) io->xpr_data))
	{
	  ioerr (io, GetLocalString( &li, MSG_NOT_ENOUGH_MEMORY ));
	  return NULL;
	}
      *sv = Default_Config;
    }

  /* Allocate memory for our unshared variables, to provide reentrancy */
  if (!(v = AllocMem ((ULONG) sizeof (struct Vars), MEMF_CLEAR)))
    {
    nomem:
      ioerr (io, GetLocalString( &li, MSG_NOT_ENOUGH_MEMORY ));
      return NULL;
    }
  v->Modemchar = v->Modembuf;

  /*
     * Allocate memory for our file I/O buffer; if we can't get as much as
     * requested, keep asking for less until we hit minimum before giving up
   */
  v->Filebufmax = origbuf = atol (sv->option_b) * 1024;
  while (!(v->Filebuf = AllocMem ((ULONG) v->Filebufmax, MEMF_ANY)))
    {

      if (v->Filebufmax > 1024)
	v->Filebufmax -= 1024;
      else
	{
	  FreeMem (v, (long) sizeof (struct Vars));
	  goto nomem;
	}
    }

  /* If framelength was intended to match buffer size, stay in sync */
  v->Tframlen = atol (sv->option_f);
  if (v->Tframlen && v->Tframlen == origbuf)
    v->Tframlen = v->Filebufmax;

  v->ErrorLimit = atol (sv->option_e);

  /* Copy caller's io struct into our Vars for easier passing */
  v->io = *io;

#ifdef DEBUGLOG
  if (!DebugLog)
    DebugLog = (char *) (*v->io.xpr_fopen) (DebugName, "w");
  dlog (v, "XPR_IO struct:\n");
  for (i = 0, lng = (long *) io; i < sizeof (struct XPR_IO) / 4; ++i)
    {
      xprsprintf (v->Msgbuf, "  %08lx\n", *lng++);
      dlog (v, v->Msgbuf);
    }
  D (DEBUGINFO);
#endif

  /* Get baud rate; set serial port mode if necessary (and possible) */
  if (v->io.xpr_setserial)
    {
      v->Oldstatus = (*v->io.xpr_setserial) (-1L);
      if (v->Oldstatus != -1)
	{
	  /*
	     * ZModem requires 8 data bits, no parity (full transparency),
	     *  leave other settings alone
	   */
	  newstatus = v->Oldstatus & 0xFFFFE0BC;
	  /*
	     * newstatus |= on_flags; Here's where we'd turn bits on if we
	     * needed to
	   */
	  if (newstatus != v->Oldstatus)
	    (*v->io.xpr_setserial) (newstatus);
	  v->Baud = bauds[(newstatus >> 16) & 0xFF];

#ifdef DEBUGLOG
	  xprsprintf (v->Msgbuf,
		      "Old serial status = %lx, new = %lx, baud = %ld\n",
		      v->Oldstatus, newstatus, v->Baud);
	  dlog (v, v->Msgbuf);
	  D (DEBUGINFO);
#endif

	}
      else
	v->Baud = 2400;
      /* If no xpr_setserial(), muddle along with most likely guess */
    }
  else
    v->Baud = 2400;

  v->ksize = atol(sv->option_m);

  return v;
}				/* End of struct Vars *setup() */

/**********************************************************
 *      void set_textmode(struct Vars *v)
 *
 * Set text/binary mode flags in accordance with T option
 * setting
 **********************************************************/
void
set_textmode (struct Vars *v)
{
  struct SetupVars *sv;
  long i;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  sv = (void *) v->io.xpr_data;
  switch (*sv->option_t)
    {
    case 'Y':			/* Force text mode on receive; suggest text mode on send */
    TY:
      v->Rxascii = TRUE;
      v->Rxbinary = FALSE;
      v->Lzconv = ZCNL;
      break;
    case 'N':			/* Force binary mode on receive; suggest binary mode on send */
    TN:
      v->Rxascii = FALSE;
      v->Rxbinary = TRUE;
      v->Lzconv = ZCBIN;
      break;
    case 'C':			/* Ask comm program for proper mode for this file */
      if (v->io.xpr_finfo)
	{
	  i = (*v->io.xpr_finfo) (v->Filename, 2L);
	  if (i == 1)		/* Comm program says use binary mode */
	    goto TN;
	  if (i == 2)		/* Comm program says use text mode */
	    goto TY;
	}
      /* xpr_finfo() not provided (or failed); default to T? */
    case '?':
      v->Rxascii = v->Rxbinary = FALSE;
      v->Lzconv = 0;
      break;
    }
}				/* End of void set_textmode() */

/**********************************************************
 *      UBYTE *find_option(UBYTE *buf, UBYTE option)
 *
 * Search for specified option setting in string
 **********************************************************/
UBYTE *
find_option (UBYTE * buf, UBYTE option)
{
  while (*buf)
    {
      buf += strspn (buf, " ,\t\r\n");
      if (*buf == option)
	return ++buf;
      buf += strcspn (buf, " ,\t\r\n");
    }

  return NULL;
}				/* End of UBYTE *find_option() */

/**********************************************************
 *      void canit(struct Vars *v)
 *
 * send cancel string to get the other end to shut up
 **********************************************************/
void
canit (struct Vars *v)
{
  static char canistr[] =
  {
    24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
    8, 8, 8, 8, 8, 8, 8, 8, 8, 8, 0};

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  zmputs (v, canistr);
}				/* End of void canit() */

/**********************************************************
 *      void zmputs(struct Vars *v, UBYTE *s)
 *
 * Send a string to the modem, with processing for \336 (sleep 1 sec)
 * and \335 (break signal, ignored since XPR spec doesn't support it)
 **********************************************************/
void
zmputs (struct Vars *v, UBYTE * s)
{
  UBYTE c;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  while (*s)
    {
      switch (c = *s++)
	{
	case '\336':
	  Delay (50L);
	case '\335':
	  break;
	default:
	  sendline (v, c);
	}
    }
  sendbuf (v);
}				/* End of void zmputs() */

/**********************************************************
 *      void xsendline(struct Vars *v, UBYTE c)
 *
 * Write one character to the modem
 **********************************************************/
void
xsendline (struct Vars *v, UBYTE c)
{
  v->Outbuf[v->Outbuflen++] = c;
  if (v->Outbuflen >= sizeof (v->Outbuf))
    sendbuf (v);

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

}				/* End of void xsendline() */

/**********************************************************
 *      void sendbuf(struct Vars *v)
 *
 * Send any data waiting in modem output buffer
 **********************************************************/
void
sendbuf (struct Vars *v)
{
#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  if (v->Outbuflen)
    {
      (*v->io.xpr_swrite) (v->Outbuf, (long) v->Outbuflen);
      v->Outbuflen = 0;
    }
}				/* End of void sendbuf() */

/**********************************************************
 *      short readock(struct Vars *v, short tenths)
 *
 * Get a byte from the modem;
 * return TIMEOUT if no read within timeout tenths of a
 * second, return RCDO if carrier lost or other fatal error
 * (sread returns -1).  Added in some buffering so we
 * wouldn't hammer the system with single-byte serial port
 * reads.  Also, the buffering makes char_avail() a lot
 * easier to implement.
 **********************************************************/
short
readock (struct Vars *v, short tenths)
{
  long t;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  /* If there's data waiting in our buffer, return next byte */
  if (v->Modemcount)
    {
    gotdata:
      --v->Modemcount;
      return (short) (*v->Modemchar++);
    }
  /*
     * Our buffer is empty; see if there's anything waiting in system buffer.
     * If the caller is in a hurry, don't wait around, but if it can spare
     * a half second, wait a bit and build up some input so we don't do as
     * many sread() calls.
   */
  t = (tenths < 5) ? 0 : 500000;

#ifdef DEBUGLOG
  xprsprintf (v->Msgbuf,
	      "Input buffer empty; calling sread for %ld bytes, %ld usec\n",
	      (long) sizeof (v->Modembuf), t);
  dlog (v, v->Msgbuf);
  D (DEBUGINFO);
#endif
  v->Modemcount = (*v->io.xpr_sread) (v->Modembuf, (long) sizeof (v->Modembuf), t);

#ifdef DEBUGLOG
  xprsprintf (v->Msgbuf, "   sread returned %ld\n", v->Modemcount);
  dlog (v, v->Msgbuf);
  D (DEBUGINFO);
#endif
  if (v->Modemcount < 0)	/* Carrier dropped or other fatal error; abort */
    {
      v->Modemcount = 0;
      return RCDO;
    }
  else if (!v->Modemcount)	/* Nothing in system buffer; try waiting */
    {
      t = tenths * 100000L - t;
#ifdef DEBUGLOG
      xprsprintf (v->Msgbuf, "   calling sread for 1 byte, %ld usec\n", t);
      dlog (v, v->Msgbuf);
      D (DEBUGINFO);
#endif
      v->Modemcount = (*v->io.xpr_sread) (v->Modembuf, 1L, t);
#ifdef DEBUGLOG
      xprsprintf (v->Msgbuf, "   sread returned %ld\n", v->Modemcount);
      dlog (v, v->Msgbuf);
      D (DEBUGINFO);
#endif
      if (v->Modemcount < 0)
	{
	  v->Modemcount = 0;
	  return RCDO;
	}
      else if (!v->Modemcount)	/* Nothing received in time */
	return TIMEOUT;
    }
  v->Modemchar = v->Modembuf;	/* Reset buffer pointer to start of data */
  goto gotdata;
}				/* End of short readock() */

/**********************************************************
 *      char char_avail(struct Vars *v)
 *
 * Check if there's anything available to read from the
 * modem
 **********************************************************/
char
char_avail (struct Vars *v)
{
#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  if (v->Modemcount)
    return TRUE;

  /* No data in our buffer; check system's input buffer */
  v->Modemcount = (*v->io.xpr_sread)
    (v->Modembuf, (long) sizeof (v->Modembuf), 0L);
  if (v->Modemcount < 1)	/* Nothing in system buffer either */
    {
      v->Modemcount = 0;
      return FALSE;
    }
  else
    /* System buffer had something waiting for us */
    {
      v->Modemchar = v->Modembuf;
      return TRUE;
    }
}				/* End of char char_avail() */

/**********************************************************
 *      void update_rate(struct Vars *v)
 *
 * Update the elapsed time, expected total time, and
 * effective data transfer rate values for status display
 **********************************************************/
void
update_rate (struct Vars *v)
{
  ULONG sent, elapsed, expect;
  short hr, min;
  struct timeval tv;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  /* Compute effective data rate so far in characters per second */
  sent = v->xpru.xpru_bytes - v->Strtpos;
  getsystime (&tv);
  elapsed = (tv.tv_secs & 0x7FFFFF) * 128 + tv.tv_micro / 8192;
  elapsed -= (v->Starttime.tv_secs & 0x7FFFFF) * 128
    + v->Starttime.tv_micro / 8192;
  if (elapsed < 128 || elapsed > 0x7FFFFF)
    /*                      ^^^^^^^^^^^^^^^^^^ Kludge for the GURU!  -WMP- */
    elapsed = 128;
  /*
     * If we haven't transferred anything yet (just starting), make reasonable
     * guess (95% throughput); otherwise, compute actual effective transfer
     * rate
   */
  v->xpru.xpru_datarate = (sent) ? (sent * 128 / elapsed)
    : (v->Baud * 95 / 1000);

  /* Compute expected total transfer time based on data rate so far */
  if (v->xpru.xpru_filesize < 0)
    expect = 1;		/* Don't know filesize; display time=1; was 0 thas to nice for GURU 8000 0005 */
  else
  {
   /* Save the 8000 0005 GURU!!! */
   if((v->xpru.xpru_datarate != 0) && ((v->xpru.xpru_filesize - v->Strtpos) != 0))
      expect = (v->xpru.xpru_filesize - v->Strtpos) / v->xpru.xpru_datarate;
    else
      expect = 1; /* Display 1 secs better than nothing... */
  }

  hr = expect / 3600;		/* How many whole hours */
  expect -= hr * 3600;		/* Remainder not counting hours */
  min = expect / 60;		/* How many whole minutes */
  expect -= min * 60;		/* Remaining seconds */
  xprsprintf (v->Msgbuf, "%02ld:%02ld:%02ld", (long) hr, (long) min, expect);
  v->xpru.xpru_expecttime = (char *) v->Msgbuf;

  /* Compute elapsed time for this transfer so far */
  elapsed /= 128;
  hr = elapsed / 3600;
  elapsed -= hr * 3600;
  min = elapsed / 60;
  elapsed -= min * 60;
  xprsprintf (v->Msgbuf + 20, "%02ld:%02ld:%02ld", (long) hr, (long) min,
	      elapsed);
  v->xpru.xpru_elapsedtime = (char *) v->Msgbuf + 20;
}				/* End of void update_rate() */

/**********************************************************
 *      long bfopen(struct Vars *v, UBYTE *mode)
 *
 * Buffered file I/O fopen() interface routine
 **********************************************************/
long
bfopen (struct Vars *v, UBYTE * mode)
{
  /* Initialize file-handling variables */
  v->Filebufpos = v->Filebuflen = v->Filebufcnt = 0;
  v->Fileflush = FALSE;
  v->Filebufptr = v->Filebuf;
  /* Open the file */
#ifdef DEBUGLOG
  xprsprintf (v->Msgbuf, "bfopen: %s %s\n", v->Filename, mode);
  dlog (v, v->Msgbuf);
  D (DEBUGINFO);
#endif
  return (*v->io.xpr_fopen) (v->Filename, mode);
}				/* End of long bfopen() */

/**********************************************************
 *      void bfclose(struct Vars *v)
 *
 * Buffered file I/O fclose() interface routine
 **********************************************************/
void
bfclose (struct Vars *v)
{
#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  if (v->File)
    {
      /* If bfwrite() left data in buffer, flush it out before closing */
      if (v->Fileflush)
	(*v->io.xpr_fwrite) (v->Filebuf, 1L, v->Filebufcnt, v->File);
      /* Close the file */
      (*v->io.xpr_fclose) (v->File);
      v->File = NULL;
    }
}				/* End of void bfclose() */

/**********************************************************
 *      void bfseek(struct Vars *v, long pos)
 *
 * Buffered file I/O fseek() interface routine
 **********************************************************/
void
bfseek (struct Vars *v, long pos)
{
  long offset;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  /* If new file position is within currently buffered section,
     reset pointers */
  if (pos >= v->Filebufpos && pos < v->Filebufpos + v->Filebuflen)
    {
      offset = pos - v->Filebufpos;
      v->Filebufptr = v->Filebuf + offset;
      v->Filebufcnt = v->Filebuflen - offset;
      /* Otherwise, fseek() file & discard buffer contents to force new read */
    }
  else
    {
      (*v->io.xpr_fseek) (v->File, pos, 0L);
      v->Filebuflen = v->Filebufcnt = 0;
      v->Filebufpos = pos;
    }
}				/* End of void bfseek() */

/**********************************************************
 *      long bfread(struct Vars *v, UBYTE *buf, long length)
 *
 * Buffered file I/O fread() interface routine
 **********************************************************/
long
bfread (struct Vars *v, UBYTE * buf, long length)
{
  long count, total = 0;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  /* Keep going until entire request completed */
  while (length > 0)
    {
      /* Copy as much of the request as possible from the buffer */
      count = (length <= v->Filebufcnt) ? length : v->Filebufcnt;
      CopyMem (v->Filebufptr, buf, count);
#ifdef DEBUGLOG
      xprsprintf (v->Msgbuf, "bfread got %ld bytes from buffer\n", count);
      dlog (v, v->Msgbuf);
      D (DEBUGINFO);
#endif
      buf += count;
      total += count;
      length -= count;
      v->Filebufptr += count;
      v->Filebufcnt -= count;

      /* If we've emptied the buffer, read next buffer's worth */
      if (!v->Filebufcnt)
	{
	  v->Filebufpos += v->Filebuflen;
	  v->Filebufptr = v->Filebuf;
	  v->Filebufcnt = v->Filebuflen
	    = (*v->io.xpr_fread) (v->Filebuf, 1L, v->Filebufmax, v->File);
#ifdef DEBUGLOG
	  xprsprintf (v->Msgbuf, "bfread read %ld bytes\n", v->Filebufcnt);
	  dlog (v, v->Msgbuf);
	  D (DEBUGINFO);
#endif
	  /* If we hit the EOF, return with however much we read so far */
	  if (!v->Filebufcnt)
	    break;
	}
    }
  return total;
}				/* End of long bfread() */

/**********************************************************
 *      long bfwrite(struct Vars *v, UBYTE *buf, long length)
 *
 * Buffered file I/O fwrite() interface routine
 **********************************************************/
long
bfwrite (struct Vars *v, UBYTE * buf, long length)
{
  long count, total = 0;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  /* Keep going until entire request completed */
  while (length > 0)
    {
      /* Copy as much as will fit into the buffer */
      count = v->Filebufmax - v->Filebufcnt;
      if (length < count)
	count = length;
      CopyMem (buf, v->Filebufptr, count);
#ifdef DEBUGLOG
      xprsprintf (v->Msgbuf, "bfwrite buffered %ld bytes\n", count);
      dlog (v, v->Msgbuf);
      D (DEBUGINFO);
#endif
      buf += count;
      total += count;
      length -= count;
      v->Filebufptr += count;
      v->Filebufcnt += count;
      v->Fileflush = TRUE;

      /* If we've filled the buffer, write it out */
      if (v->Filebufcnt == v->Filebufmax)
	{
	  count = (*v->io.xpr_fwrite) (v->Filebuf, 1L, v->Filebufcnt, v->File);
#ifdef DEBUGLOG
	  xprsprintf (v->Msgbuf, "bfwrite wrote %ld bytes\n", count);
	  dlog (v, v->Msgbuf);
	  D (DEBUGINFO);
#endif
	  if (count < v->Filebufcnt)
	    return -1;
	  v->Filebufptr = v->Filebuf;
	  v->Filebufcnt = 0;
	  v->Fileflush = FALSE;
	}
    }
  return total;
}				/* End of long bfwrite() */

/**********************************************************
 *      void ioerr(struct XPR_IO *io, char *msg)
 *
 * 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 (struct XPR_IO *io, char *msg)
{
  struct XPR_UPDATE xpru;

  if (io->xpr_update)
    {
      xpru.xpru_updatemask = XPRU_ERRORMSG;
      xpru.xpru_errormsg = msg;
      (*io->xpr_update) (&xpru);
    }
}				/* End of void ioerr() */

/**********************************************************
 *      void upderr(struct Vars *v, char *msg)
 *
 * Have the comm program display an error message for us, using the
 * normal XPR_IO structure allocated in Vars
 **********************************************************/
void
upderr (struct Vars *v, char *msg)
{
  v->xpru.xpru_updatemask = XPRU_ERRORMSG;
  v->xpru.xpru_errormsg = msg;
  if (msg == v->Msgbuf)		/* Ensure message length < 50 */
    msg[48] = '\0';
  (*v->io.xpr_update) (&v->xpru);
#ifdef DEBUGLOG
  dlog (v, msg);
  dlog (v, "\n");
  D (DEBUGINFO);
#endif
}				/* End of void upderr() */

/**********************************************************
 *      void updmsg(struct Vars *v,char *msg)
 *
 * Have the comm program display a normal message for us
 **********************************************************/
void
updmsg (struct Vars *v, char *msg)
{
#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  v->xpru.xpru_updatemask = XPRU_MSG;
  v->xpru.xpru_msg = msg;
  if (msg == v->Msgbuf)		/* Ensure message length < 50 */
    msg[48] = '\0';
  (*v->io.xpr_update) (&v->xpru);
#ifdef DEBUGLOG
  dlog (v, msg);
  dlog (v, "\n");
  D (DEBUGINFO);
#endif
}				/* End of void updmsg() */

/**********************************************************
 *      long getfree(void)
 *
 * Figure out how many bytes are free on the drive we're uploading to.
 * Stubbed out for now; not supported by XPR spec.
 **********************************************************/
long
getfree (void)
{
  return 0x7FFFFFFF;
}				/* End of long getfree() */

/**********************************************************
 *      char exist(struct Vars *v)
 *
 * Check whether file already exists; used to detect
 * potential overwrites
 **********************************************************/
char
exist (struct Vars *v)
{
  long file;

#ifdef DEBUGLOG
  D (DEBUGINFO);
#endif

  file = (*v->io.xpr_fopen) (v->Filename, "r");
  if (file)
    {
      (*v->io.xpr_fclose) (file);
      return TRUE;
    }
  else
    return FALSE;
}				/* End of char exist() */

#ifdef DEBUGLOG
/**********************************************************
 *      void dlog(struct Vars *v, UBYTE *s)
 *
 * Write a message to the debug log
 **********************************************************/
void
dlog (struct Vars *v, UBYTE * s)
{
  /* Open the debug log if it isn't already open */
  if (!DebugLog)
    DebugLog = (char *) (*v->io.xpr_fopen) (DebugName, "a");
  (*v->io.xpr_fwrite) (s, 1L, (long) strlen (s), (long) DebugLog);
  /*
     * Close file to flush output buffer; comment these two lines out if
     * you aren't crashing your system and don't mind waiting until the
     * transfer finishes to look at your log file.
   */
  /* (*v->io.xpr_fclose)((long)DebugLog);
     * DebugLog = NULL;
   */

}				/* End of void dlog() */
#endif
/* End of Utils.c source */
